updated ChangeLog, DIFFSTAT and SHORTLOG
[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 Boolean stdoutClosed = FALSE;
654
655 int
656 OutputToProcess (ProcRef pr, char *message, int count, int *outError)
657 {
658     static int line = 0;
659     ChildProc *cp = (ChildProc *) pr;
660     int outCount = count;
661
662     if (pr == NoProc)
663     {
664         if (appData.noJoin || !appData.useInternalWrap) {
665             if(!stdoutClosed) outCount = fwrite(message, 1, count, stdout);
666         } else
667         {
668             int width = get_term_width();
669             int len = wrap(NULL, message, count, width, &line);
670             char *msg = malloc(len);
671             int dbgchk;
672
673             if (!msg)
674                 outCount = fwrite(message, 1, count, stdout);
675             else
676             {
677                 dbgchk = wrap(msg, message, count, width, &line);
678                 if (dbgchk != len && appData.debugMode)
679                     fprintf(debugFP, "wrap(): dbgchk(%d) != len(%d)\n", dbgchk, len);
680                 outCount = fwrite(msg, 1, dbgchk, stdout);
681                 free(msg);
682             }
683         }
684         if(*message != '\033') ConsoleWrite(message, count);
685     }
686     else
687       outCount = write(cp->fdTo, message, count);
688
689     if (outCount == -1)
690       *outError = errno;
691     else
692       *outError = 0;
693
694     return outCount;
695 }
696
697 /* Output message to process, with "ms" milliseconds of delay
698    between each character. This is needed when sending the logon
699    script to ICC, which for some reason doesn't like the
700    instantaneous send. */
701 int
702 OutputToProcessDelayed (ProcRef pr, char *message, int count, int *outError, long msdelay)
703 {
704     ChildProc *cp = (ChildProc *) pr;
705     int outCount = 0;
706     int r;
707
708     while (count--) {
709         r = write(cp->fdTo, message++, 1);
710         if (r == -1) {
711             *outError = errno;
712             return outCount;
713         }
714         ++outCount;
715         if (msdelay >= 0)
716           TimeDelay(msdelay);
717     }
718
719     return outCount;
720 }
721
722 int
723 ICSInitScript ()
724 {
725   /* try to open the icsLogon script, either in the location given
726    * or in the users HOME directory
727    */
728
729   FILE *f;
730   char buf[MSG_SIZ];
731   char *homedir;
732
733   f = fopen(appData.icsLogon, "r");
734   if (f == NULL)
735     {
736       homedir = getenv("HOME");
737       if (homedir != NULL)
738         {
739           safeStrCpy(buf, homedir, sizeof(buf)/sizeof(buf[0]) );
740           strncat(buf, "/", MSG_SIZ - strlen(buf) - 1);
741           strncat(buf, appData.icsLogon,  MSG_SIZ - strlen(buf) - 1);
742           f = fopen(buf, "r");
743         }
744     }
745
746   if (f != NULL) {
747     ProcessICSInitScript(f);
748     return TRUE;
749   } else
750     printf("Warning: Couldn't open icsLogon file (checked %s and %s).\n", appData.icsLogon, buf);
751
752   return FALSE;
753 }
754
755 void
756 ResetFrontEnd ()
757 {
758     CommentPopDown();
759     TagsPopDown();
760     return;
761 }
762
763 #include <sys/ioctl.h>
764 static int
765 get_term_width ()
766 {
767     int fd, default_width;
768
769     fd = STDIN_FILENO;
770     default_width = 79; // this is FICS default anyway...
771
772 #if !defined(TIOCGWINSZ) && defined(TIOCGSIZE)
773     struct ttysize win;
774     if (!ioctl(fd, TIOCGSIZE, &win))
775         default_width = win.ts_cols;
776 #elif defined(TIOCGWINSZ)
777     struct winsize win;
778     if (!ioctl(fd, TIOCGWINSZ, &win))
779         default_width = win.ws_col;
780 #endif
781     return default_width;
782 }
783
784 void
785 update_ics_width ()
786 {
787   static int old_width = 0;
788   int new_width = get_term_width();
789
790   if (old_width != new_width)
791     ics_printf("set width %d\n", new_width);
792   old_width = new_width;
793 }
794
795 void
796 NotifyFrontendLogin ()
797 {
798     update_ics_width();
799 }