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