Update zh_CN.po translation
[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     ChildProc *pr;
523     char *res = (char *) calloc(1, size);
524     if(res) {
525         int count;
526         FILE *f;
527         StartChildProcess(command, ".", (ProcRef) &pr);    // run command in daughter process
528         f = fdopen(pr->fdFrom, "r");
529         count = fread(res, 1, size-1, f);  // read its output
530         fclose(f);
531         res[count > 0 ? count : 0] = NULLCHAR;  
532         DestroyChildProcess((ProcRef) pr, 9);
533         free(pr);
534     }
535     return res; // return buffer with output
536 }
537
538 void
539 InterruptChildProcess (ProcRef pr)
540 {
541     ChildProc *cp = (ChildProc *) pr;
542
543     if (cp->kind != CPReal) return;
544     (void) kill(cp->pid, SIGINT); /* stop it thinking */
545 }
546
547 int
548 OpenTelnet (char *host, char *port, ProcRef *pr)
549 {
550     char cmdLine[MSG_SIZ];
551
552     if (port[0] == NULLCHAR) {
553       snprintf(cmdLine, sizeof(cmdLine), "%s %s", appData.telnetProgram, host);
554     } else {
555       snprintf(cmdLine, sizeof(cmdLine), "%s %s %s", appData.telnetProgram, host, port);
556     }
557     return StartChildProcess(cmdLine, "", pr);
558 }
559
560 int
561 OpenTCP (char *host, char *port, ProcRef *pr)
562 {
563 #if OMIT_SOCKETS
564     DisplayFatalError(_("Socket support is not configured in"), 0, 2);
565 #else  /* !OMIT_SOCKETS */
566     struct addrinfo hints;
567     struct addrinfo *ais, *ai;
568     int error;
569     int s=0;
570     ChildProc *cp;
571
572     memset(&hints, 0, sizeof(hints));
573     hints.ai_family = AF_UNSPEC;
574     hints.ai_socktype = SOCK_STREAM;
575
576     error = getaddrinfo(host, port, &hints, &ais);
577     if (error != 0) {
578       /* a getaddrinfo error is not an errno, so can't return it */
579       fprintf(debugFP, "getaddrinfo(%s, %s): %s\n",
580               host, port, gai_strerror(error));
581       return ENOENT;
582     }
583
584     for (ai = ais; ai != NULL; ai = ai->ai_next) {
585       if ((s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) < 0) {
586         error = errno;
587         continue;
588       }
589       if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0) {
590         error = errno;
591         continue;
592       }
593       error = 0;
594       break;
595     }
596     freeaddrinfo(ais);
597
598     if (error != 0) {
599       return error;
600     }
601
602     cp = (ChildProc *) calloc(1, sizeof(ChildProc));
603     cp->kind = CPSock;
604     cp->pid = 0;
605     cp->fdFrom = s;
606     cp->fdTo = s;
607     *pr = (ProcRef) cp;
608 #endif /* !OMIT_SOCKETS */
609
610     return 0;
611 }
612
613 int
614 OpenCommPort (char *name, ProcRef *pr)
615 {
616     int fd;
617     ChildProc *cp;
618
619     fd = open(name, 2, 0);
620     if (fd < 0) return errno;
621
622     cp = (ChildProc *) calloc(1, sizeof(ChildProc));
623     cp->kind = CPComm;
624     cp->pid = 0;
625     cp->fdFrom = fd;
626     cp->fdTo = fd;
627     *pr = (ProcRef) cp;
628
629     return 0;
630 }
631
632 int
633 OpenLoopback (ProcRef *pr)
634 {
635     ChildProc *cp;
636     int to[2], from[2];
637
638     SetUpChildIO(to, from);
639
640     cp = (ChildProc *) calloc(1, sizeof(ChildProc));
641     cp->kind = CPLoop;
642     cp->pid = 0;
643     cp->fdFrom = to[0];         /* note not from[0]; we are doing a loopback */
644     cp->fdTo = to[1];
645     *pr = (ProcRef) cp;
646
647     return 0;
648 }
649
650 int
651 OpenRcmd (char *host, char *user, char *cmd, ProcRef *pr)
652 {
653     DisplayFatalError(_("internal rcmd not implemented for Unix"), 0, 1);
654     return -1;
655 }
656
657 Boolean stdoutClosed = FALSE;
658
659 int
660 OutputToProcess (ProcRef pr, char *message, int count, int *outError)
661 {
662     static int line = 0;
663     ChildProc *cp = (ChildProc *) pr;
664     int outCount = count;
665
666     if (pr == NoProc)
667     {
668         if (appData.noJoin || !appData.useInternalWrap) {
669             if(!stdoutClosed) outCount = fwrite(message, 1, count, stdout);
670         } else
671         {
672             int width = get_term_width();
673             int len = wrap(NULL, message, count, width, &line);
674             char *msg = malloc(len);
675             int dbgchk;
676
677             if (!msg)
678                 outCount = fwrite(message, 1, count, stdout);
679             else
680             {
681                 dbgchk = wrap(msg, message, count, width, &line);
682                 if (dbgchk != len && appData.debugMode)
683                     fprintf(debugFP, "wrap(): dbgchk(%d) != len(%d)\n", dbgchk, len);
684                 outCount = fwrite(msg, 1, dbgchk, stdout);
685                 free(msg);
686             }
687         }
688         if(*message != '\033') ConsoleWrite(message, count);
689     }
690     else
691       outCount = write(cp->fdTo, message, count);
692
693     if (outCount == -1)
694       *outError = errno;
695     else
696       *outError = 0;
697
698     return outCount;
699 }
700
701 /* Output message to process, with "ms" milliseconds of delay
702    between each character. This is needed when sending the logon
703    script to ICC, which for some reason doesn't like the
704    instantaneous send. */
705 int
706 OutputToProcessDelayed (ProcRef pr, char *message, int count, int *outError, long msdelay)
707 {
708     ChildProc *cp = (ChildProc *) pr;
709     int outCount = 0;
710     int r;
711
712     while (count--) {
713         r = write(cp->fdTo, message++, 1);
714         if (r == -1) {
715             *outError = errno;
716             return outCount;
717         }
718         ++outCount;
719         if (msdelay >= 0)
720           TimeDelay(msdelay);
721     }
722
723     return outCount;
724 }
725
726 int
727 ICSInitScript ()
728 {
729   /* try to open the icsLogon script, either in the location given
730    * or in the users HOME directory
731    */
732
733   FILE *f;
734   char buf[MSG_SIZ];
735   char *homedir;
736
737   f = fopen(appData.icsLogon, "r");
738   if (f == NULL)
739     {
740       homedir = getenv("HOME");
741       if (homedir != NULL)
742         {
743           safeStrCpy(buf, homedir, sizeof(buf)/sizeof(buf[0]) );
744           strncat(buf, "/", MSG_SIZ - strlen(buf) - 1);
745           strncat(buf, appData.icsLogon,  MSG_SIZ - strlen(buf) - 1);
746           f = fopen(buf, "r");
747         }
748     }
749
750   if (f != NULL) {
751     ProcessICSInitScript(f);
752     return TRUE;
753   } else
754     printf("Warning: Couldn't open icsLogon file (checked %s and %s).\n", appData.icsLogon, buf);
755
756   return FALSE;
757 }
758
759 void
760 ResetFrontEnd ()
761 {
762     CommentPopDown();
763     TagsPopDown();
764     return;
765 }
766
767 #include <sys/ioctl.h>
768 static int
769 get_term_width ()
770 {
771     int fd, default_width;
772
773     fd = STDIN_FILENO;
774     default_width = 79; // this is FICS default anyway...
775
776 #if !defined(TIOCGWINSZ) && defined(TIOCGSIZE)
777     struct ttysize win;
778     if (!ioctl(fd, TIOCGSIZE, &win))
779         default_width = win.ts_cols;
780 #elif defined(TIOCGWINSZ)
781     struct winsize win;
782     if (!ioctl(fd, TIOCGWINSZ, &win))
783         default_width = win.ws_col;
784 #endif
785     return default_width;
786 }
787
788 void
789 update_ics_width ()
790 {
791   static int old_width = 0;
792   int new_width = get_term_width();
793
794   if (old_width != new_width)
795     ics_printf("set width %d\n", new_width);
796   old_width = new_width;
797 }
798
799 void
800 NotifyFrontendLogin ()
801 {
802     update_ics_width();
803 }