2 * usystem.c -- X-free, but Unix-like code for XBoard front end
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
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.
11 * The following terms apply to Digital Equipment Corporation's copyright
13 * ------------------------------------------------------------------------
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.
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
31 * ------------------------------------------------------------------------
33 * The following terms apply to the enhanced version of XBoard
34 * distributed by the Free Software Foundation:
35 * ------------------------------------------------------------------------
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.
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.
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/. *
50 *------------------------------------------------------------------------
51 ** See the file ChangeLog for a revision history. */
59 #include <sys/types.h>
65 # if HAVE_SYS_SOCKET_H
66 # include <sys/socket.h>
67 # include <netinet/in.h>
69 # else /* not HAVE_SYS_SOCKET_H */
70 # if HAVE_LAN_SOCKET_H
71 # include <lan/socket.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 */
83 #else /* not STDC_HEADERS */
84 extern char *getenv();
87 # else /* not HAVE_STRING_H */
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
100 #if HAVE_SYS_SYSTEMINFO_H
101 # include <sys/systeminfo.h>
102 #endif /* HAVE_SYS_SYSTEMINFO_H */
104 #if TIME_WITH_SYS_TIME
105 # include <sys/time.h>
109 # include <sys/time.h>
120 # include <sys/wait.h>
125 # define NAMLEN(dirent) strlen((dirent)->d_name)
126 # define HAVE_DIR_STRUCT
128 # define dirent direct
129 # define NAMLEN(dirent) (dirent)->d_namlen
131 # include <sys/ndir.h>
132 # define HAVE_DIR_STRUCT
135 # include <sys/dir.h>
136 # define HAVE_DIR_STRUCT
140 # define HAVE_DIR_STRUCT
148 // [HGM] bitmaps: put before incuding the bitmaps / pixmaps, to know how many piece types there are.
151 #include "frontend.h"
163 #define usleep(t) _sleep2(((t)+500)/1000)
167 # define _(s) gettext (s)
168 # define N_(s) gettext_noop (s)
174 static int get_term_width P(());
176 static char *cnames[9] = { "black", "red", "green", "yellow", "blue",
177 "magenta", "cyan", "white" };
178 TextColors textColors[(int)NColorClasses];
180 /* String is: "fg, bg, attr". Which is 0, 1, 2 */
182 parse_color (char *str, int which)
184 char *p, buf[100], *d;
187 if (strlen(str) > 99) /* watch bounds on buf */
192 for (i=0; i<which; ++i) {
199 /* Could be looking at something like:
201 .. in which case we want to stop on a comma also */
202 while (*p && *p != ',' && !isalpha(*p) && !isdigit(*p))
206 return -1; /* Use default for empty field */
209 if (which == 2 || isdigit(*p))
212 while (*p && isalpha(*p))
217 for (i=0; i<8; ++i) {
218 if (!StrCaseCmp(buf, cnames[i]))
219 return which? (i+40) : (i+30);
221 if (!StrCaseCmp(buf, "default")) return -1;
223 fprintf(stderr, _("%s: unrecognized color %s\n"), programName, buf);
228 parse_cpair (ColorClass cc, char *str)
230 if ((textColors[(int)cc].fg=parse_color(str, 0)) == -2) {
231 fprintf(stderr, _("%s: can't parse foreground color in '%s'\n"),
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;
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)
258 if (appData.colorize) {
260 _("%s: can't parse color names; disabling colorization\n"),
263 appData.colorize = FALSE;
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
270 char *oldICSInteractionTitle;
275 if (appData.icsActive && oldICSInteractionTitle != NULL) {
276 DisplayIcsInteractionTitle(oldICSInteractionTitle);
278 if (saveSettingsOnExit) SaveSettings(settingsFileName);
279 unlink(gameCopyFilename);
280 unlink(gamePasteFilename);
285 RunCommand (char *buf)
291 Colorize (ColorClass cc, int continuation)
294 int count, outCount, error;
296 SetTextColor(cnames, textColors[(int)cc].fg - 30, textColors[(int)cc].bg - 40, textColors[(int)cc].attr); // for GTK widget
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);
303 snprintf(buf, MSG_SIZ, "\033[0;%d;%dm", textColors[(int)cc].attr,
304 textColors[(int)cc].bg);
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);
311 snprintf(buf, MSG_SIZ, "\033[0;%dm", textColors[(int)cc].attr);
315 outCount = OutputToProcess(NoProc, buf, count, &error);
316 if (outCount < count) {
317 DisplayFatalError(_("Error writing to display"), error, 1);
320 if (continuation) return;
321 PlaySoundForColor(cc);
327 return getpwuid(getuid())->pw_name;
331 ExpandPathName (char *path)
333 static char static_buf[4*MSG_SIZ];
334 char *d, *s, buf[4*MSG_SIZ];
340 while (*s && isspace(*s))
349 if(s[1] == '~') { // use ~~ for XBoard's private data directory
350 snprintf(d, 4*MSG_SIZ, "%s%s", dataDir, s+2);
353 safeStrCpy(d, getpwuid(getuid())->pw_dir, 4*MSG_SIZ );
357 safeStrCpy(buf, s+1, sizeof(buf)/sizeof(buf[0]) );
358 { char *p; if(p = strchr(buf, '/')) *p = 0; }
362 fprintf(stderr, _("ERROR: Unknown user %s (in path %s)\n"),
366 safeStrCpy(d, pwd->pw_dir, 4*MSG_SIZ );
367 strcat(d, strchr(s+1, '/'));
371 safeStrCpy(d, s, 4*MSG_SIZ );
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 );
383 sprintf(fullname, "%s%c%s", installDir, '/', name);
389 MyGetFullPathName (char *name, char *fullname)
390 { // should use ExpandPath?
391 name = ExpandPathName(name);
392 safeStrCpy(fullname, name, MSG_SIZ );
399 static char host_name[MSG_SIZ];
402 gethostname(host_name, MSG_SIZ);
404 #else /* not HAVE_GETHOSTNAME */
405 # if HAVE_SYSINFO && HAVE_SYS_SYSTEMINFO_H
406 sysinfo(SI_HOSTNAME, host_name, MSG_SIZ);
408 # else /* not (HAVE_SYSINFO && HAVE_SYS_SYSTEMINFO_H) */
410 # endif/* not (HAVE_SYSINFO && HAVE_SYS_SYSTEMINFO_H) */
411 #endif /* not HAVE_GETHOSTNAME */
416 StartChildProcess (char *cmdLine, char *dir, ProcRef *pr)
420 int to_prog[2], from_prog[2];
424 if (appData.debugMode) {
425 fprintf(debugFP, "StartChildProcess (dir=\"%s\") %s\n",dir, cmdLine);
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.
433 safeStrCpy(buf, cmdLine, sizeof(buf)/sizeof(buf[0]) );
436 while(*p == ' ') p++;
438 if(*p == '"' || *p == '\'')
439 p = strchr(++argv[i-1], *p);
440 else p = strchr(p, ' ');
441 if (p == NULL) break;
446 SetUpChildIO(to_prog, from_prog);
448 if ((pid = fork()) == 0) {
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
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 */
460 if (dir[0] != NULLCHAR && chdir(dir) != 0) {
465 nice(appData.niceEngines); // [HGM] nice: adjust priority of engine proc
467 execvp(argv[0], argv);
469 /* If we get here, exec failed */
478 cp = (ChildProc *) calloc(1, sizeof(ChildProc));
481 cp->fdFrom = from_prog[0];
482 cp->fdTo = to_prog[1];
487 // [HGM] kill: implement the 'hard killing' of AS's Winboard_x
491 AlarmCallBack (int n)
493 kill(pid, SIGKILL); // kill forcefully
498 DestroyChildProcess (ProcRef pr, int signalType)
500 ChildProc *cp = (ChildProc *) pr;
502 if (cp->kind != CPReal) return;
504 if (signalType & 1) {
505 kill(cp->pid, signalType == 9 ? SIGKILL : SIGTERM); // [HGM] kill: for 9 hard-kill immediately
507 signal(SIGALRM, AlarmCallBack);
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.
514 alarm(0); // cancel alarm if still pending
520 BufferCommandOutput (char *command, int size)
522 char *res = (char *) calloc(1, size);
528 StartChildProcess(command, ".", (ProcRef) &pr); // run command in daughter process
529 f = fdopen(pr->fdFrom, "r");
530 count = fread(res, 1, size-1, f); // read its output
532 DestroyChildProcess((ProcRef) pr, 9);
535 f = popen(command, "r");
537 count = fread(res, 1, size-1, f); // read its output
540 res[count > 0 ? count : 0] = NULLCHAR;
542 return res; // return buffer with output
546 InterruptChildProcess (ProcRef pr)
548 ChildProc *cp = (ChildProc *) pr;
550 if (cp->kind != CPReal) return;
551 (void) kill(cp->pid, SIGINT); /* stop it thinking */
555 OpenTelnet (char *host, char *port, ProcRef *pr)
557 char cmdLine[MSG_SIZ];
559 if (port[0] == NULLCHAR) {
560 snprintf(cmdLine, sizeof(cmdLine), "%s %s", appData.telnetProgram, host);
562 snprintf(cmdLine, sizeof(cmdLine), "%s %s %s", appData.telnetProgram, host, port);
564 return StartChildProcess(cmdLine, "", pr);
568 OpenTCP (char *host, char *port, ProcRef *pr)
571 DisplayFatalError(_("Socket support is not configured in"), 0, 2);
572 #else /* !OMIT_SOCKETS */
573 struct addrinfo hints;
574 struct addrinfo *ais, *ai;
579 memset(&hints, 0, sizeof(hints));
580 hints.ai_family = AF_UNSPEC;
581 hints.ai_socktype = SOCK_STREAM;
583 error = getaddrinfo(host, port, &hints, &ais);
585 /* a getaddrinfo error is not an errno, so can't return it */
586 fprintf(debugFP, "getaddrinfo(%s, %s): %s\n",
587 host, port, gai_strerror(error));
591 for (ai = ais; ai != NULL; ai = ai->ai_next) {
592 if ((s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) < 0) {
596 if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0) {
609 cp = (ChildProc *) calloc(1, sizeof(ChildProc));
615 #endif /* !OMIT_SOCKETS */
621 OpenCommPort (char *name, ProcRef *pr)
626 fd = open(name, 2, 0);
627 if (fd < 0) return errno;
629 cp = (ChildProc *) calloc(1, sizeof(ChildProc));
640 OpenLoopback (ProcRef *pr)
645 SetUpChildIO(to, from);
647 cp = (ChildProc *) calloc(1, sizeof(ChildProc));
650 cp->fdFrom = to[0]; /* note not from[0]; we are doing a loopback */
658 OpenRcmd (char *host, char *user, char *cmd, ProcRef *pr)
660 DisplayFatalError(_("internal rcmd not implemented for Unix"), 0, 1);
664 Boolean stdoutClosed = FALSE;
667 OutputToProcess (ProcRef pr, char *message, int count, int *outError)
670 ChildProc *cp = (ChildProc *) pr;
671 int outCount = count;
675 if (appData.noJoin || !appData.useInternalWrap) {
676 if(!stdoutClosed) outCount = fwrite(message, 1, count, stdout);
679 int width = get_term_width();
680 int len = wrap(NULL, message, count, width, &line);
681 char *msg = malloc(len);
685 outCount = fwrite(message, 1, count, stdout);
688 dbgchk = wrap(msg, message, count, width, &line);
689 if (dbgchk != len && appData.debugMode)
690 fprintf(debugFP, "wrap(): dbgchk(%d) != len(%d)\n", dbgchk, len);
691 outCount = fwrite(msg, 1, dbgchk, stdout);
695 if(*message != '\033') ConsoleWrite(message, count);
698 outCount = write(cp->fdTo, message, count);
708 /* Output message to process, with "ms" milliseconds of delay
709 between each character. This is needed when sending the logon
710 script to ICC, which for some reason doesn't like the
711 instantaneous send. */
713 OutputToProcessDelayed (ProcRef pr, char *message, int count, int *outError, long msdelay)
715 ChildProc *cp = (ChildProc *) pr;
720 r = write(cp->fdTo, message++, 1);
736 /* try to open the icsLogon script, either in the location given
737 * or in the users HOME directory
744 f = fopen(appData.icsLogon, "r");
747 homedir = getenv("HOME");
750 safeStrCpy(buf, homedir, sizeof(buf)/sizeof(buf[0]) );
751 strncat(buf, "/", MSG_SIZ - strlen(buf) - 1);
752 strncat(buf, appData.icsLogon, MSG_SIZ - strlen(buf) - 1);
758 ProcessICSInitScript(f);
761 printf("Warning: Couldn't open icsLogon file (checked %s and %s).\n", appData.icsLogon, buf);
774 #include <sys/ioctl.h>
778 int fd, default_width;
781 default_width = 79; // this is FICS default anyway...
783 #if !defined(TIOCGWINSZ) && defined(TIOCGSIZE)
785 if (!ioctl(fd, TIOCGSIZE, &win))
786 default_width = win.ts_cols;
787 #elif defined(TIOCGWINSZ)
789 if (!ioctl(fd, TIOCGWINSZ, &win))
790 default_width = win.ws_col;
792 return default_width;
798 static int old_width = 0;
799 int new_width = get_term_width();
801 if (old_width != new_width)
802 ics_printf("set width %d\n", new_width);
803 old_width = new_width;
807 NotifyFrontendLogin ()