Fix recent-engines menu
[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 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 char *cnames[9] = { "black", "red", "green", "yellow", "blue",
174                              "magenta", "cyan", "white" };
175 TextColors textColors[(int)NColorClasses];
176
177 /* String is: "fg, bg, attr". Which is 0, 1, 2 */
178 static int
179 parse_color (char *str, int which)
180 {
181     char *p, buf[100], *d;
182     int i;
183
184     if (strlen(str) > 99)       /* watch bounds on buf */
185       return -1;
186
187     p = str;
188     d = buf;
189     for (i=0; i<which; ++i) {
190         p = strchr(p, ',');
191         if (!p)
192           return -1;
193         ++p;
194     }
195
196     /* Could be looking at something like:
197        black, , 1
198        .. in which case we want to stop on a comma also */
199     while (*p && *p != ',' && !isalpha(*p) && !isdigit(*p))
200       ++p;
201
202     if (*p == ',') {
203         return -1;              /* Use default for empty field */
204     }
205
206     if (which == 2 || isdigit(*p))
207       return atoi(p);
208
209     while (*p && isalpha(*p))
210       *(d++) = *(p++);
211
212     *d = 0;
213
214     for (i=0; i<8; ++i) {
215         if (!StrCaseCmp(buf, cnames[i]))
216           return which? (i+40) : (i+30);
217     }
218     if (!StrCaseCmp(buf, "default")) return -1;
219
220     fprintf(stderr, _("%s: unrecognized color %s\n"), programName, buf);
221     return -2;
222 }
223
224 static int
225 parse_cpair (ColorClass cc, char *str)
226 {
227     if ((textColors[(int)cc].fg=parse_color(str, 0)) == -2) {
228         fprintf(stderr, _("%s: can't parse foreground color in `%s'\n"),
229                 programName, str);
230         return -1;
231     }
232
233     /* bg and attr are optional */
234     textColors[(int)cc].bg = parse_color(str, 1);
235     if ((textColors[(int)cc].attr = parse_color(str, 2)) < 0) {
236         textColors[(int)cc].attr = 0;
237     }
238     return 0;
239 }
240
241 void
242 ParseIcsTextColors ()
243 {   // [HGM] tken out of main(), so it can be called from ICS-Options dialog
244     if (parse_cpair(ColorShout, appData.colorShout) < 0 ||
245         parse_cpair(ColorSShout, appData.colorSShout) < 0 ||
246         parse_cpair(ColorChannel1, appData.colorChannel1) < 0  ||
247         parse_cpair(ColorChannel, appData.colorChannel) < 0  ||
248         parse_cpair(ColorKibitz, appData.colorKibitz) < 0 ||
249         parse_cpair(ColorTell, appData.colorTell) < 0 ||
250         parse_cpair(ColorChallenge, appData.colorChallenge) < 0  ||
251         parse_cpair(ColorRequest, appData.colorRequest) < 0  ||
252         parse_cpair(ColorSeek, appData.colorSeek) < 0  ||
253         parse_cpair(ColorNormal, appData.colorNormal) < 0)
254       {
255           if (appData.colorize) {
256               fprintf(stderr,
257                       _("%s: can't parse color names; disabling colorization\n"),
258                       programName);
259           }
260           appData.colorize = FALSE;
261       }
262     textColors[ColorNone].fg = textColors[ColorNone].bg = -1;
263     textColors[ColorNone].attr = 0;
264 }
265
266 static Boolean noEcho;
267
268 void
269 EchoOn ()
270 {
271     system("stty echo");
272     noEcho = False;
273 }
274
275 void
276 EchoOff ()
277 {
278     system("stty -echo");
279     noEcho = True;
280 }
281
282 char *oldICSInteractionTitle;
283
284 void
285 ShutDownFrontEnd ()
286 {
287     if (appData.icsActive && oldICSInteractionTitle != NULL) {
288         DisplayIcsInteractionTitle(oldICSInteractionTitle);
289     }
290     if (saveSettingsOnExit) SaveSettings(settingsFileName);
291     unlink(gameCopyFilename);
292     unlink(gamePasteFilename);
293     if(noEcho) EchoOn();
294 }
295
296 void
297 RunCommand (char *buf)
298 {
299     system(buf);
300 }
301
302 void
303 Colorize (ColorClass cc, int continuation)
304 {
305     char buf[MSG_SIZ];
306     int count, outCount, error;
307
308     if (textColors[(int)cc].bg > 0) {
309         if (textColors[(int)cc].fg > 0) {
310           snprintf(buf, MSG_SIZ, "\033[0;%d;%d;%dm", textColors[(int)cc].attr,
311                    textColors[(int)cc].fg, textColors[(int)cc].bg);
312         } else {
313           snprintf(buf, MSG_SIZ, "\033[0;%d;%dm", textColors[(int)cc].attr,
314                    textColors[(int)cc].bg);
315         }
316     } else {
317         if (textColors[(int)cc].fg > 0) {
318           snprintf(buf, MSG_SIZ, "\033[0;%d;%dm", textColors[(int)cc].attr,
319                     textColors[(int)cc].fg);
320         } else {
321           snprintf(buf, MSG_SIZ, "\033[0;%dm", textColors[(int)cc].attr);
322         }
323     }
324     count = strlen(buf);
325     outCount = OutputToProcess(NoProc, buf, count, &error);
326     if (outCount < count) {
327         DisplayFatalError(_("Error writing to display"), error, 1);
328     }
329
330     if (continuation) return;
331     PlaySoundForColor(cc);
332 }
333
334 char *
335 UserName ()
336 {
337     return getpwuid(getuid())->pw_name;
338 }
339
340 char *
341 ExpandPathName (char *path)
342 {
343     static char static_buf[4*MSG_SIZ];
344     char *d, *s, buf[4*MSG_SIZ];
345     struct passwd *pwd;
346
347     s = path;
348     d = static_buf;
349
350     while (*s && isspace(*s))
351       ++s;
352
353     if (!*s) {
354         *d = 0;
355         return static_buf;
356     }
357
358     if (*s == '~') {
359         if (*(s+1) == '/') {
360           safeStrCpy(d, getpwuid(getuid())->pw_dir, 4*MSG_SIZ );
361           strcat(d, s+1);
362         }
363         else {
364           safeStrCpy(buf, s+1, sizeof(buf)/sizeof(buf[0]) );
365           { char *p; if(p = strchr(buf, '/')) *p = 0; }
366           pwd = getpwnam(buf);
367           if (!pwd)
368             {
369               fprintf(stderr, _("ERROR: Unknown user %s (in path %s)\n"),
370                       buf, path);
371               return NULL;
372             }
373           safeStrCpy(d, pwd->pw_dir, 4*MSG_SIZ );
374           strcat(d, strchr(s+1, '/'));
375         }
376     }
377     else
378       safeStrCpy(d, s, 4*MSG_SIZ );
379
380     return static_buf;
381 }
382
383 int
384 MySearchPath (char *installDir, char *name, char *fullname)
385 { // just append installDir and name. Perhaps ExpandPath should be used here?
386   name = ExpandPathName(name);
387   if(name && name[0] == '/')
388     safeStrCpy(fullname, name, MSG_SIZ );
389   else {
390     sprintf(fullname, "%s%c%s", installDir, '/', name);
391   }
392   return 1;
393 }
394
395 int
396 MyGetFullPathName (char *name, char *fullname)
397 { // should use ExpandPath?
398   name = ExpandPathName(name);
399   safeStrCpy(fullname, name, MSG_SIZ );
400   return 1;
401 }
402
403 char *
404 HostName ()
405 {
406     static char host_name[MSG_SIZ];
407
408 #if HAVE_GETHOSTNAME
409     gethostname(host_name, MSG_SIZ);
410     return host_name;
411 #else  /* not HAVE_GETHOSTNAME */
412 # if HAVE_SYSINFO && HAVE_SYS_SYSTEMINFO_H
413     sysinfo(SI_HOSTNAME, host_name, MSG_SIZ);
414     return host_name;
415 # else /* not (HAVE_SYSINFO && HAVE_SYS_SYSTEMINFO_H) */
416     return "localhost";
417 # endif/* not (HAVE_SYSINFO && HAVE_SYS_SYSTEMINFO_H) */
418 #endif /* not HAVE_GETHOSTNAME */
419 }
420
421
422 int
423 StartChildProcess (char *cmdLine, char *dir, ProcRef *pr)
424 {
425     char *argv[64], *p;
426     int i, pid;
427     int to_prog[2], from_prog[2];
428     ChildProc *cp;
429     char buf[MSG_SIZ];
430
431     if (appData.debugMode) {
432         fprintf(debugFP, "StartChildProcess (dir=\"%s\") %s\n",dir, cmdLine);
433     }
434
435     /* We do NOT feed the cmdLine to the shell; we just
436        parse it into blank-separated arguments in the
437        most simple-minded way possible.
438        */
439     i = 0;
440     safeStrCpy(buf, cmdLine, sizeof(buf)/sizeof(buf[0]) );
441     p = buf;
442     for (;;) {
443         while(*p == ' ') p++;
444         argv[i++] = p;
445         if(*p == '"' || *p == '\'')
446              p = strchr(++argv[i-1], *p);
447         else p = strchr(p, ' ');
448         if (p == NULL) break;
449         *p++ = NULLCHAR;
450     }
451     argv[i] = NULL;
452
453     SetUpChildIO(to_prog, from_prog);
454
455     if ((pid = fork()) == 0) {
456         /* Child process */
457         // [HGM] PSWBTM: made order resistant against case where fd of created pipe was 0 or 1
458         close(to_prog[1]);     // first close the unused pipe ends
459         close(from_prog[0]);
460         dup2(to_prog[0], 0);   // to_prog was created first, nd is the only one to use 0 or 1
461         dup2(from_prog[1], 1);
462         if(to_prog[0] >= 2) close(to_prog[0]); // if 0 or 1, the dup2 already cosed the original
463         close(from_prog[1]);                   // and closing again loses one of the pipes!
464         if(fileno(stderr) >= 2) // better safe than sorry...
465                 dup2(1, fileno(stderr)); /* force stderr to the pipe */
466
467         if (dir[0] != NULLCHAR && chdir(dir) != 0) {
468             perror(dir);
469             exit(1);
470         }
471
472         nice(appData.niceEngines); // [HGM] nice: adjust priority of engine proc
473
474         execvp(argv[0], argv);
475
476         /* If we get here, exec failed */
477         perror(argv[0]);
478         exit(1);
479     }
480
481     /* Parent process */
482     close(to_prog[0]);
483     close(from_prog[1]);
484
485     cp = (ChildProc *) calloc(1, sizeof(ChildProc));
486     cp->kind = CPReal;
487     cp->pid = pid;
488     cp->fdFrom = from_prog[0];
489     cp->fdTo = to_prog[1];
490     *pr = (ProcRef) cp;
491     return 0;
492 }
493
494 // [HGM] kill: implement the 'hard killing' of AS's Winboard_x
495 static RETSIGTYPE
496 AlarmCallBack (int n)
497 {
498     return;
499 }
500
501 void
502 DestroyChildProcess (ProcRef pr, int signalType)
503 {
504     ChildProc *cp = (ChildProc *) pr;
505
506     if (cp->kind != CPReal) return;
507     cp->kind = CPNone;
508     if (signalType == 10) { // [HGM] kill: if it does not terminate in 3 sec, kill
509         signal(SIGALRM, AlarmCallBack);
510         alarm(3);
511         if(wait((int *) 0) == -1) { // process does not terminate on its own accord
512             kill(cp->pid, SIGKILL); // kill it forcefully
513             wait((int *) 0);        // and wait again
514         }
515     } else {
516         if (signalType) {
517             kill(cp->pid, signalType == 9 ? SIGKILL : SIGTERM); // [HGM] kill: use hard kill if so requested
518         }
519         /* Process is exiting either because of the kill or because of
520            a quit command sent by the backend; either way, wait for it to die.
521         */
522         wait((int *) 0);
523     }
524     close(cp->fdFrom);
525     close(cp->fdTo);
526 }
527
528 void
529 InterruptChildProcess (ProcRef pr)
530 {
531     ChildProc *cp = (ChildProc *) pr;
532
533     if (cp->kind != CPReal) return;
534     (void) kill(cp->pid, SIGINT); /* stop it thinking */
535 }
536
537 int
538 OpenTelnet (char *host, char *port, ProcRef *pr)
539 {
540     char cmdLine[MSG_SIZ];
541
542     if (port[0] == NULLCHAR) {
543       snprintf(cmdLine, sizeof(cmdLine), "%s %s", appData.telnetProgram, host);
544     } else {
545       snprintf(cmdLine, sizeof(cmdLine), "%s %s %s", appData.telnetProgram, host, port);
546     }
547     return StartChildProcess(cmdLine, "", pr);
548 }
549
550 int
551 OpenTCP (char *host, char *port, ProcRef *pr)
552 {
553 #if OMIT_SOCKETS
554     DisplayFatalError(_("Socket support is not configured in"), 0, 2);
555 #else  /* !OMIT_SOCKETS */
556     struct addrinfo hints;
557     struct addrinfo *ais, *ai;
558     int error;
559     int s=0;
560     ChildProc *cp;
561
562     memset(&hints, 0, sizeof(hints));
563     hints.ai_family = AF_UNSPEC;
564     hints.ai_socktype = SOCK_STREAM;
565
566     error = getaddrinfo(host, port, &hints, &ais);
567     if (error != 0) {
568       /* a getaddrinfo error is not an errno, so can't return it */
569       fprintf(debugFP, "getaddrinfo(%s, %s): %s\n",
570               host, port, gai_strerror(error));
571       return ENOENT;
572     }
573      
574     for (ai = ais; ai != NULL; ai = ai->ai_next) {
575       if ((s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) < 0) {
576         error = errno;
577         continue;
578       }
579       if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0) {
580         error = errno;
581         continue;
582       }
583       error = 0;
584       break;
585     }
586     freeaddrinfo(ais);
587
588     if (error != 0) {
589       return error;
590     }
591
592     cp = (ChildProc *) calloc(1, sizeof(ChildProc));
593     cp->kind = CPSock;
594     cp->pid = 0;
595     cp->fdFrom = s;
596     cp->fdTo = s;
597     *pr = (ProcRef) cp;
598 #endif /* !OMIT_SOCKETS */
599
600     return 0;
601 }
602
603 int
604 OpenCommPort (char *name, ProcRef *pr)
605 {
606     int fd;
607     ChildProc *cp;
608
609     fd = open(name, 2, 0);
610     if (fd < 0) return errno;
611
612     cp = (ChildProc *) calloc(1, sizeof(ChildProc));
613     cp->kind = CPComm;
614     cp->pid = 0;
615     cp->fdFrom = fd;
616     cp->fdTo = fd;
617     *pr = (ProcRef) cp;
618
619     return 0;
620 }
621
622 int
623 OpenLoopback (ProcRef *pr)
624 {
625     ChildProc *cp;
626     int to[2], from[2];
627
628     SetUpChildIO(to, from);
629
630     cp = (ChildProc *) calloc(1, sizeof(ChildProc));
631     cp->kind = CPLoop;
632     cp->pid = 0;
633     cp->fdFrom = to[0];         /* note not from[0]; we are doing a loopback */
634     cp->fdTo = to[1];
635     *pr = (ProcRef) cp;
636
637     return 0;
638 }
639
640 int
641 OpenRcmd (char *host, char *user, char *cmd, ProcRef *pr)
642 {
643     DisplayFatalError(_("internal rcmd not implemented for Unix"), 0, 1);
644     return -1;
645 }
646
647 int
648 OutputToProcess (ProcRef pr, char *message, int count, int *outError)
649 {
650     static int line = 0;
651     ChildProc *cp = (ChildProc *) pr;
652     int outCount;
653
654     if (pr == NoProc)
655     {
656         if (appData.noJoin || !appData.useInternalWrap)
657             outCount = fwrite(message, 1, count, stdout);
658         else
659         {
660             int width = get_term_width();
661             int len = wrap(NULL, message, count, width, &line);
662             char *msg = malloc(len);
663             int dbgchk;
664
665             if (!msg)
666                 outCount = fwrite(message, 1, count, stdout);
667             else
668             {
669                 dbgchk = wrap(msg, message, count, width, &line);
670                 if (dbgchk != len && appData.debugMode)
671                     fprintf(debugFP, "wrap(): dbgchk(%d) != len(%d)\n", dbgchk, len);
672                 outCount = fwrite(msg, 1, dbgchk, stdout);
673                 free(msg);
674             }
675         }
676     }
677     else
678       outCount = write(cp->fdTo, message, count);
679
680     if (outCount == -1)
681       *outError = errno;
682     else
683       *outError = 0;
684
685     return outCount;
686 }
687
688 /* Output message to process, with "ms" milliseconds of delay
689    between each character. This is needed when sending the logon
690    script to ICC, which for some reason doesn't like the
691    instantaneous send. */
692 int
693 OutputToProcessDelayed (ProcRef pr, char *message, int count, int *outError, long msdelay)
694 {
695     ChildProc *cp = (ChildProc *) pr;
696     int outCount = 0;
697     int r;
698
699     while (count--) {
700         r = write(cp->fdTo, message++, 1);
701         if (r == -1) {
702             *outError = errno;
703             return outCount;
704         }
705         ++outCount;
706         if (msdelay >= 0)
707           TimeDelay(msdelay);
708     }
709
710     return outCount;
711 }
712
713 void
714 ICSInitScript ()
715 {
716   /* try to open the icsLogon script, either in the location given
717    * or in the users HOME directory
718    */
719
720   FILE *f;
721   char buf[MSG_SIZ];
722   char *homedir;
723
724   f = fopen(appData.icsLogon, "r");
725   if (f == NULL)
726     {
727       homedir = getenv("HOME");
728       if (homedir != NULL)
729         {
730           safeStrCpy(buf, homedir, sizeof(buf)/sizeof(buf[0]) );
731           strncat(buf, "/", MSG_SIZ - strlen(buf) - 1);
732           strncat(buf, appData.icsLogon,  MSG_SIZ - strlen(buf) - 1);
733           f = fopen(buf, "r");
734         }
735     }
736
737   if (f != NULL)
738     ProcessICSInitScript(f);
739   else
740     printf("Warning: Couldn't open icsLogon file (checked %s and %s).\n", appData.icsLogon, buf);
741
742   return;
743 }
744
745 void
746 ResetFrontEnd ()
747 {
748     CommentPopDown();
749     TagsPopDown();
750     return;
751 }
752
753 #include <sys/ioctl.h>
754 int
755 get_term_width ()
756 {
757     int fd, default_width;
758
759     fd = STDIN_FILENO;
760     default_width = 79; // this is FICS default anyway...
761
762 #if !defined(TIOCGWINSZ) && defined(TIOCGSIZE)
763     struct ttysize win;
764     if (!ioctl(fd, TIOCGSIZE, &win))
765         default_width = win.ts_cols;
766 #elif defined(TIOCGWINSZ)
767     struct winsize win;
768     if (!ioctl(fd, TIOCGWINSZ, &win))
769         default_width = win.ws_col;
770 #endif
771     return default_width;
772 }
773
774 void
775 update_ics_width ()
776 {
777   static int old_width = 0;
778   int new_width = get_term_width();
779
780   if (old_width != new_width)
781     ics_printf("set width %d\n", new_width);
782   old_width = new_width;
783 }
784
785 void
786 NotifyFrontendLogin ()
787 {
788     update_ics_width();
789 }
790
791