Adjust menu-text clipping to square size
[xboard.git] / usystem.c
1 /*
2  * usystem.c -- X-free, but Unix-like code for XBoard front end
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Free
9  * Software Foundation, Inc.
10  *
11  * The following terms apply to Digital Equipment Corporation's copyright
12  * interest in XBoard:
13  * ------------------------------------------------------------------------
14  * All Rights Reserved
15  *
16  * Permission to use, copy, modify, and distribute this software and its
17  * documentation for any purpose and without fee is hereby granted,
18  * provided that the above copyright notice appear in all copies and that
19  * both that copyright notice and this permission notice appear in
20  * supporting documentation, and that the name of Digital not be
21  * used in advertising or publicity pertaining to distribution of the
22  * software without specific, written prior permission.
23  *
24  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
25  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
26  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
27  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
28  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
29  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
30  * SOFTWARE.
31  * ------------------------------------------------------------------------
32  *
33  * The following terms apply to the enhanced version of XBoard
34  * distributed by the Free Software Foundation:
35  * ------------------------------------------------------------------------
36  *
37  * GNU XBoard is free software: you can redistribute it and/or modify
38  * it under the terms of the GNU General Public License as published by
39  * the Free Software Foundation, either version 3 of the License, or (at
40  * your option) any later version.
41  *
42  * GNU XBoard is distributed in the hope that it will be useful, but
43  * WITHOUT ANY WARRANTY; without even the implied warranty of
44  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
45  * General Public License for more details.
46  *
47  * You should have received a copy of the GNU General Public License
48  * along with this program. If not, see http://www.gnu.org/licenses/.  *
49  *
50  *------------------------------------------------------------------------
51  ** See the file ChangeLog for a revision history.  */
52
53 #include "config.h"
54
55 #include <stdio.h>
56 #include <ctype.h>
57 #include <signal.h>
58 #include <errno.h>
59 #include <sys/types.h>
60 #include <sys/stat.h>
61 #include <pwd.h>
62 #include <math.h>
63
64 #if !OMIT_SOCKETS
65 # if HAVE_SYS_SOCKET_H
66 #  include <sys/socket.h>
67 #  include <netinet/in.h>
68 #  include <netdb.h>
69 # else /* not HAVE_SYS_SOCKET_H */
70 #  if HAVE_LAN_SOCKET_H
71 #   include <lan/socket.h>
72 #   include <lan/in.h>
73 #   include <lan/netdb.h>
74 #  else /* not HAVE_LAN_SOCKET_H */
75 #   define OMIT_SOCKETS 1
76 #  endif /* not HAVE_LAN_SOCKET_H */
77 # endif /* not HAVE_SYS_SOCKET_H */
78 #endif /* !OMIT_SOCKETS */
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 #else /* not STDC_HEADERS */
84 extern char *getenv();
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if HAVE_SYS_SYSTEMINFO_H
101 # include <sys/systeminfo.h>
102 #endif /* HAVE_SYS_SYSTEMINFO_H */
103
104 #if TIME_WITH_SYS_TIME
105 # include <sys/time.h>
106 # include <time.h>
107 #else
108 # if HAVE_SYS_TIME_H
109 #  include <sys/time.h>
110 # else
111 #  include <time.h>
112 # endif
113 #endif
114
115 #if HAVE_UNISTD_H
116 # include <unistd.h>
117 #endif
118
119 #if HAVE_SYS_WAIT_H
120 # include <sys/wait.h>
121 #endif
122
123 #if HAVE_DIRENT_H
124 # include <dirent.h>
125 # define NAMLEN(dirent) strlen((dirent)->d_name)
126 # define HAVE_DIR_STRUCT
127 #else
128 # define dirent direct
129 # define NAMLEN(dirent) (dirent)->d_namlen
130 # if HAVE_SYS_NDIR_H
131 #  include <sys/ndir.h>
132 #  define HAVE_DIR_STRUCT
133 # endif
134 # if HAVE_SYS_DIR_H
135 #  include <sys/dir.h>
136 #  define HAVE_DIR_STRUCT
137 # endif
138 # if HAVE_NDIR_H
139 #  include <ndir.h>
140 #  define HAVE_DIR_STRUCT
141 # endif
142 #endif
143
144 #if ENABLE_NLS
145 #include <locale.h>
146 #endif
147
148 // [HGM] bitmaps: put before incuding the bitmaps / pixmaps, to know how many piece types there are.
149 #include "common.h"
150
151 #include "frontend.h"
152 #include "backend.h"
153 #include "childio.h"
154 #include "menus.h"
155 #include "usystem.h"
156 #include "gettext.h"
157
158
159 #ifdef __EMX__
160 #ifndef HAVE_USLEEP
161 #define HAVE_USLEEP
162 #endif
163 #define usleep(t)   _sleep2(((t)+500)/1000)
164 #endif
165
166 #ifdef ENABLE_NLS
167 # define  _(s) gettext (s)
168 # define N_(s) gettext_noop (s)
169 #else
170 # define  _(s) (s)
171 # define N_(s)  s
172 #endif
173
174 static int get_term_width P(());
175
176 static char *cnames[9] = { "black", "red", "green", "yellow", "blue",
177                              "magenta", "cyan", "white" };
178 TextColors textColors[(int)NColorClasses];
179
180 /* String is: "fg, bg, attr". Which is 0, 1, 2 */
181 static int
182 parse_color (char *str, int which)
183 {
184     char *p, buf[100], *d;
185     int i;
186
187     if (strlen(str) > 99)       /* watch bounds on buf */
188       return -1;
189
190     p = str;
191     d = buf;
192     for (i=0; i<which; ++i) {
193         p = strchr(p, ',');
194         if (!p)
195           return -1;
196         ++p;
197     }
198
199     /* Could be looking at something like:
200        black, , 1
201        .. in which case we want to stop on a comma also */
202     while (*p && *p != ',' && !isalpha(*p) && !isdigit(*p))
203       ++p;
204
205     if (*p == ',') {
206         return -1;              /* Use default for empty field */
207     }
208
209     if (which == 2 || isdigit(*p))
210       return atoi(p);
211
212     while (*p && isalpha(*p))
213       *(d++) = *(p++);
214
215     *d = 0;
216
217     for (i=0; i<8; ++i) {
218         if (!StrCaseCmp(buf, cnames[i]))
219           return which? (i+40) : (i+30);
220     }
221     if (!StrCaseCmp(buf, "default")) return -1;
222
223     fprintf(stderr, _("%s: unrecognized color %s\n"), programName, buf);
224     return -2;
225 }
226
227 static int
228 parse_cpair (ColorClass cc, char *str)
229 {
230     if ((textColors[(int)cc].fg=parse_color(str, 0)) == -2) {
231         fprintf(stderr, _("%s: can't parse foreground color in '%s'\n"),
232                 programName, str);
233         return -1;
234     }
235
236     /* bg and attr are optional */
237     textColors[(int)cc].bg = parse_color(str, 1);
238     if ((textColors[(int)cc].attr = parse_color(str, 2)) < 0) {
239         textColors[(int)cc].attr = 0;
240     }
241     return 0;
242 }
243
244 void
245 ParseIcsTextColors ()
246 {   // [HGM] tken out of main(), so it can be called from ICS-Options dialog
247     if (parse_cpair(ColorShout, appData.colorShout) < 0 ||
248         parse_cpair(ColorSShout, appData.colorSShout) < 0 ||
249         parse_cpair(ColorChannel1, appData.colorChannel1) < 0  ||
250         parse_cpair(ColorChannel, appData.colorChannel) < 0  ||
251         parse_cpair(ColorKibitz, appData.colorKibitz) < 0 ||
252         parse_cpair(ColorTell, appData.colorTell) < 0 ||
253         parse_cpair(ColorChallenge, appData.colorChallenge) < 0  ||
254         parse_cpair(ColorRequest, appData.colorRequest) < 0  ||
255         parse_cpair(ColorSeek, appData.colorSeek) < 0  ||
256         parse_cpair(ColorNormal, appData.colorNormal) < 0)
257       {
258           if (appData.colorize) {
259               fprintf(stderr,
260                       _("%s: can't parse color names; disabling colorization\n"),
261                       programName);
262           }
263           appData.colorize = FALSE;
264       }
265     textColors[ColorNone].fg = textColors[ColorNone].bg = -1;
266     textColors[ColorNone].attr = 0;
267     SetTextColor(cnames, textColors[ColorNormal].fg - 30, textColors[ColorNormal].bg - 40, -2); // kludge to announce background color to front-end 
268 }
269
270 char *oldICSInteractionTitle;
271
272 void
273 ShutDownFrontEnd ()
274 {
275     if (appData.icsActive && oldICSInteractionTitle != NULL) {
276         DisplayIcsInteractionTitle(oldICSInteractionTitle);
277     }
278     if (saveSettingsOnExit) SaveSettings(settingsFileName);
279     unlink(gameCopyFilename);
280     unlink(gamePasteFilename);
281     EchoOn();
282 }
283
284 void
285 RunCommand (char *buf)
286 {
287     system(buf);
288 }
289
290 void
291 Colorize (ColorClass cc, int continuation)
292 {
293     char buf[MSG_SIZ];
294     int count, outCount, error;
295
296     SetTextColor(cnames, textColors[(int)cc].fg - 30, textColors[(int)cc].bg - 40, textColors[(int)cc].attr); // for GTK widget
297
298     if (textColors[(int)cc].bg > 0) {
299         if (textColors[(int)cc].fg > 0) {
300           snprintf(buf, MSG_SIZ, "\033[0;%d;%d;%dm", textColors[(int)cc].attr,
301                    textColors[(int)cc].fg, textColors[(int)cc].bg);
302         } else {
303           snprintf(buf, MSG_SIZ, "\033[0;%d;%dm", textColors[(int)cc].attr,
304                    textColors[(int)cc].bg);
305         }
306     } else {
307         if (textColors[(int)cc].fg > 0) {
308           snprintf(buf, MSG_SIZ, "\033[0;%d;%dm", textColors[(int)cc].attr,
309                     textColors[(int)cc].fg);
310         } else {
311           snprintf(buf, MSG_SIZ, "\033[0;%dm", textColors[(int)cc].attr);
312         }
313     }
314     count = strlen(buf);
315     outCount = OutputToProcess(NoProc, buf, count, &error);
316     if (outCount < count) {
317         DisplayFatalError(_("Error writing to display"), error, 1);
318     }
319
320     if (continuation) return;
321     PlaySoundForColor(cc);
322 }
323
324 char *
325 UserName ()
326 {
327     return getpwuid(getuid())->pw_name;
328 }
329
330 char *
331 ExpandPathName (char *path)
332 {
333     static char static_buf[4*MSG_SIZ];
334     char *d, *s, buf[4*MSG_SIZ];
335     struct passwd *pwd;
336
337     s = path;
338     d = static_buf;
339
340     while (*s && isspace(*s))
341       ++s;
342
343     if (!*s) {
344         *d = 0;
345         return static_buf;
346     }
347
348     if (*s == '~') {
349         if(s[1] == '~') { // use ~~ for XBoard's private data directory
350           snprintf(d, 4*MSG_SIZ, "%s%s", dataDir, s+2);
351         } else
352         if (*(s+1) == '/') {
353           safeStrCpy(d, getpwuid(getuid())->pw_dir, 4*MSG_SIZ );
354           strcat(d, s+1);
355         }
356         else {
357           safeStrCpy(buf, s+1, sizeof(buf)/sizeof(buf[0]) );
358           { char *p; if(p = strchr(buf, '/')) *p = 0; }
359           pwd = getpwnam(buf);
360           if (!pwd)
361             {
362               fprintf(stderr, _("ERROR: Unknown user %s (in path %s)\n"),
363                       buf, path);
364               return NULL;
365             }
366           safeStrCpy(d, pwd->pw_dir, 4*MSG_SIZ );
367           strcat(d, strchr(s+1, '/'));
368         }
369     }
370     else
371       safeStrCpy(d, s, 4*MSG_SIZ );
372
373     return static_buf;
374 }
375
376 int
377 MySearchPath (char *installDir, char *name, char *fullname)
378 { // just append installDir and name. Perhaps ExpandPath should be used here?
379   name = ExpandPathName(name);
380   if(name && name[0] == '/')
381     safeStrCpy(fullname, name, MSG_SIZ );
382   else {
383     sprintf(fullname, "%s%c%s", installDir, '/', name);
384   }
385   return 1;
386 }
387
388 int
389 MyGetFullPathName (char *name, char *fullname)
390 { // should use ExpandPath?
391   name = ExpandPathName(name);
392   safeStrCpy(fullname, name, MSG_SIZ );
393   return 1;
394 }
395
396 char *
397 HostName ()
398 {
399     static char host_name[MSG_SIZ];
400
401 #if HAVE_GETHOSTNAME
402     gethostname(host_name, MSG_SIZ);
403     return host_name;
404 #else  /* not HAVE_GETHOSTNAME */
405 # if HAVE_SYSINFO && HAVE_SYS_SYSTEMINFO_H
406     sysinfo(SI_HOSTNAME, host_name, MSG_SIZ);
407     return host_name;
408 # else /* not (HAVE_SYSINFO && HAVE_SYS_SYSTEMINFO_H) */
409     return "localhost";
410 # endif/* not (HAVE_SYSINFO && HAVE_SYS_SYSTEMINFO_H) */
411 #endif /* not HAVE_GETHOSTNAME */
412 }
413
414
415 int
416 StartChildProcess (char *cmdLine, char *dir, ProcRef *pr)
417 {
418     char *argv[64], *p;
419     int i, pid;
420     int to_prog[2], from_prog[2];
421     ChildProc *cp;
422     char buf[MSG_SIZ];
423
424     if (appData.debugMode) {
425         fprintf(debugFP, "StartChildProcess (dir=\"%s\") %s\n",dir, cmdLine);
426     }
427
428     /* We do NOT feed the cmdLine to the shell; we just
429        parse it into blank-separated arguments in the
430        most simple-minded way possible.
431        */
432     i = 0;
433     safeStrCpy(buf, cmdLine, sizeof(buf)/sizeof(buf[0]) );
434     p = buf;
435     for (;;) {
436         while(*p == ' ') p++;
437         argv[i++] = p;
438         if(*p == '"' || *p == '\'')
439              p = strchr(++argv[i-1], *p);
440         else p = strchr(p, ' ');
441         if (p == NULL) break;
442         *p++ = NULLCHAR;
443     }
444     argv[i] = NULL;
445
446     SetUpChildIO(to_prog, from_prog);
447
448     if ((pid = fork()) == 0) {
449         /* Child process */
450         // [HGM] PSWBTM: made order resistant against case where fd of created pipe was 0 or 1
451         close(to_prog[1]);     // first close the unused pipe ends
452         close(from_prog[0]);
453         dup2(to_prog[0], 0);   // to_prog was created first, nd is the only one to use 0 or 1
454         dup2(from_prog[1], 1);
455         if(to_prog[0] >= 2) close(to_prog[0]); // if 0 or 1, the dup2 already cosed the original
456         close(from_prog[1]);                   // and closing again loses one of the pipes!
457         if(fileno(stderr) >= 2) // better safe than sorry...
458                 dup2(1, fileno(stderr)); /* force stderr to the pipe */
459
460         if (dir[0] != NULLCHAR && chdir(dir) != 0) {
461             perror(dir);
462             exit(1);
463         }
464
465         nice(appData.niceEngines); // [HGM] nice: adjust priority of engine proc
466
467         execvp(argv[0], argv);
468
469         /* If we get here, exec failed */
470         perror(argv[0]);
471         exit(1);
472     }
473
474     /* Parent process */
475     close(to_prog[0]);
476     close(from_prog[1]);
477
478     cp = (ChildProc *) calloc(1, sizeof(ChildProc));
479     cp->kind = CPReal;
480     cp->pid = pid;
481     cp->fdFrom = from_prog[0];
482     cp->fdTo = to_prog[1];
483     *pr = (ProcRef) cp;
484     return 0;
485 }
486
487 // [HGM] kill: implement the 'hard killing' of AS's Winboard_x
488 static int pid;
489
490 static RETSIGTYPE
491 AlarmCallBack (int n)
492 {
493     kill(pid, SIGKILL); // kill forcefully
494     return;
495 }
496
497 void
498 DestroyChildProcess (ProcRef pr, int signalType)
499 {
500     ChildProc *cp = (ChildProc *) pr;
501
502     if (cp->kind != CPReal) return;
503     cp->kind = CPNone;
504     if (signalType & 1) {
505             kill(cp->pid, signalType == 9 ? SIGKILL : SIGTERM); // [HGM] kill: for 9 hard-kill immediately
506     }
507     signal(SIGALRM, AlarmCallBack);
508     pid = cp->pid;
509     if(signalType & 4) alarm(1 + appData.delayAfterQuit); // [HGM] kill: schedule hard kill if so requested
510     /* Process is exiting either because of the kill or because of
511        a quit command sent by the backend; either way, wait for it to die.
512     */
513     wait((int *) 0);
514     alarm(0); // cancel alarm if still pending
515     close(cp->fdFrom);
516     close(cp->fdTo);
517 }
518
519 char *
520 BufferCommandOutput (char *command, int size)
521 {
522     char *res = (char *) calloc(1, size);
523     if(res) {
524         int count;
525         FILE *f;
526 #if 0
527         ChildProc *pr;
528         StartChildProcess(command, ".", (ProcRef) &pr);    // run command in daughter process
529         f = fdopen(pr->fdFrom, "r");
530         count = fread(res, 1, size-1, f);  // read its output
531         fclose(f);
532         DestroyChildProcess((ProcRef) pr, 9);
533         free(pr);
534 #else
535         f = popen(command, "r");
536         if(!f) return res;
537         count = fread(res, 1, size-1, f);  // read its output
538         pclose(f);
539 #endif
540         res[count > 0 ? count : 0] = NULLCHAR;  
541     }
542     return res; // return buffer with output
543 }
544
545 void
546 InterruptChildProcess (ProcRef pr)
547 {
548     ChildProc *cp = (ChildProc *) pr;
549
550     if (cp->kind != CPReal) return;
551     (void) kill(cp->pid, SIGINT); /* stop it thinking */
552 }
553
554 int
555 OpenTelnet (char *host, char *port, ProcRef *pr)
556 {
557     char cmdLine[MSG_SIZ];
558
559     if (port[0] == NULLCHAR) {
560       snprintf(cmdLine, sizeof(cmdLine), "%s %s", appData.telnetProgram, host);
561     } else {
562       snprintf(cmdLine, sizeof(cmdLine), "%s %s %s", appData.telnetProgram, host, port);
563     }
564     return StartChildProcess(cmdLine, "", pr);
565 }
566
567 int
568 OpenTCP (char *host, char *port, ProcRef *pr)
569 {
570 #if OMIT_SOCKETS
571     DisplayFatalError(_("Socket support is not configured in"), 0, 2);
572 #else  /* !OMIT_SOCKETS */
573     struct addrinfo hints;
574     struct addrinfo *ais, *ai;
575     int error;
576     int s=0;
577     ChildProc *cp;
578
579     memset(&hints, 0, sizeof(hints));
580     hints.ai_family = AF_UNSPEC;
581     hints.ai_socktype = SOCK_STREAM;
582
583     error = getaddrinfo(host, port, &hints, &ais);
584     if (error != 0) {
585       /* a getaddrinfo error is not an errno, so can't return it */
586       fprintf(debugFP, "getaddrinfo(%s, %s): %s\n",
587               host, port, gai_strerror(error));
588       return ENOENT;
589     }
590
591     for (ai = ais; ai != NULL; ai = ai->ai_next) {
592       if ((s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) < 0) {
593         error = errno;
594         continue;
595       }
596       if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0) {
597         error = errno;
598         continue;
599       }
600       error = 0;
601       break;
602     }
603     freeaddrinfo(ais);
604
605     if (error != 0) {
606       return error;
607     }
608
609     cp = (ChildProc *) calloc(1, sizeof(ChildProc));
610     cp->kind = CPSock;
611     cp->pid = 0;
612     cp->fdFrom = s;
613     cp->fdTo = s;
614     *pr = (ProcRef) cp;
615 #endif /* !OMIT_SOCKETS */
616
617     return 0;
618 }
619
620 int
621 OpenCommPort (char *name, ProcRef *pr)
622 {
623     int fd;
624     ChildProc *cp;
625
626     fd = open(name, 2, 0);
627     if (fd < 0) return errno;
628
629     cp = (ChildProc *) calloc(1, sizeof(ChildProc));
630     cp->kind = CPComm;
631     cp->pid = 0;
632     cp->fdFrom = fd;
633     cp->fdTo = fd;
634     *pr = (ProcRef) cp;
635
636     return 0;
637 }
638
639 int
640 OpenLoopback (ProcRef *pr)
641 {
642     ChildProc *cp;
643     int to[2], from[2];
644
645     SetUpChildIO(to, from);
646
647     cp = (ChildProc *) calloc(1, sizeof(ChildProc));
648     cp->kind = CPLoop;
649     cp->pid = 0;
650     cp->fdFrom = to[0];         /* note not from[0]; we are doing a loopback */
651     cp->fdTo = to[1];
652     *pr = (ProcRef) cp;
653
654     return 0;
655 }
656
657 int
658 OpenRcmd (char *host, char *user, char *cmd, ProcRef *pr)
659 {
660     DisplayFatalError(_("internal rcmd not implemented for Unix"), 0, 1);
661     return -1;
662 }
663
664 Boolean stdoutClosed = FALSE;
665
666 int
667 OutputToProcess (ProcRef pr, char *message, int count, int *outError)
668 {
669     static int line = 0;
670     ChildProc *cp = (ChildProc *) pr;
671     int outCount = count;
672
673     if (pr == NoProc)
674     {
675         if (appData.noJoin || !appData.useInternalWrap) {
676             if(!stdoutClosed) outCount = fwrite(message, 1, count, stdout);
677         } else
678         {
679             int width = get_term_width();
680             int len = wrap(NULL, message, count, width, &line);
681             char *msg = malloc(len);
682             int dbgchk;
683
684             if (!msg)
685                 outCount = fwrite(message, 1, count, stdout);
686             else
687             {
688                 dbgchk = wrap(msg, message, count, width, &line);
689                 if (dbgchk != len && appData.debugMode)
690                     fprintf(debugFP, "wrap(): dbgchk(%d) != len(%d)\n", dbgchk, len);
691                 outCount = fwrite(msg, 1, dbgchk, stdout);
692                 free(msg);
693             }
694         }
695         if(*message != '\033') ConsoleWrite(message, count);
696     }
697     else
698       outCount = write(cp->fdTo, message, count);
699
700     if (outCount == -1)
701       *outError = errno;
702     else
703       *outError = 0;
704
705     return outCount;
706 }
707
708 /* Output message to process, with "ms" milliseconds of delay
709    between each character. This is needed when sending the logon
710    script to ICC, which for some reason doesn't like the
711    instantaneous send. */
712 int
713 OutputToProcessDelayed (ProcRef pr, char *message, int count, int *outError, long msdelay)
714 {
715     ChildProc *cp = (ChildProc *) pr;
716     int outCount = 0;
717     int r;
718
719     while (count--) {
720         r = write(cp->fdTo, message++, 1);
721         if (r == -1) {
722             *outError = errno;
723             return outCount;
724         }
725         ++outCount;
726         if (msdelay >= 0)
727           TimeDelay(msdelay);
728     }
729
730     return outCount;
731 }
732
733 int
734 ICSInitScript ()
735 {
736   /* try to open the icsLogon script, either in the location given
737    * or in the users HOME directory
738    */
739
740   FILE *f;
741   char buf[MSG_SIZ];
742   char *homedir;
743
744   f = fopen(appData.icsLogon, "r");
745   if (f == NULL)
746     {
747       homedir = getenv("HOME");
748       if (homedir != NULL)
749         {
750           safeStrCpy(buf, homedir, sizeof(buf)/sizeof(buf[0]) );
751           strncat(buf, "/", MSG_SIZ - strlen(buf) - 1);
752           strncat(buf, appData.icsLogon,  MSG_SIZ - strlen(buf) - 1);
753           f = fopen(buf, "r");
754         }
755     }
756
757   if (f != NULL) {
758     ProcessICSInitScript(f);
759     return TRUE;
760   } else
761     printf("Warning: Couldn't open icsLogon file (checked %s and %s).\n", appData.icsLogon, buf);
762
763   return FALSE;
764 }
765
766 void
767 ResetFrontEnd ()
768 {
769     CommentPopDown();
770     TagsPopDown();
771     return;
772 }
773
774 #include <sys/ioctl.h>
775 static int
776 get_term_width ()
777 {
778     int fd, default_width;
779
780     fd = STDIN_FILENO;
781     default_width = 79; // this is FICS default anyway...
782
783 #if !defined(TIOCGWINSZ) && defined(TIOCGSIZE)
784     struct ttysize win;
785     if (!ioctl(fd, TIOCGSIZE, &win))
786         default_width = win.ts_cols;
787 #elif defined(TIOCGWINSZ)
788     struct winsize win;
789     if (!ioctl(fd, TIOCGWINSZ, &win))
790         default_width = win.ws_col;
791 #endif
792     return default_width;
793 }
794
795 void
796 update_ics_width ()
797 {
798   static int old_width = 0;
799   int new_width = get_term_width();
800
801   if (old_width != new_width)
802     ics_printf("set width %d\n", new_width);
803   old_width = new_width;
804 }
805
806 void
807 NotifyFrontendLogin ()
808 {
809     update_ics_width();
810 }