1 /*
2 * Copyright (C) 2002 2003 2004 2005 2006 2008 2010, Magnus Hjorth
3 *
4 * This file is part of mhWaveEdit.
5 *
6 * mhWaveEdit is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * mhWaveEdit is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with mhWaveEdit; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 */
20
21
22 #include <config.h>
23 #include <gdk/gdkkeysyms.h>
24
25 #include <unistd.h>
26 #include <errno.h>
27 #include <string.h>
28 #include <signal.h>
29 #include <sys/time.h>
30 #include <sys/types.h>
31 #include <sys/wait.h>
32 #include <fcntl.h>
33
34 #include <gtk/gtk.h>
35
36 #include "main.h"
37 #include "pipedialog.h"
38 #include "um.h"
39 #include "inifile.h"
40 #include "tempfile.h"
41 #include "effectbrowser.h"
42 #include "gettext.h"
43
44 struct pipe_data {
45 int fds[3];
46 gchar *command;
47 GtkWidget *error_log;
48 };
49
50 extern char **environ;
51
pipe_dialog_apply_proc(Chunk * chunk,StatusBar * bar,gpointer user_data)52 static Chunk *pipe_dialog_apply_proc(Chunk *chunk, StatusBar *bar,
53 gpointer user_data)
54 {
55 gchar *cmd;
56 gboolean sendwav;
57 PipeDialog *pd = PIPE_DIALOG(user_data);
58 Chunk *r;
59 off_t clipcount = 0;
60 cmd = history_box_get_value(pd->cmd);
61 sendwav = gtk_toggle_button_get_active(pd->sendwav);
62 inifile_set_gboolean("PipeDialog_sendWav",sendwav);
63 r = pipe_dialog_pipe_chunk(chunk,cmd,sendwav,dither_editing,bar,
64 &clipcount);
65 if (r != NULL && clipwarn(clipcount,TRUE)) {
66 gtk_object_sink(GTK_OBJECT(r));
67 r = NULL;
68 }
69
70 return r;
71 }
72
pipe_dialog_apply(EffectDialog * ed)73 static gboolean pipe_dialog_apply(EffectDialog *ed)
74 {
75 gboolean b;
76 PipeDialog *pd = PIPE_DIALOG(ed);
77 b = document_apply_cb(EFFECT_BROWSER(ed->eb)->dl->selected,
78 pipe_dialog_apply_proc,TRUE,pd);
79 history_box_rotate_history(pd->cmd);
80 return b;
81 }
82
pipe_dialog_init(PipeDialog * pd)83 static void pipe_dialog_init(PipeDialog *pd)
84 {
85 GtkWidget *a,*b,*c;
86 c = gtk_vbox_new(FALSE,3);
87 gtk_container_add(EFFECT_DIALOG(pd)->input_area,c);
88 gtk_widget_show(c);
89 a = gtk_hbox_new(FALSE,0);
90 gtk_box_pack_start(GTK_BOX(c),a,FALSE,FALSE,0);
91 gtk_widget_show(a);
92 b = gtk_label_new(_("Command line: "));
93 gtk_box_pack_start(GTK_BOX(a),b,FALSE,FALSE,0);
94 gtk_widget_show(b);
95 b = history_box_new("PipeDialog","");
96 gtk_box_pack_start(GTK_BOX(a),b,TRUE,TRUE,0);
97 gtk_widget_show(b);
98 pd->cmd = HISTORY_BOX(b);
99 a = gtk_check_button_new_with_label(_("Send wav header"));
100 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(a),
101 inifile_get_gboolean("PipeDialog_sendWav",
102 FALSE));
103 gtk_box_pack_start(GTK_BOX(c),a,FALSE,FALSE,0);
104 gtk_widget_show(a);
105 pd->sendwav = GTK_TOGGLE_BUTTON(a);
106
107 }
108
pipe_dialog_class_init(EffectDialogClass * edc)109 static void pipe_dialog_class_init(EffectDialogClass *edc)
110 {
111 edc->apply = pipe_dialog_apply;
112 }
113
pipe_dialog_get_type(void)114 GtkType pipe_dialog_get_type(void)
115 {
116 static GtkType id = 0;
117 if (!id) {
118 GtkTypeInfo info = {
119 "PipeDialog",
120 sizeof(PipeDialog),
121 sizeof(PipeDialogClass),
122 (GtkClassInitFunc) pipe_dialog_class_init,
123 (GtkObjectInitFunc) pipe_dialog_init
124 };
125 id = gtk_type_unique(effect_dialog_get_type(),&info);
126 }
127 return id;
128 }
129
add_error_text(GtkWidget * t,gchar * text)130 static void add_error_text(GtkWidget *t,gchar *text)
131 {
132 #if GTK_MAJOR_VERSION < 2
133 gtk_text_insert(GTK_TEXT(t),gtk_style_get_font(t->style),t->style->fg,
134 &(t->style->white),text,-1);
135 #else
136 GtkTextBuffer *tb;
137 GtkTextIter iter;
138 tb = gtk_text_view_get_buffer(GTK_TEXT_VIEW(t));
139 gtk_text_buffer_get_end_iter(tb,&iter);
140 gtk_text_buffer_insert(tb, &iter, text, -1);
141 #endif
142 }
143
create_error_window(gchar * command)144 static GtkWidget *create_error_window(gchar *command)
145 {
146 GtkWidget *a,*b,*c;
147 GtkWidget *t;
148 gchar *q;
149 GtkAccelGroup* ag;
150
151 ag = gtk_accel_group_new();
152 a = gtk_window_new(GTK_WINDOW_DIALOG);
153 gtk_window_set_title(GTK_WINDOW(a),_("Process output"));
154 gtk_window_set_position (GTK_WINDOW (a), GTK_WIN_POS_CENTER);
155 gtk_window_set_default_size(GTK_WINDOW(a),470,200);
156 b = gtk_vbox_new(FALSE,0);
157 gtk_container_add(GTK_CONTAINER(a),b);
158 #if GTK_MAJOR_VERSION < 2
159 c = gtk_text_new(NULL,NULL);
160 #else
161 c = gtk_text_view_new();
162 #endif
163 t = c;
164 gtk_container_add(GTK_CONTAINER(b),c);
165 c = gtk_button_new_with_label(_("Close"));
166 gtk_widget_add_accelerator (c, "clicked", ag, GDK_KP_Enter, 0,
167 (GtkAccelFlags) 0);
168 gtk_widget_add_accelerator (c, "clicked", ag, GDK_Return, 0,
169 (GtkAccelFlags) 0);
170 gtk_widget_add_accelerator (c, "clicked", ag, GDK_Escape, 0,
171 (GtkAccelFlags) 0);
172
173 gtk_signal_connect_object(GTK_OBJECT(c),"clicked",
174 GTK_SIGNAL_FUNC(gtk_widget_destroy),
175 GTK_OBJECT(a));
176 gtk_box_pack_start(GTK_BOX(b),c,FALSE,FALSE,0);
177
178 q = g_strdup_printf(_("Output from command '%s':\n\n"),command);
179 add_error_text(t,q);
180 g_free(q);
181
182 gtk_widget_show_all(a);
183 gtk_window_add_accel_group(GTK_WINDOW (a), ag);
184
185 return t;
186 }
187
pipe_dialog_open_pipe(gchar * command,int * fds,gboolean open_out)188 gpointer pipe_dialog_open_pipe(gchar *command, int *fds, gboolean open_out)
189 {
190 int cmd_in[2]={},cmd_out[2]={},cmd_err[2]={};
191 unsigned char syncbyte;
192 struct pipe_data *pd;
193 pid_t p;
194 gchar *c;
195 char *argv[4];
196 int i;
197
198 /* Create pipes */
199 if (pipe(cmd_in)==-1 || (open_out && pipe(cmd_out)==-1) ||
200 pipe(cmd_err)==-1) {
201 c = g_strdup_printf(_("Could not create pipe: %s"),strerror(errno));
202 user_error(c);
203 g_free(c);
204 if (!cmd_in[0]) close(cmd_in[0]);
205 if (!cmd_in[1]) close(cmd_in[1]);
206 if (!cmd_out[0]) close(cmd_out[0]);
207 if (!cmd_out[1]) close(cmd_out[1]);
208 if (!cmd_err[0]) close(cmd_err[0]);
209 return NULL;
210 }
211 /* Fork */
212 p = fork();
213
214 if (p == -1) {
215 /* Error (parent process) */
216 c = g_strdup_printf(_("Error: fork: %s"),strerror(errno));
217 user_error(c);
218 g_free(c);
219 close(cmd_in[0]);
220 close(cmd_in[1]);
221 if (open_out) {
222 close(cmd_out[0]);
223 close(cmd_out[1]);
224 }
225 close(cmd_err[0]);
226 close(cmd_err[1]);
227 return NULL;
228 }
229
230 if (p == 0) {
231 /* Child process */
232 /* Redirect streams */
233 /* puts("Child: redirecting streams"); */
234 /* i = dup(1); */
235 dup2(cmd_in[0],0);
236 if (open_out)
237 dup2(cmd_out[1],1);
238 dup2(cmd_err[1],2);
239 /* Close the streams we don't use */
240 /* write(i,"Child: closing streams\n",23); */
241 close(cmd_in[0]);
242 close(cmd_in[1]);
243 if (open_out) {
244 close(cmd_out[0]);
245 close(cmd_out[1]);
246 }
247 close(cmd_err[0]);
248 close(cmd_err[1]);
249 /* Synchronize */
250 if (open_out) {
251 i = read(0,&syncbyte,1);
252 g_assert(i == 1 && syncbyte == 0xAB);
253 syncbyte = 0xCD;
254 i = write(1,&syncbyte,1);
255 g_assert(i == 1);
256 }
257 /* Execute subprocess */
258 argv[0] = "sh";
259 argv[1] = "-c";
260 argv[2] = command;
261 argv[3] = NULL;
262 /* write(i,"Child: execve!\n",15); */
263 fflush(stdout);
264 close_all_files();
265 if (execve("/bin/sh",argv,environ) == -1) {
266 console_perror("/bin/sh");
267 } else
268 console_message(_("Should not reach this point!"));
269 _exit(1);
270 }
271
272 /* Parent process */
273 /* Close the streams we don't use. */
274 close(cmd_in[0]);
275 if (open_out)
276 close(cmd_out[1]);
277 close(cmd_err[1]);
278
279 /* synchronize to child */
280 if (open_out) {
281 syncbyte = 0xAB;
282 i = write(cmd_in[1],&syncbyte,1);
283 g_assert(i == 1);
284 i = read(cmd_out[0],&syncbyte,1);
285 g_assert(i == 1 && syncbyte == 0xCD);
286 }
287
288 pd = g_malloc(sizeof(*pd));
289 pd->fds[0] = fds[0] = cmd_in[1];
290 pd->fds[1] = fds[1] = cmd_out[0];
291 pd->fds[2] = fds[2] = cmd_err[0];
292 pd->error_log = NULL;
293 pd->command = g_strdup(command);
294 return (gpointer)pd;
295 }
296
pipe_dialog_close(gpointer handle)297 void pipe_dialog_close(gpointer handle)
298 {
299 struct pipe_data *pd = (struct pipe_data *)handle;
300 if (pd->fds[0] != 0) close(pd->fds[0]);
301 if (pd->fds[1] != 0) close(pd->fds[1]);
302 close(pd->fds[2]);
303 g_free(pd->command);
304 g_free(pd);
305 wait(NULL);
306 }
307
pipe_dialog_error_check(gpointer handle)308 gboolean pipe_dialog_error_check(gpointer handle)
309 {
310 struct pipe_data *pd = (struct pipe_data *)handle;
311 fd_set set;
312 int i;
313 struct timeval tv = {};
314 gchar c[512];
315 FD_ZERO(&set);
316 FD_SET(pd->fds[2],&set);
317 i = select(pd->fds[2]+1,&set,NULL,NULL,&tv);
318 if (i < 1) return FALSE;
319 i = read(pd->fds[2],c,sizeof(c)-1);
320 if (i == 0) return FALSE;
321 c[i] = 0;
322 if (!pd->error_log) pd->error_log = create_error_window(pd->command);
323 add_error_text(pd->error_log,c);
324 return TRUE;
325 }
326
pipe_dialog_close_input(gpointer handle)327 void pipe_dialog_close_input(gpointer handle)
328 {
329 struct pipe_data *pd = (struct pipe_data *)handle;
330 close(pd->fds[0]);
331 pd->fds[0] = 0;
332 }
333
334 #define BZ 65536
335
pipe_dialog_pipe_chunk_main(Chunk * chunk,gchar * command,gboolean sendwav,gboolean do_read,StatusBar * bar,gboolean * sent_all,int dither_mode,off_t * clipcount)336 static Chunk *pipe_dialog_pipe_chunk_main(Chunk *chunk, gchar *command,
337 gboolean sendwav, gboolean do_read,
338 StatusBar *bar, gboolean *sent_all,
339 int dither_mode, off_t *clipcount)
340 {
341 int fds[3],bs,bp,i;
342 gboolean writing=TRUE;
343 gboolean send_header = sendwav;
344 off_t ui,read_bytes=0;
345 gchar *inbuf=NULL,*outbuf,*c;
346 ChunkHandle *ch=NULL;
347 TempFile ct=0;
348 fd_set rset,wset;
349 gpointer pipehandle;
350
351 /*
352 puts("---");
353 puts(command);
354 puts("///");
355 */
356
357 if (sent_all) *sent_all = FALSE;
358
359 pipehandle = pipe_dialog_open_pipe(command, fds, do_read);
360 if (pipehandle == NULL) return NULL;
361
362 /* Open chunk and create buffer */
363 ch = chunk_open(chunk);
364 if (!ch) {
365 pipe_dialog_close(pipehandle);
366 return NULL;
367 }
368 if (do_read) {
369 ct = tempfile_init(&(chunk->format),FALSE);
370 if (!ct) {
371 chunk_close(ch);
372 pipe_dialog_close(pipehandle);
373 return NULL;
374 }
375 }
376 status_bar_begin_progress(bar,ch->size,NULL);
377 if (do_read) inbuf = g_malloc(BZ);
378 outbuf = g_malloc(BZ);
379 ui = 0;
380 bs = bp = 0;
381 /* We want to handle broken pipes ourselves. */
382 signal(SIGPIPE,SIG_IGN);
383 /* Read/write data... */
384 /* If do_read is TRUE, this loop will keep running until the
385 * child process closes it's standard output (=our data
386 * input). Otherwise, the loop will keep running until all data has
387 * been sent to the program */
388 while (do_read || writing) {
389 /* Wait for IO */
390 FD_ZERO(&rset);
391 if (do_read) FD_SET(fds[1],&rset);
392 FD_SET(fds[2],&rset);
393 FD_ZERO(&wset);
394 if (writing) FD_SET(fds[0],&wset);
395 /* printf("Waiting (writing=%d)\n",writing); */
396 i = select(FD_SETSIZE,&rset,&wset,NULL,NULL);
397 /* printf("Wait finished.\n"); */
398 if (i == -1) {
399 if (errno == EINTR) continue;
400 goto errno_error_exit;
401 }
402
403 /* Read data from standard output */
404 if (FD_ISSET(fds[1],&rset)) {
405 /* printf("Reading from stdin..\n"); */
406 i = read(fds[1],inbuf,BZ);
407 /* printf("Read finished\n"); */
408 if (i == -1) { if (errno != EINTR) goto errno_error_exit; }
409 else if (i == 0) { break; }
410 else { if (tempfile_write(ct,inbuf,i)) goto error_exit; }
411 read_bytes += i;
412 continue;
413 }
414
415 /* Read data from standard error... */
416 if (FD_ISSET(fds[2],&rset))
417 pipe_dialog_error_check(pipehandle);
418
419
420 /* Send data to process's stdin */
421 if (writing && FD_ISSET(fds[0],&wset)) {
422 /* Make sure the buffer contains data */
423 if (bs == bp) {
424 if (send_header) {
425 /* FIXME: Broken for float on bigendian machines */
426 bs = get_wav_header(&(chunk->format),chunk->size,
427 outbuf);
428 bp = 0;
429 send_header = FALSE;
430 } else {
431 if (ui == chunk->length) {
432 writing = FALSE;
433 pipe_dialog_close_input(pipehandle);
434 continue;
435 }
436 i = chunk_read_array(ch,ui,BZ,outbuf, dither_mode,
437 clipcount);
438 if (!i) goto error_exit;
439 bs = i;
440 bp = 0;
441 /* if (IS_BIGENDIAN && sendwav)
442 byteswap(c,chunk->format.samplesize,bs); */
443 ui += i / chunk->format.samplebytes;
444 }
445 }
446 /* printf("Writing data...\n"); */
447 /* Write data */
448 #ifdef PIPE_BUF
449 i = write(fds[0],outbuf+bp,MIN(bs-bp,PIPE_BUF));
450 #else
451 i = write(fds[0],outbuf+bp,MIN(bs-bp,fpathconf(fds[0], _PC_PIPE_BUF)));
452 #endif
453 /* i = write(fds[0],outbuf+bp,1); */
454 /* printf("Finished writing.\n"); */
455 if (i == -1) {
456 /* Broken pipe - stop writing */
457 if (errno == EPIPE) {
458 /* printf("Writing: Broken pipe.\n"); */
459 writing = FALSE;
460 continue;
461 }
462 if (errno == EINTR) continue;
463 goto errno_error_exit;
464 }
465 bp += i;
466 if (status_bar_progress(bar, i))
467 goto error_exit;
468 }
469 }
470
471 /* Check for any remaining data sent to standard error */
472 while (pipe_dialog_error_check(pipehandle)) { }
473
474 /* Finish */
475 g_free(inbuf);
476 g_free(outbuf);
477 chunk_close(ch);
478 pipe_dialog_close(pipehandle);
479 status_bar_end_progress(bar);
480
481 if (do_read && !read_bytes) {
482 user_error(_("Command failed without returning any data"));
483 tempfile_abort(ct);
484 return NULL;
485 }
486
487 if (sent_all) *sent_all = !writing;
488
489 return do_read ? tempfile_finished(ct) : NULL;
490
491 errno_error_exit:
492 c = g_strdup_printf(_("Error: %s"),strerror(errno));
493 user_error(c);
494 g_free(c);
495 error_exit:
496 g_free(inbuf);
497 g_free(outbuf);
498 chunk_close(ch);
499 if (do_read) tempfile_abort(ct);
500 pipe_dialog_close(pipehandle);
501 status_bar_end_progress(bar);
502 return NULL;
503 }
504
pipe_dialog_pipe_chunk(Chunk * chunk,gchar * command,gboolean sendwav,int dither_mode,StatusBar * bar,off_t * clipcount)505 Chunk *pipe_dialog_pipe_chunk(Chunk *chunk, gchar *command, gboolean sendwav,
506 int dither_mode, StatusBar *bar,
507 off_t *clipcount)
508 {
509 return pipe_dialog_pipe_chunk_main(chunk,command,sendwav,TRUE,
510 bar,NULL,dither_mode,clipcount);
511 }
512
pipe_dialog_send_chunk(Chunk * chunk,gchar * command,gboolean sendwav,int dither_mode,StatusBar * bar,off_t * clipcount)513 gboolean pipe_dialog_send_chunk(Chunk *chunk, gchar *command, gboolean sendwav,
514 int dither_mode, StatusBar *bar,
515 off_t *clipcount)
516 {
517 gboolean sent_all;
518 pipe_dialog_pipe_chunk_main(chunk,command,sendwav,FALSE,
519 bar,&sent_all,dither_mode,clipcount);
520 return !sent_all;
521 }
522