Split usounds.c and usystem.c from xboard.c
[xboard.git] / usystem.c
diff --git a/usystem.c b/usystem.c
new file mode 100644 (file)
index 0000000..a24d04f
--- /dev/null
+++ b/usystem.c
@@ -0,0 +1,791 @@
+/*
+ * usystem.c -- X-free, but Unix-like code for XBoard front end 
+ *
+ * Copyright 1991 by Digital Equipment Corporation, Maynard,
+ * Massachusetts.
+ *
+ * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
+ * 2007, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
+ *
+ * The following terms apply to Digital Equipment Corporation's copyright
+ * interest in XBoard:
+ * ------------------------------------------------------------------------
+ * All Rights Reserved
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose and without fee is hereby granted,
+ * provided that the above copyright notice appear in all copies and that
+ * both that copyright notice and this permission notice appear in
+ * supporting documentation, and that the name of Digital not be
+ * used in advertising or publicity pertaining to distribution of the
+ * software without specific, written prior permission.
+ *
+ * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
+ * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
+ * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
+ * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+ * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ * ------------------------------------------------------------------------
+ *
+ * The following terms apply to the enhanced version of XBoard
+ * distributed by the Free Software Foundation:
+ * ------------------------------------------------------------------------
+ *
+ * GNU XBoard is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * GNU XBoard is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see http://www.gnu.org/licenses/.  *
+ *
+ *------------------------------------------------------------------------
+ ** See the file ChangeLog for a revision history.  */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <ctype.h>
+#include <signal.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <pwd.h>
+#include <math.h>
+
+#if !OMIT_SOCKETS
+# if HAVE_SYS_SOCKET_H
+#  include <sys/socket.h>
+#  include <netinet/in.h>
+#  include <netdb.h>
+# else /* not HAVE_SYS_SOCKET_H */
+#  if HAVE_LAN_SOCKET_H
+#   include <lan/socket.h>
+#   include <lan/in.h>
+#   include <lan/netdb.h>
+#  else /* not HAVE_LAN_SOCKET_H */
+#   define OMIT_SOCKETS 1
+#  endif /* not HAVE_LAN_SOCKET_H */
+# endif /* not HAVE_SYS_SOCKET_H */
+#endif /* !OMIT_SOCKETS */
+
+#if STDC_HEADERS
+# include <stdlib.h>
+# include <string.h>
+#else /* not STDC_HEADERS */
+extern char *getenv();
+# if HAVE_STRING_H
+#  include <string.h>
+# else /* not HAVE_STRING_H */
+#  include <strings.h>
+# endif /* not HAVE_STRING_H */
+#endif /* not STDC_HEADERS */
+
+#if HAVE_SYS_FCNTL_H
+# include <sys/fcntl.h>
+#else /* not HAVE_SYS_FCNTL_H */
+# if HAVE_FCNTL_H
+#  include <fcntl.h>
+# endif /* HAVE_FCNTL_H */
+#endif /* not HAVE_SYS_FCNTL_H */
+
+#if HAVE_SYS_SYSTEMINFO_H
+# include <sys/systeminfo.h>
+#endif /* HAVE_SYS_SYSTEMINFO_H */
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+#  include <sys/time.h>
+# else
+#  include <time.h>
+# endif
+#endif
+
+#if HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#if HAVE_SYS_WAIT_H
+# include <sys/wait.h>
+#endif
+
+#if HAVE_DIRENT_H
+# include <dirent.h>
+# define NAMLEN(dirent) strlen((dirent)->d_name)
+# define HAVE_DIR_STRUCT
+#else
+# define dirent direct
+# define NAMLEN(dirent) (dirent)->d_namlen
+# if HAVE_SYS_NDIR_H
+#  include <sys/ndir.h>
+#  define HAVE_DIR_STRUCT
+# endif
+# if HAVE_SYS_DIR_H
+#  include <sys/dir.h>
+#  define HAVE_DIR_STRUCT
+# endif
+# if HAVE_NDIR_H
+#  include <ndir.h>
+#  define HAVE_DIR_STRUCT
+# endif
+#endif
+
+#if ENABLE_NLS
+#include <locale.h>
+#endif
+
+// [HGM] bitmaps: put before incuding the bitmaps / pixmaps, to know how many piece types there are.
+#include "common.h"
+
+#include "frontend.h"
+#include "backend.h"
+#include "childio.h"
+#include "menus.h"
+#include "usystem.h"
+#include "gettext.h"
+
+
+#ifdef __EMX__
+#ifndef HAVE_USLEEP
+#define HAVE_USLEEP
+#endif
+#define usleep(t)   _sleep2(((t)+500)/1000)
+#endif
+
+#ifdef ENABLE_NLS
+# define  _(s) gettext (s)
+# define N_(s) gettext_noop (s)
+#else
+# define  _(s) (s)
+# define N_(s)  s
+#endif
+
+static char *cnames[9] = { "black", "red", "green", "yellow", "blue",
+                            "magenta", "cyan", "white" };
+TextColors textColors[(int)NColorClasses];
+
+/* String is: "fg, bg, attr". Which is 0, 1, 2 */
+static int
+parse_color (char *str, int which)
+{
+    char *p, buf[100], *d;
+    int i;
+
+    if (strlen(str) > 99)      /* watch bounds on buf */
+      return -1;
+
+    p = str;
+    d = buf;
+    for (i=0; i<which; ++i) {
+       p = strchr(p, ',');
+       if (!p)
+         return -1;
+       ++p;
+    }
+
+    /* Could be looking at something like:
+       black, , 1
+       .. in which case we want to stop on a comma also */
+    while (*p && *p != ',' && !isalpha(*p) && !isdigit(*p))
+      ++p;
+
+    if (*p == ',') {
+       return -1;              /* Use default for empty field */
+    }
+
+    if (which == 2 || isdigit(*p))
+      return atoi(p);
+
+    while (*p && isalpha(*p))
+      *(d++) = *(p++);
+
+    *d = 0;
+
+    for (i=0; i<8; ++i) {
+       if (!StrCaseCmp(buf, cnames[i]))
+         return which? (i+40) : (i+30);
+    }
+    if (!StrCaseCmp(buf, "default")) return -1;
+
+    fprintf(stderr, _("%s: unrecognized color %s\n"), programName, buf);
+    return -2;
+}
+
+static int
+parse_cpair (ColorClass cc, char *str)
+{
+    if ((textColors[(int)cc].fg=parse_color(str, 0)) == -2) {
+       fprintf(stderr, _("%s: can't parse foreground color in `%s'\n"),
+               programName, str);
+       return -1;
+    }
+
+    /* bg and attr are optional */
+    textColors[(int)cc].bg = parse_color(str, 1);
+    if ((textColors[(int)cc].attr = parse_color(str, 2)) < 0) {
+       textColors[(int)cc].attr = 0;
+    }
+    return 0;
+}
+
+void
+ParseIcsTextColors ()
+{   // [HGM] tken out of main(), so it can be called from ICS-Options dialog
+    if (parse_cpair(ColorShout, appData.colorShout) < 0 ||
+       parse_cpair(ColorSShout, appData.colorSShout) < 0 ||
+       parse_cpair(ColorChannel1, appData.colorChannel1) < 0  ||
+       parse_cpair(ColorChannel, appData.colorChannel) < 0  ||
+       parse_cpair(ColorKibitz, appData.colorKibitz) < 0 ||
+       parse_cpair(ColorTell, appData.colorTell) < 0 ||
+       parse_cpair(ColorChallenge, appData.colorChallenge) < 0  ||
+       parse_cpair(ColorRequest, appData.colorRequest) < 0  ||
+       parse_cpair(ColorSeek, appData.colorSeek) < 0  ||
+       parse_cpair(ColorNormal, appData.colorNormal) < 0)
+      {
+         if (appData.colorize) {
+             fprintf(stderr,
+                     _("%s: can't parse color names; disabling colorization\n"),
+                     programName);
+         }
+         appData.colorize = FALSE;
+      }
+    textColors[ColorNone].fg = textColors[ColorNone].bg = -1;
+    textColors[ColorNone].attr = 0;
+}
+
+static Boolean noEcho;
+
+void
+EchoOn ()
+{
+    system("stty echo");
+    noEcho = False;
+}
+
+void
+EchoOff ()
+{
+    system("stty -echo");
+    noEcho = True;
+}
+
+char *oldICSInteractionTitle;
+
+void
+ShutDownFrontEnd ()
+{
+    if (appData.icsActive && oldICSInteractionTitle != NULL) {
+        DisplayIcsInteractionTitle(oldICSInteractionTitle);
+    }
+    if (saveSettingsOnExit) SaveSettings(settingsFileName);
+    unlink(gameCopyFilename);
+    unlink(gamePasteFilename);
+    if(noEcho) EchoOn();
+}
+
+void
+RunCommand (char *buf)
+{
+    system(buf);
+}
+
+void
+Colorize (ColorClass cc, int continuation)
+{
+    char buf[MSG_SIZ];
+    int count, outCount, error;
+
+    if (textColors[(int)cc].bg > 0) {
+       if (textColors[(int)cc].fg > 0) {
+         snprintf(buf, MSG_SIZ, "\033[0;%d;%d;%dm", textColors[(int)cc].attr,
+                  textColors[(int)cc].fg, textColors[(int)cc].bg);
+       } else {
+         snprintf(buf, MSG_SIZ, "\033[0;%d;%dm", textColors[(int)cc].attr,
+                  textColors[(int)cc].bg);
+       }
+    } else {
+       if (textColors[(int)cc].fg > 0) {
+         snprintf(buf, MSG_SIZ, "\033[0;%d;%dm", textColors[(int)cc].attr,
+                   textColors[(int)cc].fg);
+       } else {
+         snprintf(buf, MSG_SIZ, "\033[0;%dm", textColors[(int)cc].attr);
+       }
+    }
+    count = strlen(buf);
+    outCount = OutputToProcess(NoProc, buf, count, &error);
+    if (outCount < count) {
+       DisplayFatalError(_("Error writing to display"), error, 1);
+    }
+
+    if (continuation) return;
+    PlaySoundForColor(cc);
+}
+
+char *
+UserName ()
+{
+    return getpwuid(getuid())->pw_name;
+}
+
+char *
+ExpandPathName (char *path)
+{
+    static char static_buf[4*MSG_SIZ];
+    char *d, *s, buf[4*MSG_SIZ];
+    struct passwd *pwd;
+
+    s = path;
+    d = static_buf;
+
+    while (*s && isspace(*s))
+      ++s;
+
+    if (!*s) {
+       *d = 0;
+       return static_buf;
+    }
+
+    if (*s == '~') {
+       if (*(s+1) == '/') {
+         safeStrCpy(d, getpwuid(getuid())->pw_dir, 4*MSG_SIZ );
+         strcat(d, s+1);
+       }
+       else {
+         safeStrCpy(buf, s+1, sizeof(buf)/sizeof(buf[0]) );
+         { char *p; if(p = strchr(buf, '/')) *p = 0; }
+         pwd = getpwnam(buf);
+         if (!pwd)
+           {
+             fprintf(stderr, _("ERROR: Unknown user %s (in path %s)\n"),
+                     buf, path);
+             return NULL;
+           }
+         safeStrCpy(d, pwd->pw_dir, 4*MSG_SIZ );
+         strcat(d, strchr(s+1, '/'));
+       }
+    }
+    else
+      safeStrCpy(d, s, 4*MSG_SIZ );
+
+    return static_buf;
+}
+
+int
+MySearchPath (char *installDir, char *name, char *fullname)
+{ // just append installDir and name. Perhaps ExpandPath should be used here?
+  name = ExpandPathName(name);
+  if(name && name[0] == '/')
+    safeStrCpy(fullname, name, MSG_SIZ );
+  else {
+    sprintf(fullname, "%s%c%s", installDir, '/', name);
+  }
+  return 1;
+}
+
+int
+MyGetFullPathName (char *name, char *fullname)
+{ // should use ExpandPath?
+  name = ExpandPathName(name);
+  safeStrCpy(fullname, name, MSG_SIZ );
+  return 1;
+}
+
+char *
+HostName ()
+{
+    static char host_name[MSG_SIZ];
+
+#if HAVE_GETHOSTNAME
+    gethostname(host_name, MSG_SIZ);
+    return host_name;
+#else  /* not HAVE_GETHOSTNAME */
+# if HAVE_SYSINFO && HAVE_SYS_SYSTEMINFO_H
+    sysinfo(SI_HOSTNAME, host_name, MSG_SIZ);
+    return host_name;
+# else /* not (HAVE_SYSINFO && HAVE_SYS_SYSTEMINFO_H) */
+    return "localhost";
+# endif/* not (HAVE_SYSINFO && HAVE_SYS_SYSTEMINFO_H) */
+#endif /* not HAVE_GETHOSTNAME */
+}
+
+
+int
+StartChildProcess (char *cmdLine, char *dir, ProcRef *pr)
+{
+    char *argv[64], *p;
+    int i, pid;
+    int to_prog[2], from_prog[2];
+    ChildProc *cp;
+    char buf[MSG_SIZ];
+
+    if (appData.debugMode) {
+       fprintf(debugFP, "StartChildProcess (dir=\"%s\") %s\n",dir, cmdLine);
+    }
+
+    /* We do NOT feed the cmdLine to the shell; we just
+       parse it into blank-separated arguments in the
+       most simple-minded way possible.
+       */
+    i = 0;
+    safeStrCpy(buf, cmdLine, sizeof(buf)/sizeof(buf[0]) );
+    p = buf;
+    for (;;) {
+       while(*p == ' ') p++;
+       argv[i++] = p;
+       if(*p == '"' || *p == '\'')
+            p = strchr(++argv[i-1], *p);
+       else p = strchr(p, ' ');
+       if (p == NULL) break;
+       *p++ = NULLCHAR;
+    }
+    argv[i] = NULL;
+
+    SetUpChildIO(to_prog, from_prog);
+
+    if ((pid = fork()) == 0) {
+       /* Child process */
+       // [HGM] PSWBTM: made order resistant against case where fd of created pipe was 0 or 1
+       close(to_prog[1]);     // first close the unused pipe ends
+       close(from_prog[0]);
+       dup2(to_prog[0], 0);   // to_prog was created first, nd is the only one to use 0 or 1
+       dup2(from_prog[1], 1);
+       if(to_prog[0] >= 2) close(to_prog[0]); // if 0 or 1, the dup2 already cosed the original
+       close(from_prog[1]);                   // and closing again loses one of the pipes!
+       if(fileno(stderr) >= 2) // better safe than sorry...
+               dup2(1, fileno(stderr)); /* force stderr to the pipe */
+
+       if (dir[0] != NULLCHAR && chdir(dir) != 0) {
+           perror(dir);
+           exit(1);
+       }
+
+       nice(appData.niceEngines); // [HGM] nice: adjust priority of engine proc
+
+        execvp(argv[0], argv);
+
+       /* If we get here, exec failed */
+       perror(argv[0]);
+       exit(1);
+    }
+
+    /* Parent process */
+    close(to_prog[0]);
+    close(from_prog[1]);
+
+    cp = (ChildProc *) calloc(1, sizeof(ChildProc));
+    cp->kind = CPReal;
+    cp->pid = pid;
+    cp->fdFrom = from_prog[0];
+    cp->fdTo = to_prog[1];
+    *pr = (ProcRef) cp;
+    return 0;
+}
+
+// [HGM] kill: implement the 'hard killing' of AS's Winboard_x
+static RETSIGTYPE
+AlarmCallBack (int n)
+{
+    return;
+}
+
+void
+DestroyChildProcess (ProcRef pr, int signalType)
+{
+    ChildProc *cp = (ChildProc *) pr;
+
+    if (cp->kind != CPReal) return;
+    cp->kind = CPNone;
+    if (signalType == 10) { // [HGM] kill: if it does not terminate in 3 sec, kill
+       signal(SIGALRM, AlarmCallBack);
+       alarm(3);
+       if(wait((int *) 0) == -1) { // process does not terminate on its own accord
+           kill(cp->pid, SIGKILL); // kill it forcefully
+           wait((int *) 0);        // and wait again
+       }
+    } else {
+       if (signalType) {
+           kill(cp->pid, signalType == 9 ? SIGKILL : SIGTERM); // [HGM] kill: use hard kill if so requested
+       }
+       /* Process is exiting either because of the kill or because of
+          a quit command sent by the backend; either way, wait for it to die.
+       */
+       wait((int *) 0);
+    }
+    close(cp->fdFrom);
+    close(cp->fdTo);
+}
+
+void
+InterruptChildProcess (ProcRef pr)
+{
+    ChildProc *cp = (ChildProc *) pr;
+
+    if (cp->kind != CPReal) return;
+    (void) kill(cp->pid, SIGINT); /* stop it thinking */
+}
+
+int
+OpenTelnet (char *host, char *port, ProcRef *pr)
+{
+    char cmdLine[MSG_SIZ];
+
+    if (port[0] == NULLCHAR) {
+      snprintf(cmdLine, sizeof(cmdLine), "%s %s", appData.telnetProgram, host);
+    } else {
+      snprintf(cmdLine, sizeof(cmdLine), "%s %s %s", appData.telnetProgram, host, port);
+    }
+    return StartChildProcess(cmdLine, "", pr);
+}
+
+int
+OpenTCP (char *host, char *port, ProcRef *pr)
+{
+#if OMIT_SOCKETS
+    DisplayFatalError(_("Socket support is not configured in"), 0, 2);
+#else  /* !OMIT_SOCKETS */
+    struct addrinfo hints;
+    struct addrinfo *ais, *ai;
+    int error;
+    int s=0;
+    ChildProc *cp;
+
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_family = AF_UNSPEC;
+    hints.ai_socktype = SOCK_STREAM;
+
+    error = getaddrinfo(host, port, &hints, &ais);
+    if (error != 0) {
+      /* a getaddrinfo error is not an errno, so can't return it */
+      fprintf(debugFP, "getaddrinfo(%s, %s): %s\n",
+             host, port, gai_strerror(error));
+      return ENOENT;
+    }
+     
+    for (ai = ais; ai != NULL; ai = ai->ai_next) {
+      if ((s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) < 0) {
+       error = errno;
+       continue;
+      }
+      if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0) {
+       error = errno;
+       continue;
+      }
+      error = 0;
+      break;
+    }
+    freeaddrinfo(ais);
+
+    if (error != 0) {
+      return error;
+    }
+
+    cp = (ChildProc *) calloc(1, sizeof(ChildProc));
+    cp->kind = CPSock;
+    cp->pid = 0;
+    cp->fdFrom = s;
+    cp->fdTo = s;
+    *pr = (ProcRef) cp;
+#endif /* !OMIT_SOCKETS */
+
+    return 0;
+}
+
+int
+OpenCommPort (char *name, ProcRef *pr)
+{
+    int fd;
+    ChildProc *cp;
+
+    fd = open(name, 2, 0);
+    if (fd < 0) return errno;
+
+    cp = (ChildProc *) calloc(1, sizeof(ChildProc));
+    cp->kind = CPComm;
+    cp->pid = 0;
+    cp->fdFrom = fd;
+    cp->fdTo = fd;
+    *pr = (ProcRef) cp;
+
+    return 0;
+}
+
+int
+OpenLoopback (ProcRef *pr)
+{
+    ChildProc *cp;
+    int to[2], from[2];
+
+    SetUpChildIO(to, from);
+
+    cp = (ChildProc *) calloc(1, sizeof(ChildProc));
+    cp->kind = CPLoop;
+    cp->pid = 0;
+    cp->fdFrom = to[0];                /* note not from[0]; we are doing a loopback */
+    cp->fdTo = to[1];
+    *pr = (ProcRef) cp;
+
+    return 0;
+}
+
+int
+OpenRcmd (char *host, char *user, char *cmd, ProcRef *pr)
+{
+    DisplayFatalError(_("internal rcmd not implemented for Unix"), 0, 1);
+    return -1;
+}
+
+int
+OutputToProcess (ProcRef pr, char *message, int count, int *outError)
+{
+    static int line = 0;
+    ChildProc *cp = (ChildProc *) pr;
+    int outCount;
+
+    if (pr == NoProc)
+    {
+        if (appData.noJoin || !appData.useInternalWrap)
+            outCount = fwrite(message, 1, count, stdout);
+        else
+        {
+            int width = get_term_width();
+            int len = wrap(NULL, message, count, width, &line);
+            char *msg = malloc(len);
+            int dbgchk;
+
+            if (!msg)
+                outCount = fwrite(message, 1, count, stdout);
+            else
+            {
+                dbgchk = wrap(msg, message, count, width, &line);
+                if (dbgchk != len && appData.debugMode)
+                    fprintf(debugFP, "wrap(): dbgchk(%d) != len(%d)\n", dbgchk, len);
+                outCount = fwrite(msg, 1, dbgchk, stdout);
+                free(msg);
+            }
+        }
+    }
+    else
+      outCount = write(cp->fdTo, message, count);
+
+    if (outCount == -1)
+      *outError = errno;
+    else
+      *outError = 0;
+
+    return outCount;
+}
+
+/* Output message to process, with "ms" milliseconds of delay
+   between each character. This is needed when sending the logon
+   script to ICC, which for some reason doesn't like the
+   instantaneous send. */
+int
+OutputToProcessDelayed (ProcRef pr, char *message, int count, int *outError, long msdelay)
+{
+    ChildProc *cp = (ChildProc *) pr;
+    int outCount = 0;
+    int r;
+
+    while (count--) {
+       r = write(cp->fdTo, message++, 1);
+       if (r == -1) {
+           *outError = errno;
+           return outCount;
+       }
+       ++outCount;
+       if (msdelay >= 0)
+         TimeDelay(msdelay);
+    }
+
+    return outCount;
+}
+
+void
+ICSInitScript ()
+{
+  /* try to open the icsLogon script, either in the location given
+   * or in the users HOME directory
+   */
+
+  FILE *f;
+  char buf[MSG_SIZ];
+  char *homedir;
+
+  f = fopen(appData.icsLogon, "r");
+  if (f == NULL)
+    {
+      homedir = getenv("HOME");
+      if (homedir != NULL)
+       {
+         safeStrCpy(buf, homedir, sizeof(buf)/sizeof(buf[0]) );
+         strncat(buf, "/", MSG_SIZ - strlen(buf) - 1);
+         strncat(buf, appData.icsLogon,  MSG_SIZ - strlen(buf) - 1);
+         f = fopen(buf, "r");
+       }
+    }
+
+  if (f != NULL)
+    ProcessICSInitScript(f);
+  else
+    printf("Warning: Couldn't open icsLogon file (checked %s and %s).\n", appData.icsLogon, buf);
+
+  return;
+}
+
+void
+ResetFrontEnd ()
+{
+    CommentPopDown();
+    TagsPopDown();
+    return;
+}
+
+#include <sys/ioctl.h>
+int
+get_term_width ()
+{
+    int fd, default_width;
+
+    fd = STDIN_FILENO;
+    default_width = 79; // this is FICS default anyway...
+
+#if !defined(TIOCGWINSZ) && defined(TIOCGSIZE)
+    struct ttysize win;
+    if (!ioctl(fd, TIOCGSIZE, &win))
+        default_width = win.ts_cols;
+#elif defined(TIOCGWINSZ)
+    struct winsize win;
+    if (!ioctl(fd, TIOCGWINSZ, &win))
+        default_width = win.ws_col;
+#endif
+    return default_width;
+}
+
+void
+update_ics_width ()
+{
+  static int old_width = 0;
+  int new_width = get_term_width();
+
+  if (old_width != new_width)
+    ics_printf("set width %d\n", new_width);
+  old_width = new_width;
+}
+
+void
+NotifyFrontendLogin ()
+{
+    update_ics_width();
+}
+
+