7856fb68d2e9b8d88657709bc2329ad8152f2145
[claws.git] / src / gtkspell.c
1 /* gtkspell - a spell-checking addon for GtkText
2  * Copyright (c) 2000 Evan Martin.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
17  */
18 /*
19     Stuphead: (C) 2000,2001 Grigroy Bakunov, Sergey Pinaev
20  */
21
22 /*
23  * Adapted for Sylpheed (Claws) (c) 2001 by Hiroyuki Yamamoto & 
24  * The Sylpheed Claws Team.
25  */
26
27 #if defined(HAVE_CONFIG_H)
28 #include "config.h"
29 #endif
30
31 #include "intl.h"
32
33 #include <gtk/gtk.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <unistd.h>
37 #include <stdio.h>
38 #include <signal.h>
39 #include <ctype.h>
40 #include <string.h>
41 #include <stdlib.h>
42 #include <errno.h>
43 #include <stdio.h>
44 #include <sys/time.h>
45 #include <fcntl.h>
46 #include <time.h>
47 #include <prefs_common.h>
48 #include <utils.h>
49
50 #include "gtkxtext.h"
51
52 /* size of the text buffer used in various word-processing routines. */
53 #define BUFSIZE 1024
54
55 /* number of suggestions to display on each menu. */
56 #define MENUCOUNT 15
57
58 /* because we keep only one copy of the spell program running,
59  * all ispell-related variables can be static.
60  */
61 volatile pid_t spell_pid = -1;
62 static int sp_fd_write[2], sp_fd_read[2];
63 static int signal_set_up = 0;
64
65 static GdkColor highlight = { 0, 255 * 256, 0, 0 };
66
67 static void entry_insert_cb(GtkXText *gtktext,
68                             gchar *newtext, guint len, guint *ppos, gpointer d);
69 static void set_up_signal();
70
71 int gtkspell_running() 
72 {
73     return (spell_pid > 0);
74 }
75
76 /* functions to interface with pipe */
77 static void writetext(unsigned char *text_ccc) 
78 {
79     write(sp_fd_write[1], text_ccc, strlen(text_ccc));
80 }
81
82 static int readpipe(unsigned char *buf, int bufsize) 
83 {
84     int len;
85     len = read(sp_fd_read[0], buf, bufsize - 1);
86     if (len < 0) {
87         debug_print(_("*** readpipe: read: %s\n"), strerror(errno));
88         return -1;
89     } else if (len == 0) {
90         debug_print(_("*** readpipe: pipe closed.\n"));
91         return -1;
92     } else if (len == bufsize - 1) {
93         debug_print(_("*** readpipe: buffer overflowed?\n"));
94     }
95
96     buf[len] = 0;
97     return len;
98 }
99
100 static int readline(unsigned char *buf) 
101 {
102     return readpipe(buf, BUFSIZE);
103 }
104
105 static int readresponse(unsigned char *buf) 
106 {
107     int len;
108     len = readpipe(buf, BUFSIZE);
109
110     /* all ispell responses of any reasonable length should end in \n\n.
111      * depending on the speed of the spell checker, this may require more
112      * reading. */
113     if (len >= 2 && (buf[len - 1] != '\n' || buf[len - 2] != '\n')) {
114         len += readpipe(buf + len, BUFSIZE - len);
115     }
116
117     /* now we can remove all of the the trailing newlines. */
118     while (len > 0 && buf[len - 1] == '\n')
119         buf[--len] = 0;
120
121     return len;
122 }
123
124 void gtkspell_stop() 
125 {
126     if (gtkspell_running()) {
127         kill(spell_pid, SIGTERM);
128         debug_print(_("*** Kill pid[%i] returned: %s\n"), spell_pid, strerror(errno));
129         while (spell_pid != -1);
130     }
131 }
132
133 static int poller(int buffer) 
134 {
135     int len;
136     fd_set rfds;
137     struct timeval tv;
138     int retval;
139
140     FD_ZERO(&rfds);
141     FD_SET(buffer, &rfds);
142     memset(&tv, 0, sizeof(tv));
143     tv.tv_sec = 2;
144     tv.tv_usec = 0;
145     return select(buffer + 1, &rfds, NULL, NULL, &tv);
146 }
147
148 int gtkspell_start(unsigned char *path, char * args[]) 
149 {
150     int fd_error[2];
151     FILE *sav_stdin, *sav_stdout, *sav_stderr;
152     char buf[BUFSIZE];
153     int retncode;
154
155     if (gtkspell_running()) {
156         debug_print(_("*** gtkspell_start called while already running.\n"));
157         gtkspell_stop();
158     }
159
160     if (!signal_set_up) {
161         set_up_signal();
162         signal_set_up = 1;
163     }
164
165     pipe(sp_fd_write);
166     pipe(sp_fd_read);
167     pipe(fd_error);
168
169     spell_pid = fork();
170     if (spell_pid < 0) {
171         debug_print(_("*** fork: %s\n"), strerror(errno));
172         return -1;
173     } else if (spell_pid == 0) {
174         sav_stdin = fdopen(dup(fileno(stdin)), "r");
175         sav_stdout = fdopen(dup(fileno(stdout)), "w");
176         sav_stderr = fdopen(dup(fileno(stderr)), "w");
177         dup2(sp_fd_write[0], 0);
178         dup2(sp_fd_read[1], 1);
179         dup2(fd_error[1], 2);
180         close(sp_fd_read[0]);
181         close(fd_error[0]);
182         close(sp_fd_write[1]);
183
184         if (path == NULL) {
185             if (execvp(args[0], args) < 0)
186                 //DONT call debug_print here, because stdout is closed at this moment
187                 fprintf(sav_stderr, _("*** execvp('%s'): %s\n"), args[0], strerror(errno));
188         } else {
189             if (execv(path, args) < 0)
190                 //DONT call debug_print here, because stdout is closed at this moment
191                 fprintf(sav_stderr, _("*** execv('%s'): %s\n"), path, strerror(errno));
192         }
193         /* if we get here, we failed.
194          * send some text on the pipe to indicate status.
195          */
196         write(sp_fd_read[1], "!", 1);
197
198         _exit(0);
199     } else {
200         retncode = poller(sp_fd_read[1]);
201         if (retncode < 0) {
202             debug_print(_("*** Spell comand failed: %s.\n"), strerror(errno));
203             gtkspell_stop();
204             return -1;
205         }
206         readline(buf);
207         /* ispell should print something like this:
208          * @(#) International Ispell Version 3.1.20 10/10/95
209          * if it doesn't, it's an error. */
210         if (buf[0] != '@') {
211             debug_print(_("*** ispell didnt print '@'\n"));
212             gtkspell_stop();
213             return -1;
214         }
215     }
216
217     /* put ispell into terse mode.
218      * this makes it not respond on correctly spelled words. */
219     sprintf(buf, "!\n");
220     writetext(buf);
221     return 0;
222 }
223
224 static GList* misspelled_suggest(unsigned char *word) 
225 {
226     unsigned char buf[BUFSIZE];
227     unsigned char *newword;
228     GList *l = NULL;
229     int count;
230     sprintf(buf, "^%s\n", word);  /* guard against ispell control chars */
231     writetext(buf);
232     readresponse(buf);
233     switch (buf[0]) { /* first char is ispell command. */
234     case 0:  /* no response: word is ok. */
235         return NULL;
236     case 10:  /* just enter word is ok.  */
237         return NULL;
238     case '&':  /* misspelled, with suggestions */
239         /* & <orig> <count> <ofs>: <miss>, <miss>, <guess>, ... */
240         strtok(buf, " ");  /* & */
241         newword = strtok(NULL, " ");  /* orig */
242         l = g_list_append(l, g_strdup(newword));
243         newword = strtok(NULL, " ");  /* count */
244         count = atoi(newword);
245         strtok(NULL, " ");  /* ofs: */
246
247         while ((newword = strtok(NULL, ",")) != NULL) {
248             int len = strlen(newword);
249             if (newword[len - 1] == ' ' || newword[len - 1] == '\n')
250                 newword[len - 1] = 0;
251             if (count == 0) {
252                 g_list_append(l, NULL);  /* signal the "suggestions" */
253             }
254             /* add it to the list, skipping the initial space. */
255             l = g_list_append(l,
256                               g_strdup(newword[0] == ' ' ? newword + 1 : newword));
257
258             count--;
259         }
260         return l;
261     case '?':  /* ispell is guessing. */
262     case '#':  /* misspelled, no suggestions */
263         /* # <orig> <ofs> */
264         strtok(buf, " ");  /* & */
265         newword = strtok(NULL, " ");  /* orig */
266         l = g_list_append(l, g_strdup(newword));
267         return l;
268     default:
269         debug_print(_("*** Unsupported spell command '%c'.\n"), buf[0]);
270     }
271     return NULL;
272 }
273
274 static int misspelled_test(unsigned char *word) 
275 {
276     unsigned char buf[BUFSIZE];
277     sprintf(buf, "^%s\n", word);  /* guard against ispell control chars */
278     writetext(buf);
279     readresponse(buf);
280
281     if (buf[0] == 0) {
282         return 0;
283     } else if (buf[0] == '&' || buf[0] == '#' || buf[0] == '?') {
284         return 1;
285     }
286
287     debug_print(_("*** Unsupported spell command '%c'.\n"), buf[0]);
288
289     return -1;
290 }
291
292 static gboolean iswordsep(unsigned char c) 
293 {
294     return !isalpha(c) && c != '\'';
295 }
296
297 static guchar get_text_index_whar(GtkXText *gtktext, int pos) 
298 {
299     guchar a;
300     gchar *text;
301     text = gtk_editable_get_chars(GTK_EDITABLE(gtktext), pos, pos + 1);
302     if (text == NULL) return 0;
303     a = (guchar) * text;
304     g_free(text);
305     return a;
306 }
307
308 static gboolean get_word_from_pos(GtkXText *gtktext, int pos, unsigned char* buf,
309                                   int *pstart, int *pend) 
310 {
311     gint start, end;
312     if (iswordsep(get_text_index_whar(gtktext, pos))) return FALSE;
313
314     for (start = pos; start >= 0; --start) {
315         if (iswordsep(get_text_index_whar(gtktext, start))) break;
316     }
317     start++;
318
319     for (end = pos; end < gtk_xtext_get_length(gtktext); end++) {
320         if (iswordsep(get_text_index_whar(gtktext, end))) break;
321     }
322
323     if (buf) {
324         for (pos = start; pos < end; pos++) buf[pos - start] = get_text_index_whar(gtktext, pos);
325         buf[pos - start] = 0;
326     }
327
328     if (pstart) *pstart = start;
329     if (pend) *pend = end;
330
331     return TRUE;
332 }
333
334 static gboolean get_curword(GtkXText *gtktext, unsigned char* buf,
335                             int *pstart, int *pend) 
336 {
337     int pos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
338     return get_word_from_pos(gtktext, pos, buf, pstart, pend);
339 }
340
341 static void change_color(GtkXText *gtktext, int start, int end, GdkColor *color) 
342 {
343     char *newtext;
344     if (start >= end) {
345         return ;
346     };
347     gtk_xtext_freeze(gtktext);
348     newtext = gtk_editable_get_chars(GTK_EDITABLE(gtktext), start, end);
349 //    if (prefs_common.auto_makeispell) {
350         gtk_signal_handler_block_by_func(GTK_OBJECT(gtktext),
351                                          GTK_SIGNAL_FUNC(entry_insert_cb), NULL);
352 //    }
353     gtk_xtext_set_point(gtktext, start);
354     gtk_xtext_forward_delete(gtktext, end - start);
355
356     if (newtext && end - start > 0)
357         gtk_xtext_insert(gtktext, NULL, color, NULL, newtext, end - start);
358   //  if (prefs_common.auto_makeispell) {
359         gtk_signal_handler_unblock_by_func(GTK_OBJECT(gtktext),
360                                            GTK_SIGNAL_FUNC(entry_insert_cb), NULL);
361    // }
362     gtk_xtext_thaw(gtktext);
363 }
364
365 static gboolean check_at(GtkXText *gtktext, int from_pos) 
366 {
367     int start, end;
368     unsigned char buf[BUFSIZE];
369     if (from_pos < 0) return FALSE;
370     if (!get_word_from_pos(gtktext, from_pos, buf, &start, &end)) {
371         return FALSE;
372     };
373     if (misspelled_test(buf)) {
374         if (highlight.pixel == 0) {
375             /* add an entry for the highlight in the color map. */
376             GdkColormap *gc = gtk_widget_get_colormap(GTK_WIDGET(gtktext));
377             gdk_colormap_alloc_color(gc, &highlight, FALSE, TRUE);
378             ;
379         }
380         change_color(gtktext, start, end, &highlight);
381         return TRUE;
382     } else {
383         change_color(gtktext, start, end,
384                      &(GTK_WIDGET(gtktext)->style->fg[0]));
385         return FALSE;
386     }
387 }
388
389 void gtkspell_check_all(GtkXText *gtktext) 
390 {
391     guint origpos;
392     guint pos = 0;
393     guint len;
394     float adj_value;
395
396     if (!gtkspell_running()) return ;
397
398     len = gtk_xtext_get_length(gtktext);
399
400     adj_value = gtktext->vadj->value;
401     gtk_xtext_freeze(gtktext);
402     origpos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
403     while (pos < len) {
404         while (pos < len && iswordsep(get_text_index_whar(gtktext, pos)))
405             pos++;
406         while (pos < len && !iswordsep(get_text_index_whar(gtktext, pos)))
407             pos++;
408         if (pos > 0)
409             check_at(gtktext, pos - 1);
410     }
411     gtk_xtext_thaw(gtktext);
412     gtk_editable_set_position(GTK_EDITABLE(gtktext), origpos);
413     gtk_editable_select_region(GTK_EDITABLE(gtktext), origpos, origpos);
414
415 }
416
417 static void entry_insert_cb(GtkXText *gtktext,
418                             gchar *newtext, guint len, guint *ppos, gpointer d) 
419 {
420     int origpos;
421     if (!gtkspell_running()) return ;
422
423     gtk_signal_handler_block_by_func(GTK_OBJECT(gtktext),
424                                      GTK_SIGNAL_FUNC(entry_insert_cb),
425                                      NULL);
426     gtk_xtext_insert(GTK_XTEXT(gtktext), NULL,
427                     &(GTK_WIDGET(gtktext)->style->fg[0]), NULL, newtext, len);
428
429     gtk_signal_handler_unblock_by_func(GTK_OBJECT(gtktext),
430                                        GTK_SIGNAL_FUNC(entry_insert_cb),
431                                        NULL);
432     gtk_signal_emit_stop_by_name(GTK_OBJECT(gtktext), "insert-text");
433     *ppos += len;
434     origpos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
435
436     if (iswordsep(newtext[0])) {
437         /* did we just end a word? */
438         if (*ppos >= 2) check_at(gtktext, *ppos - 2);
439
440         /* did we just split a word? */
441         if (*ppos < gtk_xtext_get_length(gtktext))
442             check_at(gtktext, *ppos + 1);
443     } else {
444         /* check as they type, *except* if they're typing at the end (the most
445          * common case.
446          */
447         if (*ppos < gtk_xtext_get_length(gtktext) &&
448                 !iswordsep(get_text_index_whar(gtktext, *ppos)))
449             check_at(gtktext, *ppos - 1);
450     }
451
452     gtk_editable_set_position(GTK_EDITABLE(gtktext), origpos);
453 }
454
455 static void entry_delete_cb(GtkXText *gtktext,
456                             gint start, gint end, gpointer d) 
457 {
458     int origpos;
459     if (!gtkspell_running()) return ;
460
461     origpos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
462     check_at(gtktext, start - 1);
463     gtk_editable_set_position(GTK_EDITABLE(gtktext), origpos);
464     gtk_editable_select_region(GTK_EDITABLE(gtktext), origpos, origpos);
465     /* this is to *UNDO* the selection, in case they were holding shift
466      * while hitting backspace. */
467 }
468
469 static void replace_word(GtkWidget *w, gpointer d) 
470 {
471     int start, end;
472     unsigned char *newword;
473     unsigned char buf[BUFSIZE];
474     gtk_xtext_freeze(GTK_XTEXT(d));
475
476     gtk_label_get(GTK_LABEL(GTK_BIN(w)->child), (gchar**) &newword);
477     get_curword(GTK_XTEXT(d), buf, &start, &end);
478
479     gtk_xtext_set_point(GTK_XTEXT(d), end);
480     gtk_xtext_backward_delete(GTK_XTEXT(d), end - start);
481     gtk_xtext_insert(GTK_XTEXT(d), NULL, NULL, NULL, newword, strlen(newword));
482
483     gtk_xtext_thaw(GTK_XTEXT(d));
484 }
485
486 static GtkMenu *make_menu(GList *l, GtkXText *gtktext) 
487 {
488     GtkWidget *menu, *item;
489     unsigned char *caption;
490     menu = gtk_menu_new(); 
491     
492         caption = g_strdup_printf(_("Not in dictionary: %s"), (unsigned char*)l->data);
493         item = gtk_menu_item_new_with_label(caption);
494         /* I'd like to make it so this item is never selectable, like
495          * the menu titles in the GNOME panel... unfortunately, the GNOME
496          * panel creates their own custom widget to do this! */
497         gtk_widget_show(item);
498         gtk_menu_append(GTK_MENU(menu), item);
499
500         item = gtk_menu_item_new();
501         gtk_widget_show(item);
502         gtk_menu_append(GTK_MENU(menu), item);
503
504         l = l->next;
505         if (l == NULL) {
506             item = gtk_menu_item_new_with_label(_("(no suggestions)"));
507             gtk_widget_show(item);
508             gtk_menu_append(GTK_MENU(menu), item);
509         } else {
510             GtkWidget *curmenu = menu;
511             int count = 0;
512             do {
513                 if (l->data == NULL && l->next != NULL) {
514                     count = 0;
515                     curmenu = gtk_menu_new();
516                     item = gtk_menu_item_new_with_label(_("Others..."));
517                     gtk_widget_show(item);
518                     gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), curmenu);
519                     gtk_menu_append(GTK_MENU(curmenu), item);
520                     l = l->next;
521                 } else if (count > MENUCOUNT) {
522                     count -= MENUCOUNT;
523                     item = gtk_menu_item_new_with_label(_("More..."));
524                     gtk_widget_show(item);
525                     gtk_menu_append(GTK_MENU(curmenu), item);
526                     curmenu = gtk_menu_new();
527                     gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), curmenu);
528                 }
529                 item = gtk_menu_item_new_with_label((unsigned char*)l->data);
530                 gtk_signal_connect(GTK_OBJECT(item), "activate",
531                                    GTK_SIGNAL_FUNC(replace_word), gtktext);
532                 gtk_widget_show(item);
533                 gtk_menu_append(GTK_MENU(curmenu), item);
534                 count++;
535             } while ((l = l->next) != NULL);
536         }
537     return GTK_MENU(menu);
538 }
539
540 static void popup_menu(GtkXText *gtktext, GdkEventButton *eb) 
541 {
542     unsigned char buf[BUFSIZE];
543     GList *list, *l;
544     if (!get_curword(gtktext, buf, NULL, NULL)) return ;
545     if (buf == NULL) return ;
546     list = misspelled_suggest(buf);
547     if (list != NULL) {
548         gtk_menu_popup(make_menu(list, gtktext), NULL, NULL, NULL, NULL,
549                        eb->button, eb->time);
550         for (l = list; l != NULL; l = l->next)
551             g_free(l->data);
552         g_list_free(list);
553     }
554 }
555
556 /* ok, this is pretty wacky:
557  * we need to let the right-mouse-click go through, so it moves the cursor,
558  * but we *can't* let it go through, because GtkText interprets rightclicks as
559  * weird selection modifiers.
560  *
561  * so what do we do?  forge rightclicks as leftclicks, then popup the menu.
562  * HACK HACK HACK.
563  */
564 static gint button_press_intercept_cb(GtkXText *gtktext, GdkEvent *e, gpointer d) 
565 {
566     GdkEventButton *eb;
567     gboolean retval;
568
569     if (!gtkspell_running()) return FALSE;
570
571     if (e->type != GDK_BUTTON_PRESS) return FALSE;
572     eb = (GdkEventButton*) e;
573
574     if (eb->button != 3) return FALSE;
575
576     /* forge the leftclick */
577     eb->button = 1;
578
579     gtk_signal_handler_block_by_func(GTK_OBJECT(gtktext),
580                                      GTK_SIGNAL_FUNC(button_press_intercept_cb), d);
581     gtk_signal_emit_by_name(GTK_OBJECT(gtktext), "button-press-event",
582                             e, &retval);
583     gtk_signal_handler_unblock_by_func(GTK_OBJECT(gtktext),
584                                        GTK_SIGNAL_FUNC(button_press_intercept_cb), d);
585     gtk_signal_emit_stop_by_name(GTK_OBJECT(gtktext), "button-press-event");
586
587     /* now do the menu wackiness */
588     popup_menu(gtktext, eb);
589     return TRUE;
590 }
591
592 void gtkspell_uncheck_all(GtkXText *gtktext) 
593 {
594     int origpos;
595     unsigned char *text;
596     float adj_value;
597     adj_value = gtktext->vadj->value;
598     gtk_xtext_freeze(gtktext);
599     origpos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
600     text = gtk_editable_get_chars(GTK_EDITABLE(gtktext), 0, -1);
601     gtk_xtext_set_point(gtktext, 0);
602     gtk_xtext_forward_delete(gtktext, gtk_xtext_get_length(gtktext));
603     gtk_xtext_insert(gtktext, NULL, NULL, NULL, text, strlen(text));
604     gtk_xtext_thaw(gtktext);
605
606     gtk_editable_set_position(GTK_EDITABLE(gtktext), origpos);
607     gtk_adjustment_set_value(gtktext->vadj, adj_value);
608 }
609
610 void gtkspell_attach(GtkXText *gtktext) 
611 {
612 //    if (prefs_common.auto_makeispell) {
613         gtk_signal_connect(GTK_OBJECT(gtktext), "insert-text",
614                            GTK_SIGNAL_FUNC(entry_insert_cb), NULL);
615         gtk_signal_connect_after(GTK_OBJECT(gtktext), "delete-text",
616                                  GTK_SIGNAL_FUNC(entry_delete_cb), NULL);
617 //    };
618     gtk_signal_connect(GTK_OBJECT(gtktext), "button-press-event",
619                        GTK_SIGNAL_FUNC(button_press_intercept_cb), NULL);
620
621 }
622
623 void gtkspell_detach(GtkXText *gtktext) 
624 {
625 //    if (prefs_common.auto_makeispell) {
626         gtk_signal_disconnect_by_func(GTK_OBJECT(gtktext),
627                                       GTK_SIGNAL_FUNC(entry_insert_cb), NULL);
628         gtk_signal_disconnect_by_func(GTK_OBJECT(gtktext),
629                                       GTK_SIGNAL_FUNC(entry_delete_cb), NULL);
630 //    };
631     gtk_signal_disconnect_by_func(GTK_OBJECT(gtktext),
632                                   GTK_SIGNAL_FUNC(button_press_intercept_cb), NULL);
633
634     gtkspell_uncheck_all(gtktext);
635 }
636
637 static void sigchld(int param) 
638 {
639     int retstat, retval;
640
641     debug_print(_("*** SIGCHLD called. (ispell pid: %i)\n"), spell_pid);
642     while ((retval = waitpid( -1, &retstat, WNOHANG)) > 0) {
643         if (retval == spell_pid) {
644             debug_print(_("*** SIGCHLD called for ispell. (pid: %i)\n"), spell_pid);
645             spell_pid = -1;
646         }
647     }
648 }
649
650 static void set_up_signal() 
651 {
652     /* RETSIGTYPE is found in autoconf's config.h */
653 #ifdef RETSIGTYPE
654     typedef RETSIGTYPE (*sighandler)(int);
655     signal(SIGCHLD, (sighandler)sigchld);
656 #else
657     /* otherwise, just hope it works */
658     signal(SIGCHLD, sigchld);
659 #endif
660 }