Declare some shared global variables in backend.h
[xboard.git] / xboard.c
1 /*
2  * xboard.c -- X front end for XBoard
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 #define HIGHDRAG 1
53
54 #include "config.h"
55
56 #include <stdio.h>
57 #include <ctype.h>
58 #include <signal.h>
59 #include <errno.h>
60 #include <sys/types.h>
61 #include <sys/stat.h>
62 #include <pwd.h>
63 #include <math.h>
64
65 #if !OMIT_SOCKETS
66 # if HAVE_SYS_SOCKET_H
67 #  include <sys/socket.h>
68 #  include <netinet/in.h>
69 #  include <netdb.h>
70 # else /* not HAVE_SYS_SOCKET_H */
71 #  if HAVE_LAN_SOCKET_H
72 #   include <lan/socket.h>
73 #   include <lan/in.h>
74 #   include <lan/netdb.h>
75 #  else /* not HAVE_LAN_SOCKET_H */
76 #   define OMIT_SOCKETS 1
77 #  endif /* not HAVE_LAN_SOCKET_H */
78 # endif /* not HAVE_SYS_SOCKET_H */
79 #endif /* !OMIT_SOCKETS */
80
81 #if STDC_HEADERS
82 # include <stdlib.h>
83 # include <string.h>
84 #else /* not STDC_HEADERS */
85 extern char *getenv();
86 # if HAVE_STRING_H
87 #  include <string.h>
88 # else /* not HAVE_STRING_H */
89 #  include <strings.h>
90 # endif /* not HAVE_STRING_H */
91 #endif /* not STDC_HEADERS */
92
93 #if HAVE_SYS_FCNTL_H
94 # include <sys/fcntl.h>
95 #else /* not HAVE_SYS_FCNTL_H */
96 # if HAVE_FCNTL_H
97 #  include <fcntl.h>
98 # endif /* HAVE_FCNTL_H */
99 #endif /* not HAVE_SYS_FCNTL_H */
100
101 #if HAVE_SYS_SYSTEMINFO_H
102 # include <sys/systeminfo.h>
103 #endif /* HAVE_SYS_SYSTEMINFO_H */
104
105 #if TIME_WITH_SYS_TIME
106 # include <sys/time.h>
107 # include <time.h>
108 #else
109 # if HAVE_SYS_TIME_H
110 #  include <sys/time.h>
111 # else
112 #  include <time.h>
113 # endif
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #if HAVE_SYS_WAIT_H
121 # include <sys/wait.h>
122 #endif
123
124 #if HAVE_DIRENT_H
125 # include <dirent.h>
126 # define NAMLEN(dirent) strlen((dirent)->d_name)
127 # define HAVE_DIR_STRUCT
128 #else
129 # define dirent direct
130 # define NAMLEN(dirent) (dirent)->d_namlen
131 # if HAVE_SYS_NDIR_H
132 #  include <sys/ndir.h>
133 #  define HAVE_DIR_STRUCT
134 # endif
135 # if HAVE_SYS_DIR_H
136 #  include <sys/dir.h>
137 #  define HAVE_DIR_STRUCT
138 # endif
139 # if HAVE_NDIR_H
140 #  include <ndir.h>
141 #  define HAVE_DIR_STRUCT
142 # endif
143 #endif
144
145 #if ENABLE_NLS
146 #include <locale.h>
147 #endif
148
149 #include <X11/Intrinsic.h>
150 #include <X11/StringDefs.h>
151 #include <X11/Shell.h>
152 #include <X11/cursorfont.h>
153 #include <X11/Xatom.h>
154 #include <X11/Xmu/Atoms.h>
155 #if USE_XAW3D
156 #include <X11/Xaw3d/Dialog.h>
157 #include <X11/Xaw3d/Form.h>
158 #include <X11/Xaw3d/List.h>
159 #include <X11/Xaw3d/Label.h>
160 #include <X11/Xaw3d/SimpleMenu.h>
161 #include <X11/Xaw3d/SmeBSB.h>
162 #include <X11/Xaw3d/SmeLine.h>
163 #include <X11/Xaw3d/Box.h>
164 #include <X11/Xaw3d/MenuButton.h>
165 #include <X11/Xaw3d/Text.h>
166 #include <X11/Xaw3d/AsciiText.h>
167 #else
168 #include <X11/Xaw/Dialog.h>
169 #include <X11/Xaw/Form.h>
170 #include <X11/Xaw/List.h>
171 #include <X11/Xaw/Label.h>
172 #include <X11/Xaw/SimpleMenu.h>
173 #include <X11/Xaw/SmeBSB.h>
174 #include <X11/Xaw/SmeLine.h>
175 #include <X11/Xaw/Box.h>
176 #include <X11/Xaw/MenuButton.h>
177 #include <X11/Xaw/Text.h>
178 #include <X11/Xaw/AsciiText.h>
179 #endif
180
181 // [HGM] bitmaps: put before incuding the bitmaps / pixmaps, to know how many piece types there are.
182 #include "common.h"
183
184 #if HAVE_LIBXPM
185 #include <X11/xpm.h>
186 #include "pixmaps/pixmaps.h"
187 #define IMAGE_EXT "xpm"
188 #else
189 #define IMAGE_EXT "xim"
190 #include "bitmaps/bitmaps.h"
191 #endif
192
193 #include "bitmaps/icon_white.bm"
194 #include "bitmaps/icon_black.bm"
195 #include "bitmaps/checkmark.bm"
196
197 #include "frontend.h"
198 #include "backend.h"
199 #include "backendz.h"
200 #include "moves.h"
201 #include "xboard.h"
202 #include "childio.h"
203 #include "xgamelist.h"
204 #include "xhistory.h"
205 #include "xedittags.h"
206 #include "menus.h"
207 #include "board.h"
208 #include "gettext.h"
209
210
211 #ifdef __EMX__
212 #ifndef HAVE_USLEEP
213 #define HAVE_USLEEP
214 #endif
215 #define usleep(t)   _sleep2(((t)+500)/1000)
216 #endif
217
218 #ifdef ENABLE_NLS
219 # define  _(s) gettext (s)
220 # define N_(s) gettext_noop (s)
221 #else
222 # define  _(s) (s)
223 # define N_(s)  s
224 #endif
225
226 int main P((int argc, char **argv));
227 FILE * XsraSelFile P((Widget w, char *prompt, char *ok, char *cancel, char *failed,
228                 char *init_path, char *filter, char *mode, int (*show_entry)(), char **name_return));
229 RETSIGTYPE CmailSigHandler P((int sig));
230 RETSIGTYPE IntSigHandler P((int sig));
231 RETSIGTYPE TermSizeSigHandler P((int sig));
232 void CreateGCs P((int redo));
233 void CreateAnyPieces P((void));
234 void CreateXIMPieces P((void));
235 void CreateXPMPieces P((void));
236 void CreateXPMBoard P((char *s, int n));
237 void CreatePieces P((void));
238 void CreatePieceMenus P((void));
239 Widget CreateMenuBar P((Menu *mb, int boardWidth));
240 Widget CreateButtonBar P ((MenuItem *mi));
241 #if ENABLE_NLS
242 char *InsertPxlSize P((char *pattern, int targetPxlSize));
243 XFontSet CreateFontSet P((char *base_fnt_lst));
244 #else
245 char *FindFont P((char *pattern, int targetPxlSize));
246 #endif
247 void PieceMenuPopup P((Widget w, XEvent *event,
248                        String *params, Cardinal *num_params));
249 static void PieceMenuSelect P((Widget w, ChessSquare piece, caddr_t junk));
250 static void DropMenuSelect P((Widget w, ChessSquare piece, caddr_t junk));
251 void ReadBitmap P((Pixmap *pm, String name, unsigned char bits[],
252                    u_int wreq, u_int hreq));
253 void CreateGrid P((void));
254 int EventToSquare P((int x, int limit));
255 void EventProc P((Widget widget, caddr_t unused, XEvent *event));
256 void DelayedDrag P((void));
257 void MoveTypeInProc P((Widget widget, caddr_t unused, XEvent *event));
258 void HandleUserMove P((Widget w, XEvent *event,
259                      String *prms, Cardinal *nprms));
260 void AnimateUserMove P((Widget w, XEvent * event,
261                      String * params, Cardinal * nParams));
262 void HandlePV P((Widget w, XEvent * event,
263                      String * params, Cardinal * nParams));
264 void SelectPV P((Widget w, XEvent * event,
265                      String * params, Cardinal * nParams));
266 void StopPV P((Widget w, XEvent * event,
267                      String * params, Cardinal * nParams));
268 void WhiteClock P((Widget w, XEvent *event,
269                    String *prms, Cardinal *nprms));
270 void BlackClock P((Widget w, XEvent *event,
271                    String *prms, Cardinal *nprms));
272 void DrawPositionProc P((Widget w, XEvent *event,
273                      String *prms, Cardinal *nprms));
274 void XDrawPosition P((Widget w, /*Boolean*/int repaint,
275                      Board board));
276 void CommentClick P((Widget w, XEvent * event,
277                    String * params, Cardinal * nParams));
278 void CommentPopUp P((char *title, char *label));
279 void CommentPopDown P((void));
280 void ICSInputBoxPopUp P((void));
281 void ICSInputBoxPopDown P((void));
282 void FileNamePopUp P((char *label, char *def, char *filter,
283                       FileProc proc, char *openMode));
284 void FileNamePopDown P((void));
285 void FileNameCallback P((Widget w, XtPointer client_data,
286                          XtPointer call_data));
287 void FileNameAction P((Widget w, XEvent *event,
288                        String *prms, Cardinal *nprms));
289 void AskQuestionReplyAction P((Widget w, XEvent *event,
290                           String *prms, Cardinal *nprms));
291 void AskQuestionProc P((Widget w, XEvent *event,
292                           String *prms, Cardinal *nprms));
293 void AskQuestionPopDown P((void));
294 void PromotionPopDown P((void));
295 void PromotionCallback P((Widget w, XtPointer client_data,
296                           XtPointer call_data));
297 void SelectCommand P((Widget w, XtPointer client_data, XtPointer call_data));
298 void KeyBindingProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
299 void QuitWrapper P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
300 void TypeInProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
301 void EnterKeyProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
302 void UpKeyProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
303 void DownKeyProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
304 void TempBackwardProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
305 void TempForwardProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
306 Boolean TempBackwardActive = False;
307 void ManInner P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
308 void DisplayMove P((int moveNumber));
309 void DisplayTitle P((char *title));
310 void ICSInitScript P((void));
311 void ErrorPopUp P((char *title, char *text, int modal));
312 void ErrorPopDown P((void));
313 static char *ExpandPathName P((char *path));
314 void SelectMove P((Widget w, XEvent * event, String * params, Cardinal * nParams));
315 void GameListOptionsPopDown P(());
316 void GenericPopDown P(());
317 void update_ics_width P(());
318 int get_term_width P(());
319 int CopyMemoProc P(());
320 void DrawArrowHighlight P((int fromX, int fromY, int toX,int toY));
321 Boolean IsDrawArrowEnabled P(());
322
323 /*
324 * XBoard depends on Xt R4 or higher
325 */
326 int xtVersion = XtSpecificationRelease;
327
328 int xScreen;
329 Display *xDisplay;
330 Window xBoardWindow;
331 Pixel lightSquareColor, darkSquareColor, whitePieceColor, blackPieceColor,
332   jailSquareColor, highlightSquareColor, premoveHighlightColor;
333 Pixel lowTimeWarningColor;
334 GC lightSquareGC, darkSquareGC, jailSquareGC, lineGC, wdPieceGC, wlPieceGC,
335   bdPieceGC, blPieceGC, wbPieceGC, bwPieceGC, coordGC, highlineGC,
336   wjPieceGC, bjPieceGC, prelineGC, countGC;
337 Pixmap iconPixmap, wIconPixmap, bIconPixmap, xMarkPixmap;
338 Widget shellWidget, layoutWidget, formWidget, boardWidget, messageWidget,
339   whiteTimerWidget, blackTimerWidget, titleWidget, widgetList[16],
340   commentShell, promotionShell, whitePieceMenu, blackPieceMenu, dropMenu,
341   menuBarWidget, buttonBarWidget, editShell, errorShell, analysisShell,
342   ICSInputShell, fileNameShell, askQuestionShell;
343 Widget historyShell, evalGraphShell, gameListShell;
344 XSegment secondSegments[BOARD_RANKS + BOARD_FILES + 2];
345 XSegment gridSegments[BOARD_RANKS + BOARD_FILES + 2];
346 XSegment jailGridSegments[BOARD_RANKS + BOARD_FILES + 6];
347 #if ENABLE_NLS
348 XFontSet fontSet, clockFontSet;
349 #else
350 Font clockFontID;
351 XFontStruct *clockFontStruct;
352 #endif
353 Font coordFontID, countFontID;
354 XFontStruct *coordFontStruct, *countFontStruct;
355 XtAppContext appContext;
356 char *layoutName;
357 char *oldICSInteractionTitle;
358
359 FileProc fileProc;
360 char *fileOpenMode;
361 char installDir[] = "."; // [HGM] UCI: needed for UCI; probably needs run-time initializtion
362
363 Position commentX = -1, commentY = -1;
364 Dimension commentW, commentH;
365 typedef unsigned int BoardSize;
366 BoardSize boardSize;
367 Boolean chessProgram;
368
369 int  minX, minY; // [HGM] placement: volatile limits on upper-left corner
370 int smallLayout = 0, tinyLayout = 0,
371   marginW, marginH, // [HGM] for run-time resizing
372   fromX = -1, fromY = -1, toX, toY, commentUp = False, analysisUp = False,
373   ICSInputBoxUp = False, askQuestionUp = False,
374   filenameUp = False, promotionUp = False, pmFromX = -1, pmFromY = -1,
375   errorUp = False, errorExitStatus = -1, defaultLineGap;
376 Dimension textHeight;
377 Pixel timerForegroundPixel, timerBackgroundPixel;
378 Pixel buttonForegroundPixel, buttonBackgroundPixel;
379 char *chessDir, *programName, *programVersion;
380 Boolean alwaysOnTop = False;
381 char *icsTextMenuString;
382 char *icsNames;
383 char *firstChessProgramNames;
384 char *secondChessProgramNames;
385
386 WindowPlacement wpMain;
387 WindowPlacement wpConsole;
388 WindowPlacement wpComment;
389 WindowPlacement wpMoveHistory;
390 WindowPlacement wpEvalGraph;
391 WindowPlacement wpEngineOutput;
392 WindowPlacement wpGameList;
393 WindowPlacement wpTags;
394
395 extern Widget shells[];
396 extern Boolean shellUp[];
397
398 #define SOLID 0
399 #define OUTLINE 1
400 Pixmap pieceBitmap[2][(int)BlackPawn];
401 Pixmap pieceBitmap2[2][(int)BlackPawn+4];       /* [HGM] pieces */
402 Pixmap xpmPieceBitmap[4][(int)BlackPawn];       /* LL, LD, DL, DD actually used*/
403 Pixmap xpmPieceBitmap2[4][(int)BlackPawn+4];    /* LL, LD, DL, DD set to select from */
404 Pixmap xpmLightSquare, xpmDarkSquare, xpmJailSquare;
405 Pixmap xpmBoardBitmap[2];
406 int useImages, useImageSqs, useTexture, textureW[2], textureH[2];
407 XImage *ximPieceBitmap[4][(int)BlackPawn+4];    /* LL, LD, DL, DD */
408 Pixmap ximMaskPm[(int)BlackPawn];               /* clipmasks, used for XIM pieces */
409 Pixmap ximMaskPm2[(int)BlackPawn+4];            /* clipmasks, used for XIM pieces */
410 XImage *ximLightSquare, *ximDarkSquare;
411 XImage *xim_Cross;
412
413 #define pieceToSolid(piece) &pieceBitmap[SOLID][(piece) % (int)BlackPawn]
414 #define pieceToOutline(piece) &pieceBitmap[OUTLINE][(piece) % (int)BlackPawn]
415
416 #define White(piece) ((int)(piece) < (int)BlackPawn)
417
418 /* Bitmaps for use as masks when drawing XPM pieces.
419    Need one for each black and white piece.             */
420 static Pixmap xpmMask[BlackKing + 1];
421
422 /* This magic number is the number of intermediate frames used
423    in each half of the animation. For short moves it's reduced
424    by 1. The total number of frames will be factor * 2 + 1.  */
425 #define kFactor    4
426
427 SizeDefaults sizeDefaults[] = SIZE_DEFAULTS;
428
429 #define PAUSE_BUTTON "P"
430 MenuItem buttonBar[] = {
431     {"<<", "<<", ToStartEvent},
432     {"<", "<", BackwardEvent},
433     {N_(PAUSE_BUTTON), PAUSE_BUTTON, PauseEvent},
434     {">", ">", ForwardEvent},
435     {">>", ">>", ToEndEvent},
436     {NULL, NULL, NULL}
437 };
438
439 #define PIECE_MENU_SIZE 18
440 String pieceMenuStrings[2][PIECE_MENU_SIZE] = {
441     { N_("White"), "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"),
442       N_("Queen"), N_("King"), "----", N_("Elephant"), N_("Cannon"),
443       N_("Archbishop"), N_("Chancellor"), "----", N_("Promote"), N_("Demote"),
444       N_("Empty square"), N_("Clear board") },
445     { N_("Black"), "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"),
446       N_("Queen"), N_("King"), "----", N_("Elephant"), N_("Cannon"),
447       N_("Archbishop"), N_("Chancellor"), "----", N_("Promote"), N_("Demote"),
448       N_("Empty square"), N_("Clear board") }
449 };
450 /* must be in same order as pieceMenuStrings! */
451 ChessSquare pieceMenuTranslation[2][PIECE_MENU_SIZE] = {
452     { WhitePlay, (ChessSquare) 0, WhitePawn, WhiteKnight, WhiteBishop,
453         WhiteRook, WhiteQueen, WhiteKing, (ChessSquare) 0, WhiteAlfil,
454         WhiteCannon, WhiteAngel, WhiteMarshall, (ChessSquare) 0,
455         PromotePiece, DemotePiece, EmptySquare, ClearBoard },
456     { BlackPlay, (ChessSquare) 0, BlackPawn, BlackKnight, BlackBishop,
457         BlackRook, BlackQueen, BlackKing, (ChessSquare) 0, BlackAlfil,
458         BlackCannon, BlackAngel, BlackMarshall, (ChessSquare) 0,
459         PromotePiece, DemotePiece, EmptySquare, ClearBoard },
460 };
461
462 #define DROP_MENU_SIZE 6
463 String dropMenuStrings[DROP_MENU_SIZE] = {
464     "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"), N_("Queen")
465   };
466 /* must be in same order as dropMenuStrings! */
467 ChessSquare dropMenuTranslation[DROP_MENU_SIZE] = {
468     (ChessSquare) 0, WhitePawn, WhiteKnight, WhiteBishop,
469     WhiteRook, WhiteQueen
470 };
471
472 typedef struct {
473     char piece;
474     char* widget;
475 } DropMenuEnables;
476
477 DropMenuEnables dmEnables[] = {
478     { 'P', "Pawn" },
479     { 'N', "Knight" },
480     { 'B', "Bishop" },
481     { 'R', "Rook" },
482     { 'Q', "Queen" }
483 };
484
485 Arg shellArgs[] = {
486     { XtNwidth, 0 },
487     { XtNheight, 0 },
488     { XtNminWidth, 0 },
489     { XtNminHeight, 0 },
490     { XtNmaxWidth, 0 },
491     { XtNmaxHeight, 0 }
492 };
493
494 Arg layoutArgs[] = {
495     { XtNborderWidth, 0 },
496     { XtNdefaultDistance, 0 },
497 };
498
499 Arg formArgs[] = {
500     { XtNborderWidth, 0 },
501     { XtNresizable, (XtArgVal) True },
502 };
503
504 Arg boardArgs[] = {
505     { XtNborderWidth, 0 },
506     { XtNwidth, 0 },
507     { XtNheight, 0 }
508 };
509
510 Arg titleArgs[] = {
511     { XtNjustify, (XtArgVal) XtJustifyRight },
512     { XtNlabel, (XtArgVal) "..." },
513     { XtNresizable, (XtArgVal) True },
514     { XtNresize, (XtArgVal) False }
515 };
516
517 Arg messageArgs[] = {
518     { XtNjustify, (XtArgVal) XtJustifyLeft },
519     { XtNlabel, (XtArgVal) "..." },
520     { XtNresizable, (XtArgVal) True },
521     { XtNresize, (XtArgVal) False }
522 };
523
524 Arg timerArgs[] = {
525     { XtNborderWidth, 0 },
526     { XtNjustify, (XtArgVal) XtJustifyLeft }
527 };
528
529 XtResource clientResources[] = {
530     { "flashCount", "flashCount", XtRInt, sizeof(int),
531         XtOffset(AppDataPtr, flashCount), XtRImmediate,
532         (XtPointer) FLASH_COUNT  },
533 };
534
535 XrmOptionDescRec shellOptions[] = {
536     { "-flashCount", "flashCount", XrmoptionSepArg, NULL },
537     { "-flash", "flashCount", XrmoptionNoArg, "3" },
538     { "-xflash", "flashCount", XrmoptionNoArg, "0" },
539 };
540
541 XtActionsRec boardActions[] = {
542     { "DrawPosition", DrawPositionProc },
543     { "HandleUserMove", HandleUserMove },
544     { "AnimateUserMove", AnimateUserMove },
545     { "HandlePV", HandlePV },
546     { "SelectPV", SelectPV },
547     { "StopPV", StopPV },
548     { "FileNameAction", FileNameAction },
549     { "AskQuestionProc", AskQuestionProc },
550     { "AskQuestionReplyAction", AskQuestionReplyAction },
551     { "PieceMenuPopup", PieceMenuPopup },
552     { "WhiteClock", WhiteClock },
553     { "BlackClock", BlackClock },
554     { "MenuItem", KeyBindingProc }, // [HGM] generic handler for key bindings
555     { "QuitProc", QuitWrapper },
556     { "ManProc", ManInner },
557     { "TempBackwardProc", TempBackwardProc },
558     { "TempForwardProc", TempForwardProc },
559     { "CommentClick", (XtActionProc) CommentClick },
560     { "CommentPopDown", (XtActionProc) CommentPopDown },
561     { "TagsPopDown", (XtActionProc) TagsPopDown },
562     { "ErrorPopDown", (XtActionProc) ErrorPopDown },
563     { "ICSInputBoxPopDown", (XtActionProc) ICSInputBoxPopDown },
564     { "FileNamePopDown", (XtActionProc) FileNamePopDown },
565     { "AskQuestionPopDown", (XtActionProc) AskQuestionPopDown },
566     { "GameListPopDown", (XtActionProc) GameListPopDown },
567     { "GameListOptionsPopDown", (XtActionProc) GameListOptionsPopDown },
568     { "PromotionPopDown", (XtActionProc) PromotionPopDown },
569     { "EngineOutputPopDown", (XtActionProc) EngineOutputPopDown },
570     { "EvalGraphPopDown", (XtActionProc) EvalGraphPopDown },
571     { "GenericPopDown", (XtActionProc) GenericPopDown },
572     { "CopyMemoProc", (XtActionProc) CopyMemoProc },
573     { "SelectMove", (XtActionProc) SelectMove },
574     { "LoadSelectedProc", LoadSelectedProc },
575     { "SetFilterProc", SetFilterProc },
576     { "TypeInProc", TypeInProc },
577     { "EnterKeyProc", EnterKeyProc },
578     { "UpKeyProc", UpKeyProc },
579     { "DownKeyProc", DownKeyProc },
580 };
581
582 char globalTranslations[] =
583   ":<Key>F9: MenuItem(ResignProc) \n \
584    :Ctrl<Key>n: MenuItem(NewGame) \n \
585    :Meta<Key>V: MenuItem(NewVariant) \n \
586    :Ctrl<Key>o: MenuItem(LoadGame) \n \
587    :Meta<Key>Next: MenuItem(LoadNextGameProc) \n \
588    :Meta<Key>Prior: MenuItem(LoadPrevGameProc) \n \
589    :Ctrl<Key>Down: LoadSelectedProc(3) \n \
590    :Ctrl<Key>Up: LoadSelectedProc(-3) \n \
591    :Ctrl<Key>s: MenuItem(SaveGame) \n \
592    :Ctrl<Key>c: MenuItem(CopyGame) \n \
593    :Ctrl<Key>v: MenuItem(PasteGame) \n \
594    :Ctrl<Key>O: MenuItem(LoadPosition) \n \
595    :Shift<Key>Next: MenuItem(LoadNextPositionProc) \n \
596    :Shift<Key>Prior: MenuItem(LoadPrevPositionProc) \n \
597    :Ctrl<Key>S: MenuItem(SavePosition) \n \
598    :Ctrl<Key>C: MenuItem(CopyPosition) \n \
599    :Ctrl<Key>V: MenuItem(PastePosition) \n \
600    :Ctrl<Key>q: MenuItem(Exit) \n \
601    :Ctrl<Key>w: MenuItem(MachineWhite) \n \
602    :Ctrl<Key>b: MenuItem(MachineBlack) \n \
603    :Ctrl<Key>t: MenuItem(TwoMachines) \n \
604    :Ctrl<Key>a: MenuItem(AnalysisMode) \n \
605    :Ctrl<Key>g: MenuItem(AnalyzeFile) \n \
606    :Ctrl<Key>e: MenuItem(EditGame) \n \
607    :Ctrl<Key>E: MenuItem(EditPosition) \n \
608    :Meta<Key>O: MenuItem(ShowEngineOutput) \n \
609    :Meta<Key>E: MenuItem(ShowEvaluationGraph) \n \
610    :Meta<Key>G: MenuItem(ShowGameList) \n \
611    :Meta<Key>H: MenuItem(ShowMoveHistory) \n \
612    :<Key>Pause: MenuItem(Pause) \n \
613    :<Key>F3: MenuItem(Accept) \n \
614    :<Key>F4: MenuItem(Decline) \n \
615    :<Key>F12: MenuItem(Rematch) \n \
616    :<Key>F5: MenuItem(CallFlag) \n \
617    :<Key>F6: MenuItem(Draw) \n \
618    :<Key>F7: MenuItem(Adjourn) \n \
619    :<Key>F8: MenuItem(Abort) \n \
620    :<Key>F10: MenuItem(StopObserving) \n \
621    :<Key>F11: MenuItem(StopExamining) \n \
622    :Ctrl<Key>d: MenuItem(DebugProc) \n \
623    :Meta Ctrl<Key>F12: MenuItem(DebugProc) \n \
624    :Meta<Key>End: MenuItem(ToEnd) \n \
625    :Meta<Key>Right: MenuItem(Forward) \n \
626    :Meta<Key>Home: MenuItem(ToStart) \n \
627    :Meta<Key>Left: MenuItem(Backward) \n \
628    :<Key>Left: MenuItem(Backward) \n \
629    :<Key>Right: MenuItem(Forward) \n \
630    :<Key>Home: MenuItem(Revert) \n \
631    :<Key>End: MenuItem(TruncateGame) \n \
632    :Ctrl<Key>m: MenuItem(MoveNow) \n \
633    :Ctrl<Key>x: MenuItem(RetractMove) \n \
634    :Meta<Key>J: MenuItem(Adjudications) \n \
635    :Meta<Key>U: MenuItem(CommonEngine) \n \
636    :Meta<Key>T: MenuItem(TimeControl) \n \
637    :Ctrl<Key>P: MenuItem(PonderNextMove) \n "
638 #ifndef OPTIONSDIALOG
639     "\
640    :Ctrl<Key>Q: MenuItem(AlwaysQueenProc) \n \
641    :Ctrl<Key>F: MenuItem(AutoflagProc) \n \
642    :Ctrl<Key>A: MenuItem(AnimateMovingProc) \n \
643    :Ctrl<Key>L: MenuItem(TestLegalityProc) \n \
644    :Ctrl<Key>H: MenuItem(HideThinkingProc) \n "
645 #endif
646    "\
647    :<Key>F1: MenuItem(Manual) \n \
648    :<Key>F2: MenuItem(FlipView) \n \
649    :<KeyDown>Return: TempBackwardProc() \n \
650    :<KeyUp>Return: TempForwardProc() \n";
651
652 char boardTranslations[] =
653    "<Btn1Down>: HandleUserMove(0) \n \
654    Shift<Btn1Up>: HandleUserMove(1) \n \
655    <Btn1Up>: HandleUserMove(0) \n \
656    <Btn1Motion>: AnimateUserMove() \n \
657    <Btn3Motion>: HandlePV() \n \
658    <Btn2Motion>: HandlePV() \n \
659    <Btn3Up>: PieceMenuPopup(menuB) \n \
660    <Btn2Up>: PieceMenuPopup(menuB) \n \
661    Shift<Btn2Down>: XawPositionSimpleMenu(menuB) XawPositionSimpleMenu(menuD)\
662                  PieceMenuPopup(menuB) \n \
663    Any<Btn2Down>: XawPositionSimpleMenu(menuW) XawPositionSimpleMenu(menuD) \
664                  PieceMenuPopup(menuW) \n \
665    Shift<Btn3Down>: XawPositionSimpleMenu(menuW) XawPositionSimpleMenu(menuD)\
666                  PieceMenuPopup(menuW) \n \
667    Any<Btn3Down>: XawPositionSimpleMenu(menuB) XawPositionSimpleMenu(menuD) \
668                  PieceMenuPopup(menuB) \n";
669
670 char whiteTranslations[] =
671    "Shift<BtnDown>: WhiteClock(1)\n \
672    <BtnDown>: WhiteClock(0)\n";
673 char blackTranslations[] =
674    "Shift<BtnDown>: BlackClock(1)\n \
675    <BtnDown>: BlackClock(0)\n";
676
677 char ICSInputTranslations[] =
678     "<Key>Up: UpKeyProc() \n "
679     "<Key>Down: DownKeyProc() \n "
680     "<Key>Return: EnterKeyProc() \n";
681
682 // [HGM] vari: another hideous kludge: call extend-end first so we can be sure select-start works,
683 //             as the widget is destroyed before the up-click can call extend-end
684 char commentTranslations[] = "<Btn3Down>: extend-end() select-start() CommentClick() \n";
685
686 String xboardResources[] = {
687     "*fileName*value.translations: #override\\n <Key>Return: FileNameAction()",
688     "*question*value.translations: #override\\n <Key>Return: AskQuestionReplyAction()",
689     "*errorpopup*translations: #override\\n <Key>Return: ErrorPopDown()",
690     NULL
691   };
692
693
694 /* Max possible square size */
695 #define MAXSQSIZE 256
696
697 static int xpm_avail[MAXSQSIZE];
698
699 #ifdef HAVE_DIR_STRUCT
700
701 /* Extract piece size from filename */
702 static int
703 xpm_getsize (char *name, int len, char *ext)
704 {
705     char *p, *d;
706     char buf[10];
707
708     if (len < 4)
709       return 0;
710
711     if ((p=strchr(name, '.')) == NULL ||
712         StrCaseCmp(p+1, ext) != 0)
713       return 0;
714
715     p = name + 3;
716     d = buf;
717
718     while (*p && isdigit(*p))
719       *(d++) = *(p++);
720
721     *d = 0;
722     return atoi(buf);
723 }
724
725 /* Setup xpm_avail */
726 static int
727 xpm_getavail (char *dirname, char *ext)
728 {
729     DIR *dir;
730     struct dirent *ent;
731     int  i;
732
733     for (i=0; i<MAXSQSIZE; ++i)
734       xpm_avail[i] = 0;
735
736     if (appData.debugMode)
737       fprintf(stderr, "XPM dir:%s:ext:%s:\n", dirname, ext);
738
739     dir = opendir(dirname);
740     if (!dir)
741       {
742           fprintf(stderr, _("%s: Can't access XPM directory %s\n"),
743                   programName, dirname);
744           exit(1);
745       }
746
747     while ((ent=readdir(dir)) != NULL) {
748         i = xpm_getsize(ent->d_name, NAMLEN(ent), ext);
749         if (i > 0 && i < MAXSQSIZE)
750           xpm_avail[i] = 1;
751     }
752
753     closedir(dir);
754
755     return 0;
756 }
757
758 void
759 xpm_print_avail (FILE *fp, char *ext)
760 {
761     int i;
762
763     fprintf(fp, _("Available `%s' sizes:\n"), ext);
764     for (i=1; i<MAXSQSIZE; ++i) {
765         if (xpm_avail[i])
766           printf("%d\n", i);
767     }
768 }
769
770 /* Return XPM piecesize closest to size */
771 int
772 xpm_closest_to (char *dirname, int size, char *ext)
773 {
774     int i;
775     int sm_diff = MAXSQSIZE;
776     int sm_index = 0;
777     int diff;
778
779     xpm_getavail(dirname, ext);
780
781     if (appData.debugMode)
782       xpm_print_avail(stderr, ext);
783
784     for (i=1; i<MAXSQSIZE; ++i) {
785         if (xpm_avail[i]) {
786             diff = size - i;
787             diff = (diff<0) ? -diff : diff;
788             if (diff < sm_diff) {
789                 sm_diff = diff;
790                 sm_index = i;
791             }
792         }
793     }
794
795     if (!sm_index) {
796         fprintf(stderr, _("Error: No `%s' files!\n"), ext);
797         exit(1);
798     }
799
800     return sm_index;
801 }
802 #else   /* !HAVE_DIR_STRUCT */
803 /* If we are on a system without a DIR struct, we can't
804    read the directory, so we can't collect a list of
805    filenames, etc., so we can't do any size-fitting. */
806 int
807 xpm_closest_to (char *dirname, int size, char *ext)
808 {
809     fprintf(stderr, _("\
810 Warning: No DIR structure found on this system --\n\
811          Unable to autosize for XPM/XIM pieces.\n\
812    Please report this error to %s.\n\
813    Include system type & operating system in message.\n"), PACKAGE_BUGREPORT););
814     return size;
815 }
816 #endif /* HAVE_DIR_STRUCT */
817
818 static char *cnames[9] = { "black", "red", "green", "yellow", "blue",
819                              "magenta", "cyan", "white" };
820 typedef struct {
821     int attr, bg, fg;
822 } TextColors;
823 TextColors textColors[(int)NColorClasses];
824
825 /* String is: "fg, bg, attr". Which is 0, 1, 2 */
826 static int
827 parse_color (char *str, int which)
828 {
829     char *p, buf[100], *d;
830     int i;
831
832     if (strlen(str) > 99)       /* watch bounds on buf */
833       return -1;
834
835     p = str;
836     d = buf;
837     for (i=0; i<which; ++i) {
838         p = strchr(p, ',');
839         if (!p)
840           return -1;
841         ++p;
842     }
843
844     /* Could be looking at something like:
845        black, , 1
846        .. in which case we want to stop on a comma also */
847     while (*p && *p != ',' && !isalpha(*p) && !isdigit(*p))
848       ++p;
849
850     if (*p == ',') {
851         return -1;              /* Use default for empty field */
852     }
853
854     if (which == 2 || isdigit(*p))
855       return atoi(p);
856
857     while (*p && isalpha(*p))
858       *(d++) = *(p++);
859
860     *d = 0;
861
862     for (i=0; i<8; ++i) {
863         if (!StrCaseCmp(buf, cnames[i]))
864           return which? (i+40) : (i+30);
865     }
866     if (!StrCaseCmp(buf, "default")) return -1;
867
868     fprintf(stderr, _("%s: unrecognized color %s\n"), programName, buf);
869     return -2;
870 }
871
872 static int
873 parse_cpair (ColorClass cc, char *str)
874 {
875     if ((textColors[(int)cc].fg=parse_color(str, 0)) == -2) {
876         fprintf(stderr, _("%s: can't parse foreground color in `%s'\n"),
877                 programName, str);
878         return -1;
879     }
880
881     /* bg and attr are optional */
882     textColors[(int)cc].bg = parse_color(str, 1);
883     if ((textColors[(int)cc].attr = parse_color(str, 2)) < 0) {
884         textColors[(int)cc].attr = 0;
885     }
886     return 0;
887 }
888
889
890 /* Arrange to catch delete-window events */
891 Atom wm_delete_window;
892 void
893 CatchDeleteWindow (Widget w, String procname)
894 {
895   char buf[MSG_SIZ];
896   XSetWMProtocols(xDisplay, XtWindow(w), &wm_delete_window, 1);
897   snprintf(buf, sizeof(buf), "<Message>WM_PROTOCOLS: %s() \n", procname);
898   XtAugmentTranslations(w, XtParseTranslationTable(buf));
899 }
900
901 void
902 BoardToTop ()
903 {
904   Arg args[16];
905   XtSetArg(args[0], XtNiconic, False);
906   XtSetValues(shellWidget, args, 1);
907
908   XtPopup(shellWidget, XtGrabNone); /* Raise if lowered  */
909 }
910
911 //---------------------------------------------------------------------------------------------------------
912 // some symbol definitions to provide the proper (= XBoard) context for the code in args.h
913 #define XBOARD True
914 #define JAWS_ARGS
915 #define CW_USEDEFAULT (1<<31)
916 #define ICS_TEXT_MENU_SIZE 90
917 #define DEBUG_FILE "xboard.debug"
918 #define SetCurrentDirectory chdir
919 #define GetCurrentDirectory(SIZE, NAME) getcwd(NAME, SIZE)
920 #define OPTCHAR "-"
921 #define SEPCHAR " "
922
923 // these two must some day move to frontend.h, when they are implemented
924 Boolean GameListIsUp();
925
926 // The option definition and parsing code common to XBoard and WinBoard is collected in this file
927 #include "args.h"
928
929 // front-end part of option handling
930
931 // [HGM] This platform-dependent table provides the location for storing the color info
932 extern char *crWhite, * crBlack;
933
934 void *
935 colorVariable[] = {
936   &appData.whitePieceColor,
937   &appData.blackPieceColor,
938   &appData.lightSquareColor,
939   &appData.darkSquareColor,
940   &appData.highlightSquareColor,
941   &appData.premoveHighlightColor,
942   &appData.lowTimeWarningColor,
943   NULL,
944   NULL,
945   NULL,
946   NULL,
947   NULL,
948   &crWhite,
949   &crBlack,
950   NULL
951 };
952
953 // [HGM] font: keep a font for each square size, even non-stndard ones
954 #define NUM_SIZES 18
955 #define MAX_SIZE 130
956 Boolean fontIsSet[NUM_FONTS], fontValid[NUM_FONTS][MAX_SIZE];
957 char *fontTable[NUM_FONTS][MAX_SIZE];
958
959 void
960 ParseFont (char *name, int number)
961 { // in XBoard, only 2 of the fonts are currently implemented, and we just copy their name
962   int size;
963   if(sscanf(name, "size%d:", &size)) {
964     // [HGM] font: font is meant for specific boardSize (likely from settings file);
965     //       defer processing it until we know if it matches our board size
966     if(size >= 0 && size<MAX_SIZE) { // for now, fixed limit
967         fontTable[number][size] = strdup(strchr(name, ':')+1);
968         fontValid[number][size] = True;
969     }
970     return;
971   }
972   switch(number) {
973     case 0: // CLOCK_FONT
974         appData.clockFont = strdup(name);
975       break;
976     case 1: // MESSAGE_FONT
977         appData.font = strdup(name);
978       break;
979     case 2: // COORD_FONT
980         appData.coordFont = strdup(name);
981       break;
982     default:
983       return;
984   }
985   fontIsSet[number] = True; // [HGM] font: indicate a font was specified (not from settings file)
986 }
987
988 void
989 SetFontDefaults ()
990 { // only 2 fonts currently
991   appData.clockFont = CLOCK_FONT_NAME;
992   appData.coordFont = COORD_FONT_NAME;
993   appData.font  =   DEFAULT_FONT_NAME;
994 }
995
996 void
997 CreateFonts ()
998 { // no-op, until we identify the code for this already in XBoard and move it here
999 }
1000
1001 void
1002 ParseColor (int n, char *name)
1003 { // in XBoard, just copy the color-name string
1004   if(colorVariable[n]) *(char**)colorVariable[n] = strdup(name);
1005 }
1006
1007 void
1008 ParseTextAttribs (ColorClass cc, char *s)
1009 {
1010     (&appData.colorShout)[cc] = strdup(s);
1011 }
1012
1013 void
1014 ParseBoardSize (void *addr, char *name)
1015 {
1016     appData.boardSize = strdup(name);
1017 }
1018
1019 void
1020 LoadAllSounds ()
1021 { // In XBoard the sound-playing program takes care of obtaining the actual sound
1022 }
1023
1024 void
1025 SetCommPortDefaults ()
1026 { // for now, this is a no-op, as the corresponding option does not exist in XBoard
1027 }
1028
1029 // [HGM] args: these three cases taken out to stay in front-end
1030 void
1031 SaveFontArg (FILE *f, ArgDescriptor *ad)
1032 {
1033   char *name;
1034   int i, n = (int)(intptr_t)ad->argLoc;
1035   switch(n) {
1036     case 0: // CLOCK_FONT
1037         name = appData.clockFont;
1038       break;
1039     case 1: // MESSAGE_FONT
1040         name = appData.font;
1041       break;
1042     case 2: // COORD_FONT
1043         name = appData.coordFont;
1044       break;
1045     default:
1046       return;
1047   }
1048   for(i=0; i<NUM_SIZES; i++) // [HGM] font: current font becomes standard for current size
1049     if(sizeDefaults[i].squareSize == squareSize) { // only for standard sizes!
1050         fontTable[n][squareSize] = strdup(name);
1051         fontValid[n][squareSize] = True;
1052         break;
1053   }
1054   for(i=0; i<MAX_SIZE; i++) if(fontValid[n][i]) // [HGM] font: store all standard fonts
1055     fprintf(f, OPTCHAR "%s" SEPCHAR "\"size%d:%s\"\n", ad->argName, i, fontTable[n][i]);
1056 }
1057
1058 void
1059 ExportSounds ()
1060 { // nothing to do, as the sounds are at all times represented by their text-string names already
1061 }
1062
1063 void
1064 SaveAttribsArg (FILE *f, ArgDescriptor *ad)
1065 {       // here the "argLoc" defines a table index. It could have contained the 'ta' pointer itself, though
1066         fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, (&appData.colorShout)[(int)(intptr_t)ad->argLoc]);
1067 }
1068
1069 void
1070 SaveColor (FILE *f, ArgDescriptor *ad)
1071 {       // in WinBoard the color is an int and has to be converted to text. In X it would be a string already?
1072         if(colorVariable[(int)(intptr_t)ad->argLoc])
1073         fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, *(char**)colorVariable[(int)(intptr_t)ad->argLoc]);
1074 }
1075
1076 void
1077 SaveBoardSize (FILE *f, char *name, void *addr)
1078 { // wrapper to shield back-end from BoardSize & sizeInfo
1079   fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", name, appData.boardSize);
1080 }
1081
1082 void
1083 ParseCommPortSettings (char *s)
1084 { // no such option in XBoard (yet)
1085 }
1086
1087 extern Widget engineOutputShell;
1088 int frameX, frameY;
1089
1090 void
1091 GetActualPlacement (Widget wg, WindowPlacement *wp)
1092 {
1093   Arg args[16];
1094   Dimension w, h;
1095   Position x, y;
1096   XWindowAttributes winAt;
1097   Window win, dummy;
1098   int i, rx, ry;
1099
1100   if(!wg) return;
1101
1102     win = XtWindow(wg);
1103     XGetWindowAttributes(xDisplay, win, &winAt); // this works, where XtGetValues on XtNx, XtNy does not!
1104     XTranslateCoordinates (xDisplay, win, winAt.root, -winAt.border_width, -winAt.border_width, &rx, &ry, &dummy);
1105     wp->x = rx - winAt.x;
1106     wp->y = ry - winAt.y;
1107     wp->height = winAt.height;
1108     wp->width = winAt.width;
1109     frameX = winAt.x; frameY = winAt.y; // remember to decide if windows touch
1110 }
1111
1112 void
1113 GetWindowCoords ()
1114 { // wrapper to shield use of window handles from back-end (make addressible by number?)
1115   // In XBoard this will have to wait until awareness of window parameters is implemented
1116   GetActualPlacement(shellWidget, &wpMain);
1117   if(EngineOutputIsUp()) GetActualPlacement(engineOutputShell, &wpEngineOutput);
1118   if(MoveHistoryIsUp()) GetActualPlacement(shells[7], &wpMoveHistory);
1119   if(EvalGraphIsUp()) GetActualPlacement(evalGraphShell, &wpEvalGraph);
1120   if(GameListIsUp()) GetActualPlacement(gameListShell, &wpGameList);
1121   if(shellUp[1]) GetActualPlacement(shells[1], &wpComment);
1122   if(shellUp[2]) GetActualPlacement(shells[2], &wpTags);
1123 }
1124
1125 void
1126 PrintCommPortSettings (FILE *f, char *name)
1127 { // This option does not exist in XBoard
1128 }
1129
1130 int
1131 MySearchPath (char *installDir, char *name, char *fullname)
1132 { // just append installDir and name. Perhaps ExpandPath should be used here?
1133   name = ExpandPathName(name);
1134   if(name && name[0] == '/')
1135     safeStrCpy(fullname, name, MSG_SIZ );
1136   else {
1137     sprintf(fullname, "%s%c%s", installDir, '/', name);
1138   }
1139   return 1;
1140 }
1141
1142 int
1143 MyGetFullPathName (char *name, char *fullname)
1144 { // should use ExpandPath?
1145   name = ExpandPathName(name);
1146   safeStrCpy(fullname, name, MSG_SIZ );
1147   return 1;
1148 }
1149
1150 void
1151 EnsureOnScreen (int *x, int *y, int minX, int minY)
1152 {
1153   return;
1154 }
1155
1156 int
1157 MainWindowUp ()
1158 { // [HGM] args: allows testing if main window is realized from back-end
1159   return xBoardWindow != 0;
1160 }
1161
1162 void
1163 PopUpStartupDialog ()
1164 {  // start menu not implemented in XBoard
1165 }
1166
1167 char *
1168 ConvertToLine (int argc, char **argv)
1169 {
1170   static char line[128*1024], buf[1024];
1171   int i;
1172
1173   line[0] = NULLCHAR;
1174   for(i=1; i<argc; i++)
1175     {
1176       if( (strchr(argv[i], ' ') || strchr(argv[i], '\n') ||strchr(argv[i], '\t') || argv[i][0] == NULLCHAR)
1177           && argv[i][0] != '{' )
1178         snprintf(buf, sizeof(buf)/sizeof(buf[0]), "{%s} ", argv[i]);
1179       else
1180         snprintf(buf, sizeof(buf)/sizeof(buf[0]), "%s ", argv[i]);
1181       strncat(line, buf, 128*1024 - strlen(line) - 1 );
1182     }
1183
1184   line[strlen(line)-1] = NULLCHAR;
1185   return line;
1186 }
1187
1188 //--------------------------------------------------------------------------------------------
1189
1190 #ifdef IDSIZES
1191   // eventually, all layout determining code should go into a subroutine, but until then IDSIZE remains undefined
1192 #else
1193 #define BoardSize int
1194 void
1195 InitDrawingSizes (BoardSize boardSize, int flags)
1196 {   // [HGM] resize is functional now, but for board format changes only (nr of ranks, files)
1197     Dimension timerWidth, boardWidth, boardHeight, w, h, sep, bor, wr, hr;
1198     Arg args[16];
1199     XtGeometryResult gres;
1200     int i;
1201     static Dimension oldWidth, oldHeight;
1202     static VariantClass oldVariant;
1203     static int oldDual = -1, oldMono = -1;
1204
1205     if(!formWidget) return;
1206
1207     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
1208     boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
1209     boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
1210
1211   if(boardWidth != oldWidth || boardHeight != oldHeight || oldDual != twoBoards) { // do resizing stuff only if size actually changed
1212     /*
1213      * Enable shell resizing.
1214      */
1215     shellArgs[0].value = (XtArgVal) &w;
1216     shellArgs[1].value = (XtArgVal) &h;
1217     XtGetValues(shellWidget, shellArgs, 2);
1218
1219     shellArgs[4].value = 3*w; shellArgs[2].value = 10;
1220     shellArgs[5].value = 2*h; shellArgs[3].value = 10;
1221     XtSetValues(shellWidget, &shellArgs[2], 4);
1222
1223     XtSetArg(args[0], XtNdefaultDistance, &sep);
1224     XtGetValues(formWidget, args, 1);
1225
1226     oldWidth = boardWidth; oldHeight = boardHeight; oldDual = twoBoards;
1227     CreateGrid();
1228     hOffset = boardWidth + 10;
1229     for(i=0; i<BOARD_WIDTH+BOARD_HEIGHT+2; i++) { // [HGM] dual: grid for second board
1230         secondSegments[i] = gridSegments[i];
1231         secondSegments[i].x1 += hOffset;
1232         secondSegments[i].x2 += hOffset;
1233     }
1234
1235     XtSetArg(args[0], XtNwidth, boardWidth);
1236     XtSetArg(args[1], XtNheight, boardHeight);
1237     XtSetValues(boardWidget, args, 2);
1238
1239     timerWidth = (boardWidth - sep) / 2;
1240     XtSetArg(args[0], XtNwidth, timerWidth);
1241     XtSetValues(whiteTimerWidget, args, 1);
1242     XtSetValues(blackTimerWidget, args, 1);
1243
1244     XawFormDoLayout(formWidget, False);
1245
1246     if (appData.titleInWindow) {
1247         i = 0;
1248         XtSetArg(args[i], XtNborderWidth, &bor); i++;
1249         XtSetArg(args[i], XtNheight, &h);  i++;
1250         XtGetValues(titleWidget, args, i);
1251         if (smallLayout) {
1252             w = boardWidth - 2*bor;
1253         } else {
1254             XtSetArg(args[0], XtNwidth, &w);
1255             XtGetValues(menuBarWidget, args, 1);
1256             w = boardWidth - w - sep - 2*bor - 2; // WIDTH_FUDGE
1257         }
1258
1259         gres = XtMakeResizeRequest(titleWidget, w, h, &wr, &hr);
1260         if (gres != XtGeometryYes && appData.debugMode) {
1261             fprintf(stderr,
1262                     _("%s: titleWidget geometry error %d %d %d %d %d\n"),
1263                     programName, gres, w, h, wr, hr);
1264         }
1265     }
1266
1267     XawFormDoLayout(formWidget, True);
1268
1269     /*
1270      * Inhibit shell resizing.
1271      */
1272     shellArgs[0].value = w = (XtArgVal) boardWidth + marginW + twoBoards*hOffset; // [HGM] dual
1273     shellArgs[1].value = h = (XtArgVal) boardHeight + marginH;
1274     shellArgs[4].value = shellArgs[2].value = w;
1275     shellArgs[5].value = shellArgs[3].value = h;
1276     XtSetValues(shellWidget, &shellArgs[0], 6);
1277
1278     XSync(xDisplay, False);
1279     DelayedDrag();
1280   }
1281
1282     // [HGM] pieces: tailor piece bitmaps to needs of specific variant
1283     // (only for xpm)
1284
1285   if(gameInfo.variant != oldVariant) { // and only if variant changed
1286
1287     if(useImages) {
1288       for(i=0; i<4; i++) {
1289         int p;
1290         for(p=0; p<=(int)WhiteKing; p++)
1291            xpmPieceBitmap[i][p] = xpmPieceBitmap2[i][p]; // defaults
1292         if(gameInfo.variant == VariantShogi) {
1293            xpmPieceBitmap[i][(int)WhiteCannon] = xpmPieceBitmap2[i][(int)WhiteKing+1];
1294            xpmPieceBitmap[i][(int)WhiteNightrider] = xpmPieceBitmap2[i][(int)WhiteKing+2];
1295            xpmPieceBitmap[i][(int)WhiteSilver] = xpmPieceBitmap2[i][(int)WhiteKing+3];
1296            xpmPieceBitmap[i][(int)WhiteGrasshopper] = xpmPieceBitmap2[i][(int)WhiteKing+4];
1297            xpmPieceBitmap[i][(int)WhiteQueen] = xpmPieceBitmap2[i][(int)WhiteLance];
1298         }
1299 #ifdef GOTHIC
1300         if(gameInfo.variant == VariantGothic) {
1301            xpmPieceBitmap[i][(int)WhiteMarshall] = xpmPieceBitmap2[i][(int)WhiteSilver];
1302         }
1303 #endif
1304         if(gameInfo.variant == VariantSChess && (squareSize == 49 || squareSize == 72)) {
1305            xpmPieceBitmap[i][(int)WhiteAngel]    = xpmPieceBitmap2[i][(int)WhiteFalcon];
1306            xpmPieceBitmap[i][(int)WhiteMarshall] = xpmPieceBitmap2[i][(int)WhiteAlfil];
1307         }
1308 #if !HAVE_LIBXPM
1309         // [HGM] why are thee ximMasks used at all? the ximPieceBitmaps seem to be never used!
1310         for(p=0; p<=(int)WhiteKing; p++)
1311            ximMaskPm[p] = ximMaskPm2[p]; // defaults
1312         if(gameInfo.variant == VariantShogi) {
1313            ximMaskPm[(int)WhiteCannon] = ximMaskPm2[(int)WhiteKing+1];
1314            ximMaskPm[(int)WhiteNightrider] = ximMaskPm2[(int)WhiteKing+2];
1315            ximMaskPm[(int)WhiteSilver] = ximMaskPm2[(int)WhiteKing+3];
1316            ximMaskPm[(int)WhiteGrasshopper] = ximMaskPm2[(int)WhiteKing+4];
1317            ximMaskPm[(int)WhiteQueen] = ximMaskPm2[(int)WhiteLance];
1318         }
1319 #ifdef GOTHIC
1320         if(gameInfo.variant == VariantGothic) {
1321            ximMaskPm[(int)WhiteMarshall] = ximMaskPm2[(int)WhiteSilver];
1322         }
1323 #endif
1324         if(gameInfo.variant == VariantSChess && (squareSize == 49 || squareSize == 72)) {
1325            ximMaskPm[(int)WhiteAngel]    = ximMaskPm2[(int)WhiteFalcon];
1326            ximMaskPm[(int)WhiteMarshall] = ximMaskPm2[(int)WhiteAlfil];
1327         }
1328 #endif
1329       }
1330     } else {
1331       for(i=0; i<2; i++) {
1332         int p;
1333         for(p=0; p<=(int)WhiteKing; p++)
1334            pieceBitmap[i][p] = pieceBitmap2[i][p]; // defaults
1335         if(gameInfo.variant == VariantShogi) {
1336            pieceBitmap[i][(int)WhiteCannon] = pieceBitmap2[i][(int)WhiteKing+1];
1337            pieceBitmap[i][(int)WhiteNightrider] = pieceBitmap2[i][(int)WhiteKing+2];
1338            pieceBitmap[i][(int)WhiteSilver] = pieceBitmap2[i][(int)WhiteKing+3];
1339            pieceBitmap[i][(int)WhiteGrasshopper] = pieceBitmap2[i][(int)WhiteKing+4];
1340            pieceBitmap[i][(int)WhiteQueen] = pieceBitmap2[i][(int)WhiteLance];
1341         }
1342 #ifdef GOTHIC
1343         if(gameInfo.variant == VariantGothic) {
1344            pieceBitmap[i][(int)WhiteMarshall] = pieceBitmap2[i][(int)WhiteSilver];
1345         }
1346 #endif
1347         if(gameInfo.variant == VariantSChess && (squareSize == 49 || squareSize == 72)) {
1348            pieceBitmap[i][(int)WhiteAngel]    = pieceBitmap2[i][(int)WhiteFalcon];
1349            pieceBitmap[i][(int)WhiteMarshall] = pieceBitmap2[i][(int)WhiteAlfil];
1350         }
1351       }
1352     }
1353     oldMono = -10; // kludge to force recreation of animation masks
1354     oldVariant = gameInfo.variant;
1355   }
1356 #if HAVE_LIBXPM
1357   if(appData.monoMode != oldMono)
1358     CreateAnimVars();
1359 #endif
1360   oldMono = appData.monoMode;
1361 }
1362 #endif
1363
1364 void
1365 ParseIcsTextColors ()
1366 {   // [HGM] tken out of main(), so it can be called from ICS-Options dialog
1367     if (parse_cpair(ColorShout, appData.colorShout) < 0 ||
1368         parse_cpair(ColorSShout, appData.colorSShout) < 0 ||
1369         parse_cpair(ColorChannel1, appData.colorChannel1) < 0  ||
1370         parse_cpair(ColorChannel, appData.colorChannel) < 0  ||
1371         parse_cpair(ColorKibitz, appData.colorKibitz) < 0 ||
1372         parse_cpair(ColorTell, appData.colorTell) < 0 ||
1373         parse_cpair(ColorChallenge, appData.colorChallenge) < 0  ||
1374         parse_cpair(ColorRequest, appData.colorRequest) < 0  ||
1375         parse_cpair(ColorSeek, appData.colorSeek) < 0  ||
1376         parse_cpair(ColorNormal, appData.colorNormal) < 0)
1377       {
1378           if (appData.colorize) {
1379               fprintf(stderr,
1380                       _("%s: can't parse color names; disabling colorization\n"),
1381                       programName);
1382           }
1383           appData.colorize = FALSE;
1384       }
1385 }
1386
1387 int
1388 MakeOneColor (char *name, Pixel *color)
1389 {
1390     XrmValue vFrom, vTo;
1391     if (!appData.monoMode) {
1392         vFrom.addr = (caddr_t) name;
1393         vFrom.size = strlen(name);
1394         XtConvert(shellWidget, XtRString, &vFrom, XtRPixel, &vTo);
1395         if (vTo.addr == NULL) {
1396           appData.monoMode = True;
1397           return True;
1398         } else {
1399           *color = *(Pixel *) vTo.addr;
1400         }
1401     }
1402     return False;
1403 }
1404
1405 int
1406 MakeColors ()
1407 {   // [HGM] taken out of main(), so it can be called from BoardOptions dialog
1408     int forceMono = False;
1409
1410     forceMono |= MakeOneColor(appData.lightSquareColor, &lightSquareColor);
1411     forceMono |= MakeOneColor(appData.darkSquareColor, &darkSquareColor);
1412     forceMono |= MakeOneColor(appData.whitePieceColor, &whitePieceColor);
1413     forceMono |= MakeOneColor(appData.blackPieceColor, &blackPieceColor);
1414     forceMono |= MakeOneColor(appData.highlightSquareColor, &highlightSquareColor);
1415     forceMono |= MakeOneColor(appData.premoveHighlightColor, &premoveHighlightColor);
1416
1417     return forceMono;
1418 }
1419
1420 void
1421 CreateAnyPieces ()
1422 {   // [HGM] taken out of main
1423 #if HAVE_LIBXPM
1424     if (appData.monoMode && // [HGM] no sense to go on to certain doom
1425        (appData.bitmapDirectory == NULL || appData.bitmapDirectory[0] == NULLCHAR))
1426             appData.bitmapDirectory = strdup(DEF_BITMAP_DIR);
1427
1428     if (appData.bitmapDirectory[0] != NULLCHAR) {
1429       CreatePieces();
1430     } else {
1431       CreateXPMPieces();
1432       CreateXPMBoard(appData.liteBackTextureFile, 1);
1433       CreateXPMBoard(appData.darkBackTextureFile, 0);
1434     }
1435 #else
1436     CreateXIMPieces();
1437     /* Create regular pieces */
1438     if (!useImages) CreatePieces();
1439 #endif
1440 }
1441
1442 int
1443 main (int argc, char **argv)
1444 {
1445     int i, j, clockFontPxlSize, coordFontPxlSize, fontPxlSize;
1446     XSetWindowAttributes window_attributes;
1447     Arg args[16];
1448     Dimension timerWidth, boardWidth, boardHeight, w, h, sep, bor, wr, hr;
1449     XrmValue vFrom, vTo;
1450     XtGeometryResult gres;
1451     char *p;
1452     XrmDatabase xdb;
1453     int forceMono = False;
1454
1455     srandom(time(0)); // [HGM] book: make random truly random
1456
1457     setbuf(stdout, NULL);
1458     setbuf(stderr, NULL);
1459     debugFP = stderr;
1460
1461     if(argc > 1 && (!strcmp(argv[1], "-v" ) || !strcmp(argv[1], "--version" ))) {
1462         printf("%s version %s\n", PACKAGE_NAME, PACKAGE_VERSION);
1463         exit(0);
1464     }
1465
1466     programName = strrchr(argv[0], '/');
1467     if (programName == NULL)
1468       programName = argv[0];
1469     else
1470       programName++;
1471
1472 #ifdef ENABLE_NLS
1473     XtSetLanguageProc(NULL, NULL, NULL);
1474     bindtextdomain(PACKAGE, LOCALEDIR);
1475     textdomain(PACKAGE);
1476 #endif
1477
1478     shellWidget =
1479       XtAppInitialize(&appContext, "XBoard", shellOptions,
1480                       XtNumber(shellOptions),
1481                       &argc, argv, xboardResources, NULL, 0);
1482     appData.boardSize = "";
1483     InitAppData(ConvertToLine(argc, argv));
1484     p = getenv("HOME");
1485     if (p == NULL) p = "/tmp";
1486     i = strlen(p) + strlen("/.xboardXXXXXx.pgn") + 1;
1487     gameCopyFilename = (char*) malloc(i);
1488     gamePasteFilename = (char*) malloc(i);
1489     snprintf(gameCopyFilename,i, "%s/.xboard%05uc.pgn", p, getpid());
1490     snprintf(gamePasteFilename,i, "%s/.xboard%05up.pgn", p, getpid());
1491
1492     XtGetApplicationResources(shellWidget, (XtPointer) &appData,
1493                               clientResources, XtNumber(clientResources),
1494                               NULL, 0);
1495
1496     { // [HGM] initstring: kludge to fix bad bug. expand '\n' characters in init string and computer string.
1497         static char buf[MSG_SIZ];
1498         EscapeExpand(buf, appData.firstInitString);
1499         appData.firstInitString = strdup(buf);
1500         EscapeExpand(buf, appData.secondInitString);
1501         appData.secondInitString = strdup(buf);
1502         EscapeExpand(buf, appData.firstComputerString);
1503         appData.firstComputerString = strdup(buf);
1504         EscapeExpand(buf, appData.secondComputerString);
1505         appData.secondComputerString = strdup(buf);
1506     }
1507
1508     if ((chessDir = (char *) getenv("CHESSDIR")) == NULL) {
1509         chessDir = ".";
1510     } else {
1511         if (chdir(chessDir) != 0) {
1512             fprintf(stderr, _("%s: can't cd to CHESSDIR: "), programName);
1513             perror(chessDir);
1514             exit(1);
1515         }
1516     }
1517
1518     if (appData.debugMode && appData.nameOfDebugFile && strcmp(appData.nameOfDebugFile, "stderr")) {
1519         /* [DM] debug info to file [HGM] make the filename a command-line option, and allow it to remain stderr */
1520         if ((debugFP = fopen(appData.nameOfDebugFile, "w")) == NULL)  {
1521            printf(_("Failed to open file '%s'\n"), appData.nameOfDebugFile);
1522            exit(errno);
1523         }
1524         setbuf(debugFP, NULL);
1525     }
1526
1527 #if ENABLE_NLS
1528     if (appData.debugMode) {
1529       fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
1530     }
1531 #endif
1532
1533     /* [HGM,HR] make sure board size is acceptable */
1534     if(appData.NrFiles > BOARD_FILES ||
1535        appData.NrRanks > BOARD_RANKS   )
1536          DisplayFatalError(_("Recompile with larger BOARD_RANKS or BOARD_FILES to support this size"), 0, 2);
1537
1538 #if !HIGHDRAG
1539     /* This feature does not work; animation needs a rewrite */
1540     appData.highlightDragging = FALSE;
1541 #endif
1542     InitBackEnd1();
1543
1544     xDisplay = XtDisplay(shellWidget);
1545     xScreen = DefaultScreen(xDisplay);
1546     wm_delete_window = XInternAtom(xDisplay, "WM_DELETE_WINDOW", True);
1547
1548         gameInfo.variant = StringToVariant(appData.variant);
1549         InitPosition(FALSE);
1550
1551 #ifdef IDSIZE
1552     InitDrawingSizes(-1, 0); // [HGM] initsize: make this into a subroutine
1553 #else
1554     if (isdigit(appData.boardSize[0])) {
1555         i = sscanf(appData.boardSize, "%d,%d,%d,%d,%d,%d,%d", &squareSize,
1556                    &lineGap, &clockFontPxlSize, &coordFontPxlSize,
1557                    &fontPxlSize, &smallLayout, &tinyLayout);
1558         if (i == 0) {
1559             fprintf(stderr, _("%s: bad boardSize syntax %s\n"),
1560                     programName, appData.boardSize);
1561             exit(2);
1562         }
1563         if (i < 7) {
1564             /* Find some defaults; use the nearest known size */
1565             SizeDefaults *szd, *nearest;
1566             int distance = 99999;
1567             nearest = szd = sizeDefaults;
1568             while (szd->name != NULL) {
1569                 if (abs(szd->squareSize - squareSize) < distance) {
1570                     nearest = szd;
1571                     distance = abs(szd->squareSize - squareSize);
1572                     if (distance == 0) break;
1573                 }
1574                 szd++;
1575             }
1576             if (i < 2) lineGap = nearest->lineGap;
1577             if (i < 3) clockFontPxlSize = nearest->clockFontPxlSize;
1578             if (i < 4) coordFontPxlSize = nearest->coordFontPxlSize;
1579             if (i < 5) fontPxlSize = nearest->fontPxlSize;
1580             if (i < 6) smallLayout = nearest->smallLayout;
1581             if (i < 7) tinyLayout = nearest->tinyLayout;
1582         }
1583     } else {
1584         SizeDefaults *szd = sizeDefaults;
1585         if (*appData.boardSize == NULLCHAR) {
1586             while (DisplayWidth(xDisplay, xScreen) < szd->minScreenSize ||
1587                    DisplayHeight(xDisplay, xScreen) < szd->minScreenSize) {
1588               szd++;
1589             }
1590             if (szd->name == NULL) szd--;
1591             appData.boardSize = strdup(szd->name); // [HGM] settings: remember name for saving settings
1592         } else {
1593             while (szd->name != NULL &&
1594                    StrCaseCmp(szd->name, appData.boardSize) != 0) szd++;
1595             if (szd->name == NULL) {
1596                 fprintf(stderr, _("%s: unrecognized boardSize name %s\n"),
1597                         programName, appData.boardSize);
1598                 exit(2);
1599             }
1600         }
1601         squareSize = szd->squareSize;
1602         lineGap = szd->lineGap;
1603         clockFontPxlSize = szd->clockFontPxlSize;
1604         coordFontPxlSize = szd->coordFontPxlSize;
1605         fontPxlSize = szd->fontPxlSize;
1606         smallLayout = szd->smallLayout;
1607         tinyLayout = szd->tinyLayout;
1608         // [HGM] font: use defaults from settings file if available and not overruled
1609     }
1610     if(!fontIsSet[CLOCK_FONT] && fontValid[CLOCK_FONT][squareSize])
1611         appData.clockFont = fontTable[CLOCK_FONT][squareSize];
1612     if(!fontIsSet[MESSAGE_FONT] && fontValid[MESSAGE_FONT][squareSize])
1613         appData.font = fontTable[MESSAGE_FONT][squareSize];
1614     if(!fontIsSet[COORD_FONT] && fontValid[COORD_FONT][squareSize])
1615         appData.coordFont = fontTable[COORD_FONT][squareSize];
1616
1617     /* Now, using squareSize as a hint, find a good XPM/XIM set size */
1618     if (strlen(appData.pixmapDirectory) > 0) {
1619         p = ExpandPathName(appData.pixmapDirectory);
1620         if (!p) {
1621             fprintf(stderr, _("Error expanding path name \"%s\"\n"),
1622                    appData.pixmapDirectory);
1623             exit(1);
1624         }
1625         if (appData.debugMode) {
1626           fprintf(stderr, _("\
1627 XBoard square size (hint): %d\n\
1628 %s fulldir:%s:\n"), squareSize, IMAGE_EXT, p);
1629         }
1630         squareSize = xpm_closest_to(p, squareSize, IMAGE_EXT);
1631         if (appData.debugMode) {
1632             fprintf(stderr, _("Closest %s size: %d\n"), IMAGE_EXT, squareSize);
1633         }
1634     }
1635     defaultLineGap = lineGap;
1636     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
1637
1638     /* [HR] height treated separately (hacked) */
1639     boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
1640     boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
1641     if (appData.showJail == 1) {
1642         /* Jail on top and bottom */
1643         XtSetArg(boardArgs[1], XtNwidth, boardWidth);
1644         XtSetArg(boardArgs[2], XtNheight,
1645                  boardHeight + 2*(lineGap + squareSize));
1646     } else if (appData.showJail == 2) {
1647         /* Jail on sides */
1648         XtSetArg(boardArgs[1], XtNwidth,
1649                  boardWidth + 2*(lineGap + squareSize));
1650         XtSetArg(boardArgs[2], XtNheight, boardHeight);
1651     } else {
1652         /* No jail */
1653         XtSetArg(boardArgs[1], XtNwidth, boardWidth);
1654         XtSetArg(boardArgs[2], XtNheight, boardHeight);
1655     }
1656
1657     /*
1658      * Determine what fonts to use.
1659      */
1660 #if ENABLE_NLS
1661     appData.font = InsertPxlSize(appData.font, fontPxlSize);
1662     appData.clockFont = InsertPxlSize(appData.clockFont, clockFontPxlSize);
1663     appData.coordFont = InsertPxlSize(appData.coordFont, coordFontPxlSize);
1664     fontSet = CreateFontSet(appData.font);
1665     clockFontSet = CreateFontSet(appData.clockFont);
1666     {
1667       /* For the coordFont, use the 0th font of the fontset. */
1668       XFontSet coordFontSet = CreateFontSet(appData.coordFont);
1669       XFontStruct **font_struct_list;
1670       XFontSetExtents *fontSize;
1671       char **font_name_list;
1672       XFontsOfFontSet(coordFontSet, &font_struct_list, &font_name_list);
1673       coordFontID = XLoadFont(xDisplay, font_name_list[0]);
1674       coordFontStruct = XQueryFont(xDisplay, coordFontID);
1675       fontSize = XExtentsOfFontSet(fontSet); // [HGM] figure out how much vertical space font takes
1676       textHeight = fontSize->max_logical_extent.height + 5; // add borderWidth
1677     }
1678 #else
1679     appData.font = FindFont(appData.font, fontPxlSize);
1680     appData.clockFont = FindFont(appData.clockFont, clockFontPxlSize);
1681     appData.coordFont = FindFont(appData.coordFont, coordFontPxlSize);
1682     clockFontID = XLoadFont(xDisplay, appData.clockFont);
1683     clockFontStruct = XQueryFont(xDisplay, clockFontID);
1684     coordFontID = XLoadFont(xDisplay, appData.coordFont);
1685     coordFontStruct = XQueryFont(xDisplay, coordFontID);
1686 #endif
1687     countFontID = coordFontID;  // [HGM] holdings
1688     countFontStruct = coordFontStruct;
1689
1690     xdb = XtDatabase(xDisplay);
1691 #if ENABLE_NLS
1692     XrmPutLineResource(&xdb, "*international: True");
1693     vTo.size = sizeof(XFontSet);
1694     vTo.addr = (XtPointer) &fontSet;
1695     XrmPutResource(&xdb, "*fontSet", XtRFontSet, &vTo);
1696 #else
1697     XrmPutStringResource(&xdb, "*font", appData.font);
1698 #endif
1699
1700     /*
1701      * Detect if there are not enough colors available and adapt.
1702      */
1703     if (DefaultDepth(xDisplay, xScreen) <= 2) {
1704       appData.monoMode = True;
1705     }
1706
1707     forceMono = MakeColors();
1708
1709     if (forceMono) {
1710       fprintf(stderr, _("%s: too few colors available; trying monochrome mode\n"),
1711               programName);
1712         appData.monoMode = True;
1713     }
1714
1715     if (appData.lowTimeWarning && !appData.monoMode) {
1716       vFrom.addr = (caddr_t) appData.lowTimeWarningColor;
1717       vFrom.size = strlen(appData.lowTimeWarningColor);
1718       XtConvert(shellWidget, XtRString, &vFrom, XtRPixel, &vTo);
1719       if (vTo.addr == NULL)
1720                 appData.monoMode = True;
1721       else
1722                 lowTimeWarningColor = *(Pixel *) vTo.addr;
1723     }
1724
1725     if (appData.monoMode && appData.debugMode) {
1726         fprintf(stderr, _("white pixel = 0x%lx, black pixel = 0x%lx\n"),
1727                 (unsigned long) XWhitePixel(xDisplay, xScreen),
1728                 (unsigned long) XBlackPixel(xDisplay, xScreen));
1729     }
1730
1731     ParseIcsTextColors();
1732     textColors[ColorNone].fg = textColors[ColorNone].bg = -1;
1733     textColors[ColorNone].attr = 0;
1734
1735     XtAppAddActions(appContext, boardActions, XtNumber(boardActions));
1736
1737     /*
1738      * widget hierarchy
1739      */
1740     if (tinyLayout) {
1741         layoutName = "tinyLayout";
1742     } else if (smallLayout) {
1743         layoutName = "smallLayout";
1744     } else {
1745         layoutName = "normalLayout";
1746     }
1747     /* Outer layoutWidget is there only to provide a name for use in
1748        resources that depend on the layout style */
1749     layoutWidget =
1750       XtCreateManagedWidget(layoutName, formWidgetClass, shellWidget,
1751                             layoutArgs, XtNumber(layoutArgs));
1752     formWidget =
1753       XtCreateManagedWidget("form", formWidgetClass, layoutWidget,
1754                             formArgs, XtNumber(formArgs));
1755     XtSetArg(args[0], XtNdefaultDistance, &sep);
1756     XtGetValues(formWidget, args, 1);
1757
1758     j = 0;
1759     widgetList[j++] = menuBarWidget = CreateMenuBar(menuBar, boardWidth);
1760     XtSetArg(args[0], XtNtop,    XtChainTop);
1761     XtSetArg(args[1], XtNbottom, XtChainTop);
1762     XtSetArg(args[2], XtNright,  XtChainLeft);
1763     XtSetValues(menuBarWidget, args, 3);
1764
1765     widgetList[j++] = whiteTimerWidget =
1766       XtCreateWidget("whiteTime", labelWidgetClass,
1767                      formWidget, timerArgs, XtNumber(timerArgs));
1768 #if ENABLE_NLS
1769     XtSetArg(args[0], XtNfontSet, clockFontSet);
1770 #else
1771     XtSetArg(args[0], XtNfont, clockFontStruct);
1772 #endif
1773     XtSetArg(args[1], XtNtop,    XtChainTop);
1774     XtSetArg(args[2], XtNbottom, XtChainTop);
1775     XtSetValues(whiteTimerWidget, args, 3);
1776
1777     widgetList[j++] = blackTimerWidget =
1778       XtCreateWidget("blackTime", labelWidgetClass,
1779                      formWidget, timerArgs, XtNumber(timerArgs));
1780 #if ENABLE_NLS
1781     XtSetArg(args[0], XtNfontSet, clockFontSet);
1782 #else
1783     XtSetArg(args[0], XtNfont, clockFontStruct);
1784 #endif
1785     XtSetArg(args[1], XtNtop,    XtChainTop);
1786     XtSetArg(args[2], XtNbottom, XtChainTop);
1787     XtSetValues(blackTimerWidget, args, 3);
1788
1789     if (appData.titleInWindow) {
1790         widgetList[j++] = titleWidget =
1791           XtCreateWidget("title", labelWidgetClass, formWidget,
1792                          titleArgs, XtNumber(titleArgs));
1793         XtSetArg(args[0], XtNtop,    XtChainTop);
1794         XtSetArg(args[1], XtNbottom, XtChainTop);
1795         XtSetValues(titleWidget, args, 2);
1796     }
1797
1798     if (appData.showButtonBar) {
1799       widgetList[j++] = buttonBarWidget = CreateButtonBar(buttonBar);
1800       XtSetArg(args[0], XtNleft,  XtChainRight); // [HGM] glue to right window edge
1801       XtSetArg(args[1], XtNright, XtChainRight); //       for good run-time sizing
1802       XtSetArg(args[2], XtNtop,    XtChainTop);
1803       XtSetArg(args[3], XtNbottom, XtChainTop);
1804       XtSetValues(buttonBarWidget, args, 4);
1805     }
1806
1807     widgetList[j++] = messageWidget =
1808       XtCreateWidget("message", labelWidgetClass, formWidget,
1809                      messageArgs, XtNumber(messageArgs));
1810     XtSetArg(args[0], XtNtop,    XtChainTop);
1811     XtSetArg(args[1], XtNbottom, XtChainTop);
1812     XtSetValues(messageWidget, args, 2);
1813
1814     widgetList[j++] = boardWidget =
1815       XtCreateWidget("board", widgetClass, formWidget, boardArgs,
1816                      XtNumber(boardArgs));
1817
1818     XtManageChildren(widgetList, j);
1819
1820     timerWidth = (boardWidth - sep) / 2;
1821     XtSetArg(args[0], XtNwidth, timerWidth);
1822     XtSetValues(whiteTimerWidget, args, 1);
1823     XtSetValues(blackTimerWidget, args, 1);
1824
1825     XtSetArg(args[0], XtNbackground, &timerBackgroundPixel);
1826     XtSetArg(args[1], XtNforeground, &timerForegroundPixel);
1827     XtGetValues(whiteTimerWidget, args, 2);
1828
1829     if (appData.showButtonBar) {
1830       XtSetArg(args[0], XtNbackground, &buttonBackgroundPixel);
1831       XtSetArg(args[1], XtNforeground, &buttonForegroundPixel);
1832       XtGetValues(XtNameToWidget(buttonBarWidget, PAUSE_BUTTON), args, 2);
1833     }
1834
1835     /*
1836      * formWidget uses these constraints but they are stored
1837      * in the children.
1838      */
1839     i = 0;
1840     XtSetArg(args[i], XtNfromHoriz, 0); i++;
1841     XtSetValues(menuBarWidget, args, i);
1842     if (appData.titleInWindow) {
1843         if (smallLayout) {
1844             i = 0;
1845             XtSetArg(args[i], XtNfromVert, menuBarWidget); i++;
1846             XtSetValues(whiteTimerWidget, args, i);
1847             i = 0;
1848             XtSetArg(args[i], XtNfromVert, menuBarWidget); i++;
1849             XtSetArg(args[i], XtNfromHoriz, whiteTimerWidget); i++;
1850             XtSetValues(blackTimerWidget, args, i);
1851             i = 0;
1852             XtSetArg(args[i], XtNfromVert, whiteTimerWidget); i++;
1853             XtSetArg(args[i], XtNjustify, XtJustifyLeft); i++;
1854             XtSetValues(titleWidget, args, i);
1855             i = 0;
1856             XtSetArg(args[i], XtNfromVert, titleWidget); i++;
1857             XtSetArg(args[i], XtNresizable, (XtArgVal) True); i++;
1858             XtSetValues(messageWidget, args, i);
1859             if (appData.showButtonBar) {
1860               i = 0;
1861               XtSetArg(args[i], XtNfromVert, titleWidget); i++;
1862               XtSetArg(args[i], XtNfromHoriz, messageWidget); i++;
1863               XtSetValues(buttonBarWidget, args, i);
1864             }
1865         } else {
1866             i = 0;
1867             XtSetArg(args[i], XtNfromVert, titleWidget); i++;
1868             XtSetValues(whiteTimerWidget, args, i);
1869             i = 0;
1870             XtSetArg(args[i], XtNfromVert, titleWidget); i++;
1871             XtSetArg(args[i], XtNfromHoriz, whiteTimerWidget); i++;
1872             XtSetValues(blackTimerWidget, args, i);
1873             i = 0;
1874             XtSetArg(args[i], XtNfromHoriz, menuBarWidget); i++;
1875             XtSetValues(titleWidget, args, i);
1876             i = 0;
1877             XtSetArg(args[i], XtNfromVert, whiteTimerWidget); i++;
1878             XtSetArg(args[i], XtNresizable, (XtArgVal) True); i++;
1879             XtSetValues(messageWidget, args, i);
1880             if (appData.showButtonBar) {
1881               i = 0;
1882               XtSetArg(args[i], XtNfromVert, whiteTimerWidget); i++;
1883               XtSetArg(args[i], XtNfromHoriz, messageWidget); i++;
1884               XtSetValues(buttonBarWidget, args, i);
1885             }
1886         }
1887     } else {
1888         i = 0;
1889         XtSetArg(args[i], XtNfromVert, menuBarWidget); i++;
1890         XtSetValues(whiteTimerWidget, args, i);
1891         i = 0;
1892         XtSetArg(args[i], XtNfromVert, menuBarWidget); i++;
1893         XtSetArg(args[i], XtNfromHoriz, whiteTimerWidget); i++;
1894         XtSetValues(blackTimerWidget, args, i);
1895         i = 0;
1896         XtSetArg(args[i], XtNfromVert, whiteTimerWidget); i++;
1897         XtSetArg(args[i], XtNresizable, (XtArgVal) True); i++;
1898         XtSetValues(messageWidget, args, i);
1899         if (appData.showButtonBar) {
1900           i = 0;
1901           XtSetArg(args[i], XtNfromVert, whiteTimerWidget); i++;
1902           XtSetArg(args[i], XtNfromHoriz, messageWidget); i++;
1903           XtSetValues(buttonBarWidget, args, i);
1904         }
1905     }
1906     i = 0;
1907     XtSetArg(args[0], XtNfromVert, messageWidget);
1908     XtSetArg(args[1], XtNtop,    XtChainTop);
1909     XtSetArg(args[2], XtNbottom, XtChainBottom);
1910     XtSetArg(args[3], XtNleft,   XtChainLeft);
1911     XtSetArg(args[4], XtNright,  XtChainRight);
1912     XtSetValues(boardWidget, args, 5);
1913
1914     XtRealizeWidget(shellWidget);
1915
1916     if(wpMain.x > 0) {
1917       XtSetArg(args[0], XtNx, wpMain.x);
1918       XtSetArg(args[1], XtNy, wpMain.y);
1919       XtSetValues(shellWidget, args, 2);
1920     }
1921
1922     /*
1923      * Correct the width of the message and title widgets.
1924      * It is not known why some systems need the extra fudge term.
1925      * The value "2" is probably larger than needed.
1926      */
1927     XawFormDoLayout(formWidget, False);
1928
1929 #define WIDTH_FUDGE 2
1930     i = 0;
1931     XtSetArg(args[i], XtNborderWidth, &bor);  i++;
1932     XtSetArg(args[i], XtNheight, &h);  i++;
1933     XtGetValues(messageWidget, args, i);
1934     if (appData.showButtonBar) {
1935       i = 0;
1936       XtSetArg(args[i], XtNwidth, &w);  i++;
1937       XtGetValues(buttonBarWidget, args, i);
1938       w = boardWidth - w - sep - 2*bor - WIDTH_FUDGE;
1939     } else {
1940       w = boardWidth - 2*bor + 1; /*!! +1 compensates for kludge below */
1941     }
1942
1943     gres = XtMakeResizeRequest(messageWidget, w, h, &wr, &hr);
1944     if (gres != XtGeometryYes && appData.debugMode) {
1945       fprintf(stderr, _("%s: messageWidget geometry error %d %d %d %d %d\n"),
1946               programName, gres, w, h, wr, hr);
1947     }
1948
1949     /* !! Horrible hack to work around bug in XFree86 4.0.1 (X11R6.4.3) */
1950     /* The size used for the child widget in layout lags one resize behind
1951        its true size, so we resize a second time, 1 pixel smaller.  Yeech! */
1952     w--;
1953     gres = XtMakeResizeRequest(messageWidget, w, h, &wr, &hr);
1954     if (gres != XtGeometryYes && appData.debugMode) {
1955       fprintf(stderr, _("%s: messageWidget geometry error %d %d %d %d %d\n"),
1956               programName, gres, w, h, wr, hr);
1957     }
1958     /* !! end hack */
1959     if(!textHeight) textHeight = hr; // [HGM] if !NLS textHeight is still undefined, and we grab it from here
1960     XtSetArg(args[0], XtNleft,  XtChainLeft);  // [HGM] glue ends for good run-time sizing
1961     XtSetArg(args[1], XtNright, XtChainRight);
1962     XtSetValues(messageWidget, args, 2);
1963
1964     if (appData.titleInWindow) {
1965         i = 0;
1966         XtSetArg(args[i], XtNborderWidth, &bor); i++;
1967         XtSetArg(args[i], XtNheight, &h);  i++;
1968         XtGetValues(titleWidget, args, i);
1969         if (smallLayout) {
1970             w = boardWidth - 2*bor;
1971         } else {
1972             XtSetArg(args[0], XtNwidth, &w);
1973             XtGetValues(menuBarWidget, args, 1);
1974             w = boardWidth - w - sep - 2*bor - WIDTH_FUDGE;
1975         }
1976
1977         gres = XtMakeResizeRequest(titleWidget, w, h, &wr, &hr);
1978         if (gres != XtGeometryYes && appData.debugMode) {
1979             fprintf(stderr,
1980                     _("%s: titleWidget geometry error %d %d %d %d %d\n"),
1981                     programName, gres, w, h, wr, hr);
1982         }
1983     }
1984     XawFormDoLayout(formWidget, True);
1985
1986     xBoardWindow = XtWindow(boardWidget);
1987
1988     // [HGM] it seems the layout code ends here, but perhaps the color stuff is size independent and would
1989     //       not need to go into InitDrawingSizes().
1990 #endif
1991
1992     /*
1993      * Create X checkmark bitmap and initialize option menu checks.
1994      */
1995     ReadBitmap(&xMarkPixmap, "checkmark.bm",
1996                checkmark_bits, checkmark_width, checkmark_height);
1997     InitMenuMarkers();
1998
1999     /*
2000      * Create an icon.
2001      */
2002     ReadBitmap(&wIconPixmap, "icon_white.bm",
2003                icon_white_bits, icon_white_width, icon_white_height);
2004     ReadBitmap(&bIconPixmap, "icon_black.bm",
2005                icon_black_bits, icon_black_width, icon_black_height);
2006     iconPixmap = wIconPixmap;
2007     i = 0;
2008     XtSetArg(args[i], XtNiconPixmap, iconPixmap);  i++;
2009     XtSetValues(shellWidget, args, i);
2010
2011     /*
2012      * Create a cursor for the board widget.
2013      */
2014     window_attributes.cursor = XCreateFontCursor(xDisplay, XC_hand2);
2015     XChangeWindowAttributes(xDisplay, xBoardWindow,
2016                             CWCursor, &window_attributes);
2017
2018     /*
2019      * Inhibit shell resizing.
2020      */
2021     shellArgs[0].value = (XtArgVal) &w;
2022     shellArgs[1].value = (XtArgVal) &h;
2023     XtGetValues(shellWidget, shellArgs, 2);
2024     shellArgs[4].value = shellArgs[2].value = w;
2025     shellArgs[5].value = shellArgs[3].value = h;
2026     XtSetValues(shellWidget, &shellArgs[2], 4);
2027     marginW =  w - boardWidth; // [HGM] needed to set new shellWidget size when we resize board
2028     marginH =  h - boardHeight;
2029
2030     CatchDeleteWindow(shellWidget, "QuitProc");
2031
2032     CreateGCs(False);
2033     CreateGrid();
2034     CreateAnyPieces();
2035
2036     CreatePieceMenus();
2037
2038     if (appData.animate || appData.animateDragging)
2039       CreateAnimVars();
2040
2041     XtAugmentTranslations(formWidget,
2042                           XtParseTranslationTable(globalTranslations));
2043     XtAugmentTranslations(boardWidget,
2044                           XtParseTranslationTable(boardTranslations));
2045     XtAugmentTranslations(whiteTimerWidget,
2046                           XtParseTranslationTable(whiteTranslations));
2047     XtAugmentTranslations(blackTimerWidget,
2048                           XtParseTranslationTable(blackTranslations));
2049
2050     /* Why is the following needed on some versions of X instead
2051      * of a translation? */
2052     XtAddEventHandler(boardWidget, ExposureMask|PointerMotionMask, False,
2053                       (XtEventHandler) EventProc, NULL);
2054     /* end why */
2055     XtAddEventHandler(formWidget, KeyPressMask, False,
2056                       (XtEventHandler) MoveTypeInProc, NULL);
2057     XtAddEventHandler(shellWidget, StructureNotifyMask, False,
2058                       (XtEventHandler) EventProc, NULL);
2059
2060     /* [AS] Restore layout */
2061     if( wpMoveHistory.visible ) {
2062       HistoryPopUp();
2063     }
2064
2065     if( wpEvalGraph.visible )
2066       {
2067         EvalGraphPopUp();
2068       };
2069
2070     if( wpEngineOutput.visible ) {
2071       EngineOutputPopUp();
2072     }
2073
2074     InitBackEnd2();
2075
2076     if (errorExitStatus == -1) {
2077         if (appData.icsActive) {
2078             /* We now wait until we see "login:" from the ICS before
2079                sending the logon script (problems with timestamp otherwise) */
2080             /*ICSInitScript();*/
2081             if (appData.icsInputBox) ICSInputBoxPopUp();
2082         }
2083
2084     #ifdef SIGWINCH
2085     signal(SIGWINCH, TermSizeSigHandler);
2086     #endif
2087         signal(SIGINT, IntSigHandler);
2088         signal(SIGTERM, IntSigHandler);
2089         if (*appData.cmailGameName != NULLCHAR) {
2090             signal(SIGUSR1, CmailSigHandler);
2091         }
2092     }
2093
2094     gameInfo.boardWidth = 0; // [HGM] pieces: kludge to ensure InitPosition() calls InitDrawingSizes()
2095     InitPosition(TRUE);
2096 //    XtSetKeyboardFocus(shellWidget, formWidget);
2097     XSetInputFocus(xDisplay, XtWindow(formWidget), RevertToPointerRoot, CurrentTime);
2098
2099     XtAppMainLoop(appContext);
2100     if (appData.debugMode) fclose(debugFP); // [DM] debug
2101     return 0;
2102 }
2103
2104 static Boolean noEcho;
2105
2106 void
2107 ShutDownFrontEnd ()
2108 {
2109     if (appData.icsActive && oldICSInteractionTitle != NULL) {
2110         DisplayIcsInteractionTitle(oldICSInteractionTitle);
2111     }
2112     if (saveSettingsOnExit) SaveSettings(settingsFileName);
2113     unlink(gameCopyFilename);
2114     unlink(gamePasteFilename);
2115     if(noEcho) EchoOn();
2116 }
2117
2118 RETSIGTYPE
2119 TermSizeSigHandler (int sig)
2120 {
2121     update_ics_width();
2122 }
2123
2124 RETSIGTYPE
2125 IntSigHandler (int sig)
2126 {
2127     ExitEvent(sig);
2128 }
2129
2130 RETSIGTYPE
2131 CmailSigHandler (int sig)
2132 {
2133     int dummy = 0;
2134     int error;
2135
2136     signal(SIGUSR1, SIG_IGN);   /* suspend handler     */
2137
2138     /* Activate call-back function CmailSigHandlerCallBack()             */
2139     OutputToProcess(cmailPR, (char *)(&dummy), sizeof(int), &error);
2140
2141     signal(SIGUSR1, CmailSigHandler); /* re-activate handler */
2142 }
2143
2144 void
2145 CmailSigHandlerCallBack (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
2146 {
2147     BoardToTop();
2148     ReloadCmailMsgEvent(TRUE);  /* Reload cmail msg  */
2149 }
2150 /**** end signal code ****/
2151
2152
2153 void
2154 ICSInitScript ()
2155 {
2156   /* try to open the icsLogon script, either in the location given
2157    * or in the users HOME directory
2158    */
2159
2160   FILE *f;
2161   char buf[MSG_SIZ];
2162   char *homedir;
2163
2164   f = fopen(appData.icsLogon, "r");
2165   if (f == NULL)
2166     {
2167       homedir = getenv("HOME");
2168       if (homedir != NULL)
2169         {
2170           safeStrCpy(buf, homedir, sizeof(buf)/sizeof(buf[0]) );
2171           strncat(buf, "/", MSG_SIZ - strlen(buf) - 1);
2172           strncat(buf, appData.icsLogon,  MSG_SIZ - strlen(buf) - 1);
2173           f = fopen(buf, "r");
2174         }
2175     }
2176
2177   if (f != NULL)
2178     ProcessICSInitScript(f);
2179   else
2180     printf("Warning: Couldn't open icsLogon file (checked %s and %s).\n", appData.icsLogon, buf);
2181
2182   return;
2183 }
2184
2185 void
2186 ResetFrontEnd ()
2187 {
2188     CommentPopDown();
2189     TagsPopDown();
2190     return;
2191 }
2192
2193 // [HGM] code borrowed from winboard.c (which should thus go to backend.c!)
2194 #define HISTORY_SIZE 64
2195 static char *history[HISTORY_SIZE];
2196 int histIn = 0, histP = 0;
2197
2198 void
2199 SaveInHistory (char *cmd)
2200 {
2201   if (history[histIn] != NULL) {
2202     free(history[histIn]);
2203     history[histIn] = NULL;
2204   }
2205   if (*cmd == NULLCHAR) return;
2206   history[histIn] = StrSave(cmd);
2207   histIn = (histIn + 1) % HISTORY_SIZE;
2208   if (history[histIn] != NULL) {
2209     free(history[histIn]);
2210     history[histIn] = NULL;
2211   }
2212   histP = histIn;
2213 }
2214
2215 char *
2216 PrevInHistory (char *cmd)
2217 {
2218   int newhp;
2219   if (histP == histIn) {
2220     if (history[histIn] != NULL) free(history[histIn]);
2221     history[histIn] = StrSave(cmd);
2222   }
2223   newhp = (histP - 1 + HISTORY_SIZE) % HISTORY_SIZE;
2224   if (newhp == histIn || history[newhp] == NULL) return NULL;
2225   histP = newhp;
2226   return history[histP];
2227 }
2228
2229 char *
2230 NextInHistory ()
2231 {
2232   if (histP == histIn) return NULL;
2233   histP = (histP + 1) % HISTORY_SIZE;
2234   return history[histP];   
2235 }
2236 // end of borrowed code
2237
2238 #define Abs(n) ((n)<0 ? -(n) : (n))
2239
2240 #ifdef ENABLE_NLS
2241 char *
2242 InsertPxlSize (char *pattern, int targetPxlSize)
2243 {
2244     char *base_fnt_lst, strInt[12], *p, *q;
2245     int alternatives, i, len, strIntLen;
2246
2247     /*
2248      * Replace the "*" (if present) in the pixel-size slot of each
2249      * alternative with the targetPxlSize.
2250      */
2251     p = pattern;
2252     alternatives = 1;
2253     while ((p = strchr(p, ',')) != NULL) {
2254       alternatives++;
2255       p++;
2256     }
2257     snprintf(strInt, sizeof(strInt), "%d", targetPxlSize);
2258     strIntLen = strlen(strInt);
2259     base_fnt_lst = calloc(1, strlen(pattern) + strIntLen * alternatives + 1);
2260
2261     p = pattern;
2262     q = base_fnt_lst;
2263     while (alternatives--) {
2264       char *comma = strchr(p, ',');
2265       for (i=0; i<14; i++) {
2266         char *hyphen = strchr(p, '-');
2267         if (!hyphen) break;
2268         if (comma && hyphen > comma) break;
2269         len = hyphen + 1 - p;
2270         if (i == 7 && *p == '*' && len == 2) {
2271           p += len;
2272           memcpy(q, strInt, strIntLen);
2273           q += strIntLen;
2274           *q++ = '-';
2275         } else {
2276           memcpy(q, p, len);
2277           p += len;
2278           q += len;
2279         }
2280       }
2281       if (!comma) break;
2282       len = comma + 1 - p;
2283       memcpy(q, p, len);
2284       p += len;
2285       q += len;
2286     }
2287     strcpy(q, p);
2288
2289     return base_fnt_lst;
2290 }
2291
2292 XFontSet
2293 CreateFontSet (char *base_fnt_lst)
2294 {
2295     XFontSet fntSet;
2296     char **missing_list;
2297     int missing_count;
2298     char *def_string;
2299
2300     fntSet = XCreateFontSet(xDisplay, base_fnt_lst,
2301                             &missing_list, &missing_count, &def_string);
2302     if (appData.debugMode) {
2303       int i, count;
2304       XFontStruct **font_struct_list;
2305       char **font_name_list;
2306       fprintf(debugFP, "Requested font set for list %s\n", base_fnt_lst);
2307       if (fntSet) {
2308         fprintf(debugFP, " got list %s, locale %s\n",
2309                 XBaseFontNameListOfFontSet(fntSet),
2310                 XLocaleOfFontSet(fntSet));
2311         count = XFontsOfFontSet(fntSet, &font_struct_list, &font_name_list);
2312         for (i = 0; i < count; i++) {
2313           fprintf(debugFP, " got charset %s\n", font_name_list[i]);
2314         }
2315       }
2316       for (i = 0; i < missing_count; i++) {
2317         fprintf(debugFP, " missing charset %s\n", missing_list[i]);
2318       }
2319     }
2320     if (fntSet == NULL) {
2321       fprintf(stderr, _("Unable to create font set for %s.\n"), base_fnt_lst);
2322       exit(2);
2323     }
2324     return fntSet;
2325 }
2326 #else // not ENABLE_NLS
2327 /*
2328  * Find a font that matches "pattern" that is as close as
2329  * possible to the targetPxlSize.  Prefer fonts that are k
2330  * pixels smaller to fonts that are k pixels larger.  The
2331  * pattern must be in the X Consortium standard format,
2332  * e.g. "-*-helvetica-bold-r-normal--*-*-*-*-*-*-*-*".
2333  * The return value should be freed with XtFree when no
2334  * longer needed.
2335  */
2336 char *
2337 FindFont (char *pattern, int targetPxlSize)
2338 {
2339     char **fonts, *p, *best, *scalable, *scalableTail;
2340     int i, j, nfonts, minerr, err, pxlSize;
2341
2342     fonts = XListFonts(xDisplay, pattern, 999999, &nfonts);
2343     if (nfonts < 1) {
2344         fprintf(stderr, _("%s: no fonts match pattern %s\n"),
2345                 programName, pattern);
2346         exit(2);
2347     }
2348
2349     best = fonts[0];
2350     scalable = NULL;
2351     minerr = 999999;
2352     for (i=0; i<nfonts; i++) {
2353         j = 0;
2354         p = fonts[i];
2355         if (*p != '-') continue;
2356         while (j < 7) {
2357             if (*p == NULLCHAR) break;
2358             if (*p++ == '-') j++;
2359         }
2360         if (j < 7) continue;
2361         pxlSize = atoi(p);
2362         if (pxlSize == 0) {
2363             scalable = fonts[i];
2364             scalableTail = p;
2365         } else {
2366             err = pxlSize - targetPxlSize;
2367             if (Abs(err) < Abs(minerr) ||
2368                 (minerr > 0 && err < 0 && -err == minerr)) {
2369                 best = fonts[i];
2370                 minerr = err;
2371             }
2372         }
2373     }
2374     if (scalable && Abs(minerr) > appData.fontSizeTolerance) {
2375         /* If the error is too big and there is a scalable font,
2376            use the scalable font. */
2377         int headlen = scalableTail - scalable;
2378         p = (char *) XtMalloc(strlen(scalable) + 10);
2379         while (isdigit(*scalableTail)) scalableTail++;
2380         sprintf(p, "%.*s%d%s", headlen, scalable, targetPxlSize, scalableTail);
2381     } else {
2382         p = (char *) XtMalloc(strlen(best) + 2);
2383         safeStrCpy(p, best, strlen(best)+1 );
2384     }
2385     if (appData.debugMode) {
2386         fprintf(debugFP, _("resolved %s at pixel size %d\n  to %s\n"),
2387                 pattern, targetPxlSize, p);
2388     }
2389     XFreeFontNames(fonts);
2390     return p;
2391 }
2392 #endif
2393
2394 void
2395 DeleteGCs ()
2396 {   // [HGM] deletes GCs that are to be remade, to prevent resource leak;
2397     // must be called before all non-first callse to CreateGCs()
2398     XtReleaseGC(shellWidget, highlineGC);
2399     XtReleaseGC(shellWidget, lightSquareGC);
2400     XtReleaseGC(shellWidget, darkSquareGC);
2401     XtReleaseGC(shellWidget, lineGC);
2402     if (appData.monoMode) {
2403         if (DefaultDepth(xDisplay, xScreen) == 1) {
2404             XtReleaseGC(shellWidget, wbPieceGC);
2405         } else {
2406             XtReleaseGC(shellWidget, bwPieceGC);
2407         }
2408     } else {
2409         XtReleaseGC(shellWidget, prelineGC);
2410         XtReleaseGC(shellWidget, jailSquareGC);
2411         XtReleaseGC(shellWidget, wdPieceGC);
2412         XtReleaseGC(shellWidget, wlPieceGC);
2413         XtReleaseGC(shellWidget, wjPieceGC);
2414         XtReleaseGC(shellWidget, bdPieceGC);
2415         XtReleaseGC(shellWidget, blPieceGC);
2416         XtReleaseGC(shellWidget, bjPieceGC);
2417     }
2418 }
2419
2420 static GC
2421 CreateOneGC (XGCValues *gc_values, Pixel foreground, Pixel background)
2422 {
2423     XtGCMask value_mask = GCLineWidth | GCLineStyle | GCForeground
2424       | GCBackground | GCFunction | GCPlaneMask;
2425     gc_values->foreground = foreground;
2426     gc_values->background = background;
2427     return XtGetGC(shellWidget, value_mask, gc_values);
2428 }
2429
2430 void
2431 CreateGCs (int redo)
2432 {
2433     XtGCMask value_mask = GCLineWidth | GCLineStyle | GCForeground
2434       | GCBackground | GCFunction | GCPlaneMask;
2435     XGCValues gc_values;
2436     GC copyInvertedGC;
2437     Pixel white = XWhitePixel(xDisplay, xScreen);
2438     Pixel black = XBlackPixel(xDisplay, xScreen);
2439
2440     gc_values.plane_mask = AllPlanes;
2441     gc_values.line_width = lineGap;
2442     gc_values.line_style = LineSolid;
2443     gc_values.function = GXcopy;
2444
2445   if(redo) {
2446     DeleteGCs(); // called a second time; clean up old GCs first
2447   } else { // [HGM] grid and font GCs created on first call only
2448     coordGC = CreateOneGC(&gc_values, black, white);
2449     XSetFont(xDisplay, coordGC, coordFontID);
2450
2451     // [HGM] make font for holdings counts (white on black)
2452     countGC = CreateOneGC(&gc_values, white, black);
2453     XSetFont(xDisplay, countGC, countFontID);
2454   }
2455     lineGC = CreateOneGC(&gc_values, black, black);
2456
2457     if (appData.monoMode) {
2458
2459         highlineGC = CreateOneGC(&gc_values, white, white);
2460         lightSquareGC = wbPieceGC = CreateOneGC(&gc_values, white, black);
2461         darkSquareGC = bwPieceGC = CreateOneGC(&gc_values, black, white);
2462
2463         if (DefaultDepth(xDisplay, xScreen) == 1) {
2464             /* Avoid XCopyPlane on 1-bit screens to work around Sun bug */
2465             gc_values.function = GXcopyInverted;
2466             copyInvertedGC = CreateOneGC(&gc_values, black, white);
2467             gc_values.function = GXcopy;
2468             if (XBlackPixel(xDisplay, xScreen) == 1) {
2469                 bwPieceGC = darkSquareGC;
2470                 wbPieceGC = copyInvertedGC;
2471             } else {
2472                 bwPieceGC = copyInvertedGC;
2473                 wbPieceGC = lightSquareGC;
2474             }
2475         }
2476     } else {
2477
2478         highlineGC = CreateOneGC(&gc_values, highlightSquareColor, highlightSquareColor);
2479         prelineGC = CreateOneGC(&gc_values, premoveHighlightColor, premoveHighlightColor);
2480         lightSquareGC = CreateOneGC(&gc_values, lightSquareColor, darkSquareColor);
2481         darkSquareGC = CreateOneGC(&gc_values, darkSquareColor, lightSquareColor);
2482         jailSquareGC = CreateOneGC(&gc_values, jailSquareColor, jailSquareColor);
2483         wdPieceGC = CreateOneGC(&gc_values, whitePieceColor, darkSquareColor);
2484         wlPieceGC = CreateOneGC(&gc_values, whitePieceColor, lightSquareColor);
2485         wjPieceGC = CreateOneGC(&gc_values, whitePieceColor, jailSquareColor);
2486         bdPieceGC = CreateOneGC(&gc_values, blackPieceColor, darkSquareColor);
2487         blPieceGC = CreateOneGC(&gc_values, blackPieceColor, lightSquareColor);
2488         bjPieceGC = CreateOneGC(&gc_values, blackPieceColor, jailSquareColor);
2489     }
2490 }
2491
2492 void
2493 loadXIM (XImage *xim, XImage *xmask, char *filename, Pixmap *dest, Pixmap *mask)
2494 {
2495     int x, y, w, h, p;
2496     FILE *fp;
2497     Pixmap temp;
2498     XGCValues   values;
2499     GC maskGC;
2500
2501     fp = fopen(filename, "rb");
2502     if (!fp) {
2503         fprintf(stderr, _("%s: error loading XIM!\n"), programName);
2504         exit(1);
2505     }
2506
2507     w = fgetc(fp);
2508     h = fgetc(fp);
2509
2510     for (y=0; y<h; ++y) {
2511         for (x=0; x<h; ++x) {
2512             p = fgetc(fp);
2513
2514             switch (p) {
2515               case 0:
2516                 XPutPixel(xim, x, y, blackPieceColor);
2517                 if (xmask)
2518                   XPutPixel(xmask, x, y, WhitePixel(xDisplay,xScreen));
2519                 break;
2520               case 1:
2521                 XPutPixel(xim, x, y, darkSquareColor);
2522                 if (xmask)
2523                   XPutPixel(xmask, x, y, BlackPixel(xDisplay,xScreen));
2524                 break;
2525               case 2:
2526                 XPutPixel(xim, x, y, whitePieceColor);
2527                 if (xmask)
2528                   XPutPixel(xmask, x, y, WhitePixel(xDisplay,xScreen));
2529                 break;
2530               case 3:
2531                 XPutPixel(xim, x, y, lightSquareColor);
2532                 if (xmask)
2533                   XPutPixel(xmask, x, y, BlackPixel(xDisplay,xScreen));
2534                 break;
2535             }
2536         }
2537     }
2538
2539     fclose(fp);
2540
2541     /* create Pixmap of piece */
2542     *dest = XCreatePixmap(xDisplay, DefaultRootWindow(xDisplay),
2543                           w, h, xim->depth);
2544     XPutImage(xDisplay, *dest, lightSquareGC, xim,
2545               0, 0, 0, 0, w, h);
2546
2547     /* create Pixmap of clipmask
2548        Note: We assume the white/black pieces have the same
2549              outline, so we make only 6 masks. This is okay
2550              since the XPM clipmask routines do the same. */
2551     if (xmask) {
2552       temp = XCreatePixmap(xDisplay, DefaultRootWindow(xDisplay),
2553                             w, h, xim->depth);
2554       XPutImage(xDisplay, temp, lightSquareGC, xmask,
2555               0, 0, 0, 0, w, h);
2556
2557       /* now create the 1-bit version */
2558       *mask = XCreatePixmap(xDisplay, DefaultRootWindow(xDisplay),
2559                           w, h, 1);
2560
2561       values.foreground = 1;
2562       values.background = 0;
2563
2564       /* Don't use XtGetGC, not read only */
2565       maskGC = XCreateGC(xDisplay, *mask,
2566                     GCForeground | GCBackground, &values);
2567       XCopyPlane(xDisplay, temp, *mask, maskGC,
2568                   0, 0, squareSize, squareSize, 0, 0, 1);
2569       XFreePixmap(xDisplay, temp);
2570     }
2571 }
2572
2573
2574 char pieceBitmapNames[] = "pnbrqfeacwmohijgdvlsukpnsl";
2575
2576 void
2577 CreateXIMPieces ()
2578 {
2579     int piece, kind;
2580     char buf[MSG_SIZ];
2581     u_int ss;
2582     static char *ximkind[] = { "ll", "ld", "dl", "dd" };
2583     XImage *ximtemp;
2584
2585     ss = squareSize;
2586
2587     /* The XSynchronize calls were copied from CreatePieces.
2588        Not sure if needed, but can't hurt */
2589     XSynchronize(xDisplay, True); /* Work-around for xlib/xt
2590                                      buffering bug */
2591
2592     /* temp needed by loadXIM() */
2593     ximtemp = XGetImage(xDisplay, DefaultRootWindow(xDisplay),
2594                  0, 0, ss, ss, AllPlanes, XYPixmap);
2595
2596     if (strlen(appData.pixmapDirectory) == 0) {
2597       useImages = 0;
2598     } else {
2599         useImages = 1;
2600         if (appData.monoMode) {
2601           DisplayFatalError(_("XIM pieces cannot be used in monochrome mode"),
2602                             0, 2);
2603           ExitEvent(2);
2604         }
2605         fprintf(stderr, _("\nLoading XIMs...\n"));
2606         /* Load pieces */
2607         for (piece = (int) WhitePawn; piece <= (int) WhiteKing + 4; piece++) {
2608             fprintf(stderr, "%d", piece+1);
2609             for (kind=0; kind<4; kind++) {
2610                 fprintf(stderr, ".");
2611                 snprintf(buf, sizeof(buf), "%s/%s%c%s%u.xim",
2612                         ExpandPathName(appData.pixmapDirectory),
2613                         piece <= (int) WhiteKing ? "" : "w",
2614                         pieceBitmapNames[piece],
2615                         ximkind[kind], ss);
2616                 ximPieceBitmap[kind][piece] =
2617                   XGetImage(xDisplay, DefaultRootWindow(xDisplay),
2618                             0, 0, ss, ss, AllPlanes, XYPixmap);
2619                 if (appData.debugMode)
2620                   fprintf(stderr, _("(File:%s:) "), buf);
2621                 loadXIM(ximPieceBitmap[kind][piece],
2622                         ximtemp, buf,
2623                         &(xpmPieceBitmap2[kind][piece]),
2624                         &(ximMaskPm2[piece]));
2625                 if(piece <= (int)WhiteKing)
2626                     xpmPieceBitmap[kind][piece] = xpmPieceBitmap2[kind][piece];
2627             }
2628             fprintf(stderr," ");
2629         }
2630         /* Load light and dark squares */
2631         /* If the LSQ and DSQ pieces don't exist, we will
2632            draw them with solid squares. */
2633         snprintf(buf,sizeof(buf), "%s/lsq%u.xim", ExpandPathName(appData.pixmapDirectory), ss);
2634         if (access(buf, 0) != 0) {
2635             useImageSqs = 0;
2636         } else {
2637             useImageSqs = 1;
2638             fprintf(stderr, _("light square "));
2639             ximLightSquare=
2640               XGetImage(xDisplay, DefaultRootWindow(xDisplay),
2641                         0, 0, ss, ss, AllPlanes, XYPixmap);
2642             if (appData.debugMode)
2643               fprintf(stderr, _("(File:%s:) "), buf);
2644
2645             loadXIM(ximLightSquare, NULL, buf, &xpmLightSquare, NULL);
2646             fprintf(stderr, _("dark square "));
2647             snprintf(buf,sizeof(buf), "%s/dsq%u.xim",
2648                     ExpandPathName(appData.pixmapDirectory), ss);
2649             if (appData.debugMode)
2650               fprintf(stderr, _("(File:%s:) "), buf);
2651             ximDarkSquare=
2652               XGetImage(xDisplay, DefaultRootWindow(xDisplay),
2653                         0, 0, ss, ss, AllPlanes, XYPixmap);
2654             loadXIM(ximDarkSquare, NULL, buf, &xpmDarkSquare, NULL);
2655             xpmJailSquare = xpmLightSquare;
2656         }
2657         fprintf(stderr, _("Done.\n"));
2658     }
2659     XSynchronize(xDisplay, False); /* Work-around for xlib/xt buffering bug */
2660 }
2661
2662 static VariantClass oldVariant = (VariantClass) -1; // [HGM] pieces: redo every time variant changes
2663
2664 #if HAVE_LIBXPM
2665 void
2666 CreateXPMBoard (char *s, int kind)
2667 {
2668     XpmAttributes attr;
2669     attr.valuemask = 0;
2670     if(!appData.useBitmaps || s == NULL || *s == 0 || *s == '*') { useTexture &= ~(kind+1); return; }
2671     if (XpmReadFileToPixmap(xDisplay, xBoardWindow, s, &(xpmBoardBitmap[kind]), NULL, &attr) == 0) {
2672         useTexture |= kind + 1; textureW[kind] = attr.width; textureH[kind] = attr.height;
2673     }
2674 }
2675
2676 void
2677 FreeXPMPieces ()
2678 {   // [HGM] to prevent resoucre leak on calling CreaeXPMPieces() a second time,
2679     // thisroutine has to be called t free the old piece pixmaps
2680     int piece, kind;
2681     for (piece = (int) WhitePawn; piece <= (int) WhiteKing + 4; piece++)
2682         for (kind=0; kind<4; kind++) XFreePixmap(xDisplay, xpmPieceBitmap2[kind][piece]);
2683     if(useImageSqs) {
2684         XFreePixmap(xDisplay, xpmLightSquare);
2685         XFreePixmap(xDisplay, xpmDarkSquare);
2686     }
2687 }
2688
2689 void
2690 CreateXPMPieces ()
2691 {
2692     int piece, kind, r;
2693     char buf[MSG_SIZ];
2694     u_int ss = squareSize;
2695     XpmAttributes attr;
2696     static char *xpmkind[] = { "ll", "ld", "dl", "dd" };
2697     XpmColorSymbol symbols[4];
2698     static int redo = False;
2699
2700     if(redo) FreeXPMPieces(); else redo = 1;
2701
2702     /* The XSynchronize calls were copied from CreatePieces.
2703        Not sure if needed, but can't hurt */
2704     XSynchronize(xDisplay, True); /* Work-around for xlib/xt buffering bug */
2705
2706     /* Setup translations so piece colors match square colors */
2707     symbols[0].name = "light_piece";
2708     symbols[0].value = appData.whitePieceColor;
2709     symbols[1].name = "dark_piece";
2710     symbols[1].value = appData.blackPieceColor;
2711     symbols[2].name = "light_square";
2712     symbols[2].value = appData.lightSquareColor;
2713     symbols[3].name = "dark_square";
2714     symbols[3].value = appData.darkSquareColor;
2715
2716     attr.valuemask = XpmColorSymbols;
2717     attr.colorsymbols = symbols;
2718     attr.numsymbols = 4;
2719
2720     if (appData.monoMode) {
2721       DisplayFatalError(_("XPM pieces cannot be used in monochrome mode"),
2722                         0, 2);
2723       ExitEvent(2);
2724     }
2725     if (strlen(appData.pixmapDirectory) == 0) {
2726         XpmPieces* pieces = builtInXpms;
2727         useImages = 1;
2728         /* Load pieces */
2729         while (pieces->size != squareSize && pieces->size) pieces++;
2730         if (!pieces->size) {
2731           fprintf(stderr, _("No builtin XPM pieces of size %d\n"), squareSize);
2732           exit(1);
2733         }
2734         for (piece = (int) WhitePawn; piece <= (int) WhiteKing + 4; piece++) {
2735             for (kind=0; kind<4; kind++) {
2736
2737                 if ((r=XpmCreatePixmapFromData(xDisplay, xBoardWindow,
2738                                                pieces->xpm[piece][kind],
2739                                                &(xpmPieceBitmap2[kind][piece]),
2740                                                NULL, &attr)) != 0) {
2741                   fprintf(stderr, _("Error %d loading XPM image \"%s\"\n"),
2742                           r, buf);
2743                   exit(1);
2744                 }
2745                 if(piece <= (int) WhiteKing)
2746                     xpmPieceBitmap[kind][piece] = xpmPieceBitmap2[kind][piece];
2747             }
2748         }
2749         useImageSqs = 0;
2750         xpmJailSquare = xpmLightSquare;
2751     } else {
2752         useImages = 1;
2753
2754         fprintf(stderr, _("\nLoading XPMs...\n"));
2755
2756         /* Load pieces */
2757         for (piece = (int) WhitePawn; piece <= (int) WhiteKing + 4; piece++) {
2758             fprintf(stderr, "%d ", piece+1);
2759             for (kind=0; kind<4; kind++) {
2760               snprintf(buf, sizeof(buf), "%s/%s%c%s%u.xpm",
2761                         ExpandPathName(appData.pixmapDirectory),
2762                         piece > (int) WhiteKing ? "w" : "",
2763                         pieceBitmapNames[piece],
2764                         xpmkind[kind], ss);
2765                 if (appData.debugMode) {
2766                     fprintf(stderr, _("(File:%s:) "), buf);
2767                 }
2768                 if ((r=XpmReadFileToPixmap(xDisplay, xBoardWindow, buf,
2769                                            &(xpmPieceBitmap2[kind][piece]),
2770                                            NULL, &attr)) != 0) {
2771                     if(piece != (int)WhiteKing && piece > (int)WhiteQueen) {
2772                       // [HGM] missing: read of unorthodox piece failed; substitute King.
2773                       snprintf(buf, sizeof(buf), "%s/k%s%u.xpm",
2774                                 ExpandPathName(appData.pixmapDirectory),
2775                                 xpmkind[kind], ss);
2776                         if (appData.debugMode) {
2777                             fprintf(stderr, _("(Replace by File:%s:) "), buf);
2778                         }
2779                         r=XpmReadFileToPixmap(xDisplay, xBoardWindow, buf,
2780                                                 &(xpmPieceBitmap2[kind][piece]),
2781                                                 NULL, &attr);
2782                     }
2783                     if (r != 0) {
2784                         fprintf(stderr, _("Error %d loading XPM file \"%s\"\n"),
2785                                 r, buf);
2786                         exit(1);
2787                     }
2788                 }
2789                 if(piece <= (int) WhiteKing)
2790                     xpmPieceBitmap[kind][piece] = xpmPieceBitmap2[kind][piece];
2791             }
2792         }
2793         /* Load light and dark squares */
2794         /* If the LSQ and DSQ pieces don't exist, we will
2795            draw them with solid squares. */
2796         fprintf(stderr, _("light square "));
2797         snprintf(buf, sizeof(buf), "%s/lsq%u.xpm", ExpandPathName(appData.pixmapDirectory), ss);
2798         if (access(buf, 0) != 0) {
2799             useImageSqs = 0;
2800         } else {
2801             useImageSqs = 1;
2802             if (appData.debugMode)
2803               fprintf(stderr, _("(File:%s:) "), buf);
2804
2805             if ((r=XpmReadFileToPixmap(xDisplay, xBoardWindow, buf,
2806                                        &xpmLightSquare, NULL, &attr)) != 0) {
2807                 fprintf(stderr, _("Error %d loading XPM file \"%s\"\n"), r, buf);
2808                 exit(1);
2809             }
2810             fprintf(stderr, _("dark square "));
2811             snprintf(buf, sizeof(buf), "%s/dsq%u.xpm",
2812                     ExpandPathName(appData.pixmapDirectory), ss);
2813             if (appData.debugMode) {
2814                 fprintf(stderr, _("(File:%s:) "), buf);
2815             }
2816             if ((r=XpmReadFileToPixmap(xDisplay, xBoardWindow, buf,
2817                                        &xpmDarkSquare, NULL, &attr)) != 0) {
2818                 fprintf(stderr, _("Error %d loading XPM file \"%s\"\n"), r, buf);
2819                 exit(1);
2820             }
2821         }
2822         xpmJailSquare = xpmLightSquare;
2823         fprintf(stderr, _("Done.\n"));
2824     }
2825     oldVariant = -1; // kludge to force re-makig of animation masks
2826     XSynchronize(xDisplay, False); /* Work-around for xlib/xt
2827                                       buffering bug */
2828 }
2829 #endif /* HAVE_LIBXPM */
2830
2831 #if HAVE_LIBXPM
2832 /* No built-in bitmaps */
2833 void CreatePieces()
2834 {
2835     int piece, kind;
2836     char buf[MSG_SIZ];
2837     u_int ss = squareSize;
2838
2839     XSynchronize(xDisplay, True); /* Work-around for xlib/xt
2840                                      buffering bug */
2841
2842     for (kind = SOLID; kind <= (appData.monoMode ? OUTLINE : SOLID); kind++) {
2843         for (piece = (int) WhitePawn; piece <= (int) WhiteKing + 4; piece++) {
2844           snprintf(buf, MSG_SIZ, "%s%c%u%c.bm", piece > (int)WhiteKing ? "w" : "",
2845                    pieceBitmapNames[piece],
2846                    ss, kind == SOLID ? 's' : 'o');
2847           ReadBitmap(&pieceBitmap2[kind][piece], buf, NULL, ss, ss);
2848           if(piece <= (int)WhiteKing)
2849             pieceBitmap[kind][piece] = pieceBitmap2[kind][piece];
2850         }
2851     }
2852
2853     XSynchronize(xDisplay, False); /* Work-around for xlib/xt
2854                                       buffering bug */
2855 }
2856 #else
2857 /* With built-in bitmaps */
2858 void
2859 CreatePieces ()
2860 {
2861     BuiltInBits* bib = builtInBits;
2862     int piece, kind;
2863     char buf[MSG_SIZ];
2864     u_int ss = squareSize;
2865
2866     XSynchronize(xDisplay, True); /* Work-around for xlib/xt
2867                                      buffering bug */
2868
2869     while (bib->squareSize != ss && bib->squareSize != 0) bib++;
2870
2871     for (kind = SOLID; kind <= (appData.monoMode ? OUTLINE : SOLID); kind++) {
2872         for (piece = (int) WhitePawn; piece <= (int) WhiteKing + 4; piece++) {
2873           snprintf(buf, MSG_SIZ, "%s%c%u%c.bm", piece > (int)WhiteKing ? "w" : "",
2874                    pieceBitmapNames[piece],
2875                    ss, kind == SOLID ? 's' : 'o');
2876           ReadBitmap(&pieceBitmap2[kind][piece], buf,
2877                      bib->bits[kind][piece], ss, ss);
2878           if(piece <= (int)WhiteKing)
2879             pieceBitmap[kind][piece] = pieceBitmap2[kind][piece];
2880         }
2881     }
2882
2883     XSynchronize(xDisplay, False); /* Work-around for xlib/xt
2884                                       buffering bug */
2885 }
2886 #endif
2887
2888 void
2889 ReadBitmap (Pixmap *pm, String name, unsigned char bits[], u_int wreq, u_int hreq)
2890 {
2891     int x_hot, y_hot;
2892     u_int w, h;
2893     int errcode;
2894     char msg[MSG_SIZ], fullname[MSG_SIZ];
2895
2896     if (*appData.bitmapDirectory != NULLCHAR) {
2897       safeStrCpy(fullname, appData.bitmapDirectory, sizeof(fullname)/sizeof(fullname[0]) );
2898       strncat(fullname, "/", MSG_SIZ - strlen(fullname) - 1);
2899       strncat(fullname, name, MSG_SIZ - strlen(fullname) - 1);
2900       errcode = XReadBitmapFile(xDisplay, xBoardWindow, fullname,
2901                                 &w, &h, pm, &x_hot, &y_hot);
2902       fprintf(stderr, "load %s\n", name);
2903         if (errcode != BitmapSuccess) {
2904             switch (errcode) {
2905               case BitmapOpenFailed:
2906                 snprintf(msg, sizeof(msg), _("Can't open bitmap file %s"), fullname);
2907                 break;
2908               case BitmapFileInvalid:
2909                 snprintf(msg, sizeof(msg), _("Invalid bitmap in file %s"), fullname);
2910                 break;
2911               case BitmapNoMemory:
2912                 snprintf(msg, sizeof(msg), _("Ran out of memory reading bitmap file %s"),
2913                         fullname);
2914                 break;
2915               default:
2916                 snprintf(msg, sizeof(msg), _("Unknown XReadBitmapFile error %d on file %s"),
2917                         errcode, fullname);
2918                 break;
2919             }
2920             fprintf(stderr, _("%s: %s...using built-in\n"),
2921                     programName, msg);
2922         } else if (w != wreq || h != hreq) {
2923             fprintf(stderr,
2924                     _("%s: Bitmap %s is %dx%d, not %dx%d...using built-in\n"),
2925                     programName, fullname, w, h, wreq, hreq);
2926         } else {
2927             return;
2928         }
2929     }
2930     if (bits != NULL) {
2931         *pm = XCreateBitmapFromData(xDisplay, xBoardWindow, (char *) bits,
2932                                     wreq, hreq);
2933     }
2934 }
2935
2936 void
2937 CreateGrid ()
2938 {
2939     int i, j;
2940
2941     if (lineGap == 0) return;
2942
2943     /* [HR] Split this into 2 loops for non-square boards. */
2944
2945     for (i = 0; i < BOARD_HEIGHT + 1; i++) {
2946         gridSegments[i].x1 = 0;
2947         gridSegments[i].x2 =
2948           lineGap + BOARD_WIDTH * (squareSize + lineGap);
2949         gridSegments[i].y1 = gridSegments[i].y2
2950           = lineGap / 2 + (i * (squareSize + lineGap));
2951     }
2952
2953     for (j = 0; j < BOARD_WIDTH + 1; j++) {
2954         gridSegments[j + i].y1 = 0;
2955         gridSegments[j + i].y2 =
2956           lineGap + BOARD_HEIGHT * (squareSize + lineGap);
2957         gridSegments[j + i].x1 = gridSegments[j + i].x2
2958           = lineGap / 2 + (j * (squareSize + lineGap));
2959     }
2960 }
2961
2962 int nrOfMenuItems = 7;
2963 Widget menuWidget[150];
2964 MenuListItem menuItemList[150] = {
2965     { "LoadNextGameProc", LoadNextGameProc },
2966     { "LoadPrevGameProc", LoadPrevGameProc },
2967     { "ReloadGameProc", ReloadGameProc },
2968     { "ReloadPositionProc", ReloadPositionProc },
2969 #ifndef OPTIONSDIALOG
2970     { "AlwaysQueenProc", AlwaysQueenProc },
2971     { "AnimateDraggingProc", AnimateDraggingProc },
2972     { "AnimateMovingProc", AnimateMovingProc },
2973     { "AutoflagProc", AutoflagProc },
2974     { "AutoflipProc", AutoflipProc },
2975     { "BlindfoldProc", BlindfoldProc },
2976     { "FlashMovesProc", FlashMovesProc },
2977 #if HIGHDRAG
2978     { "HighlightDraggingProc", HighlightDraggingProc },
2979 #endif
2980     { "HighlightLastMoveProc", HighlightLastMoveProc },
2981 //    { "IcsAlarmProc", IcsAlarmProc },
2982     { "MoveSoundProc", MoveSoundProc },
2983     { "PeriodicUpdatesProc", PeriodicUpdatesProc },
2984     { "PopupExitMessageProc", PopupExitMessageProc },
2985     { "PopupMoveErrorsProc", PopupMoveErrorsProc },
2986 //    { "PremoveProc", PremoveProc },
2987     { "ShowCoordsProc", ShowCoordsProc },
2988     { "ShowThinkingProc", ShowThinkingProc },
2989     { "HideThinkingProc", HideThinkingProc },
2990     { "TestLegalityProc", TestLegalityProc },
2991 #endif
2992     { "AboutGameProc", AboutGameEvent },
2993     { "DebugProc", DebugProc },
2994     { "NothingProc", NothingProc },
2995   {NULL, NothingProc}
2996 };
2997
2998 void
2999 MarkMenuItem (char *menuRef, int state)
3000 {
3001     int nr = MenuToNumber(menuRef);
3002     if(nr >= 0) {
3003         Arg args[2];
3004         XtSetArg(args[0], XtNleftBitmap, state ? xMarkPixmap : None);
3005         XtSetValues(menuWidget[nr], args, 1);
3006     }
3007 }
3008
3009 void
3010 EnableMenuItem (char *menuRef, int state)
3011 {
3012     int nr = MenuToNumber(menuRef);
3013     if(nr >= 0) XtSetSensitive(menuWidget[nr], state);
3014 }
3015
3016 void
3017 EnableButtonBar (int state)
3018 {
3019     XtSetSensitive(buttonBarWidget, state);
3020 }
3021
3022
3023 void
3024 SetMenuEnables (Enables *enab)
3025 {
3026   while (enab->name != NULL) {
3027     EnableMenuItem(enab->name, enab->value);
3028     enab++;
3029   }
3030 }
3031
3032 int
3033 Equal(char *p, char *s)
3034 {   // compare strings skipping spaces in second
3035     while(*s) {
3036         if(*s == ' ') { s++; continue; }
3037         if(*s++ != *p++) return 0;
3038     }
3039     return !*p;
3040 }
3041
3042 void
3043 KeyBindingProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
3044 {   // [HGM] new method of key binding: specify MenuItem(FlipView) in stead of FlipViewProc in translation string
3045     int i;
3046     if(*nprms == 0) return;
3047     for(i=0; menuItemList[i].name; i++) {
3048         if(Equal(prms[0], menuItemList[i].name)) {
3049             (menuItemList[i].proc) ();
3050             return;
3051         }
3052     }
3053 }
3054
3055 static void
3056 MenuBarSelect (Widget w, caddr_t addr, caddr_t index)
3057 {
3058     MenuProc *proc = (MenuProc *) addr;
3059
3060     (proc)();
3061 }
3062
3063 static void
3064 MenuEngineSelect (Widget w, caddr_t addr, caddr_t index)
3065 {
3066     RecentEngineEvent((int) (intptr_t) addr);
3067 }
3068
3069 // some stuff that must remain in front-end
3070 static Widget mainBar, currentMenu;
3071 static int wtot, nr = 0, widths[10];
3072
3073 void
3074 AppendMenuItem (char *text, char *name, MenuProc *action)
3075 {
3076     int j;
3077     Widget entry;
3078     Arg args[16];
3079
3080     j = 0;
3081     XtSetArg(args[j], XtNleftMargin, 20);   j++;
3082     XtSetArg(args[j], XtNrightMargin, 20);  j++;
3083
3084         if (strcmp(text, "----") == 0) {
3085           entry = XtCreateManagedWidget(text, smeLineObjectClass,
3086                                           currentMenu, args, j);
3087         } else {
3088           XtSetArg(args[j], XtNlabel, XtNewString(_(text)));
3089             entry = XtCreateManagedWidget(name, smeBSBObjectClass,
3090                                           currentMenu, args, j+1);
3091             XtAddCallback(entry, XtNcallback,
3092                           (XtCallbackProc) (strcmp(name, "recent") ? MenuBarSelect : MenuEngineSelect),
3093                           (caddr_t) action);
3094             menuWidget[nrOfMenuItems] = entry;
3095         }
3096 }
3097
3098 void
3099 CreateMenuButton (char *name, Menu *mb)
3100 {   // create menu button on main bar, and shell for pull-down list
3101     int i, j;
3102     Arg args[16];
3103     Dimension w;
3104
3105         j = 0;
3106         XtSetArg(args[j], XtNmenuName, XtNewString(name));  j++;
3107         XtSetArg(args[j], XtNlabel, XtNewString(_(mb->name)));  j++;
3108         XtSetArg(args[j], XtNborderWidth, 0);                   j++;
3109         mb->subMenu = XtCreateManagedWidget(mb->name, menuButtonWidgetClass,
3110                                        mainBar, args, j);
3111     currentMenu = XtCreatePopupShell(name, simpleMenuWidgetClass,
3112                               mainBar, NULL, 0);
3113         j = 0;
3114         XtSetArg(args[j], XtNwidth, &w);                   j++;
3115         XtGetValues(mb->subMenu, args, j);
3116         wtot += mb->textWidth = widths[nr++] = w;
3117 }
3118
3119 Widget
3120 CreateMenuBar (Menu *mb, int boardWidth)
3121 {
3122     int i, j;
3123     Arg args[16];
3124     char menuName[MSG_SIZ];
3125     Dimension w;
3126     Menu *ma = mb;
3127
3128     // create bar itself
3129     j = 0;
3130     XtSetArg(args[j], XtNorientation, XtorientHorizontal);  j++;
3131     XtSetArg(args[j], XtNvSpace, 0);                        j++;
3132     XtSetArg(args[j], XtNborderWidth, 0);                   j++;
3133     mainBar = XtCreateWidget("menuBar", boxWidgetClass,
3134                              formWidget, args, j);
3135
3136     CreateMainMenus(mb); // put menus in bar according to description in back-end
3137
3138     // size buttons to make menu bar fit, clipping menu names where necessary
3139     while(wtot > boardWidth - 40) {
3140         int wmax=0, imax=0;
3141         for(i=0; i<nr; i++) if(widths[i] > wmax) wmax = widths[imax=i];
3142         widths[imax]--;
3143         wtot--;
3144     }
3145     for(i=0; i<nr; i++) if(widths[i] != ma[i].textWidth) {
3146         j = 0;
3147         XtSetArg(args[j], XtNwidth, widths[i]);                   j++;
3148         XtSetValues(ma[i].subMenu, args, j);
3149     }
3150
3151     return mainBar;
3152 }
3153
3154 Widget
3155 CreateButtonBar (MenuItem *mi)
3156 {
3157     int j;
3158     Widget button, buttonBar;
3159     Arg args[16];
3160
3161     j = 0;
3162     XtSetArg(args[j], XtNorientation, XtorientHorizontal); j++;
3163     if (tinyLayout) {
3164         XtSetArg(args[j], XtNhSpace, 0); j++;
3165     }
3166     XtSetArg(args[j], XtNborderWidth, 0); j++;
3167     XtSetArg(args[j], XtNvSpace, 0);                        j++;
3168     buttonBar = XtCreateWidget("buttonBar", boxWidgetClass,
3169                                formWidget, args, j);
3170
3171     while (mi->string != NULL) {
3172         j = 0;
3173         if (tinyLayout) {
3174             XtSetArg(args[j], XtNinternalWidth, 2); j++;
3175             XtSetArg(args[j], XtNborderWidth, 0); j++;
3176         }
3177       XtSetArg(args[j], XtNlabel, XtNewString(_(mi->string))); j++;
3178         button = XtCreateManagedWidget(mi->string, commandWidgetClass,
3179                                        buttonBar, args, j);
3180         XtAddCallback(button, XtNcallback,
3181                       (XtCallbackProc) MenuBarSelect,
3182                       (caddr_t) mi->proc);
3183         mi++;
3184     }
3185     return buttonBar;
3186 }
3187
3188 Widget
3189 CreatePieceMenu (char *name, int color)
3190 {
3191     int i;
3192     Widget entry, menu;
3193     Arg args[16];
3194     ChessSquare selection;
3195
3196     menu = XtCreatePopupShell(name, simpleMenuWidgetClass,
3197                               boardWidget, args, 0);
3198
3199     for (i = 0; i < PIECE_MENU_SIZE; i++) {
3200         String item = pieceMenuStrings[color][i];
3201
3202         if (strcmp(item, "----") == 0) {
3203             entry = XtCreateManagedWidget(item, smeLineObjectClass,
3204                                           menu, NULL, 0);
3205         } else {
3206           XtSetArg(args[0], XtNlabel, XtNewString(_(item)));
3207             entry = XtCreateManagedWidget(item, smeBSBObjectClass,
3208                                 menu, args, 1);
3209             selection = pieceMenuTranslation[color][i];
3210             XtAddCallback(entry, XtNcallback,
3211                           (XtCallbackProc) PieceMenuSelect,
3212                           (caddr_t) selection);
3213             if (selection == WhitePawn || selection == BlackPawn) {
3214                 XtSetArg(args[0], XtNpopupOnEntry, entry);
3215                 XtSetValues(menu, args, 1);
3216             }
3217         }
3218     }
3219     return menu;
3220 }
3221
3222 void
3223 CreatePieceMenus ()
3224 {
3225     int i;
3226     Widget entry;
3227     Arg args[16];
3228     ChessSquare selection;
3229
3230     whitePieceMenu = CreatePieceMenu("menuW", 0);
3231     blackPieceMenu = CreatePieceMenu("menuB", 1);
3232
3233     if(appData.pieceMenu) // [HGM] sweep: no idea what this was good for, but it stopped reporting button events outside the window
3234     XtRegisterGrabAction(PieceMenuPopup, True,
3235                          (unsigned)(ButtonPressMask|ButtonReleaseMask),
3236                          GrabModeAsync, GrabModeAsync);
3237
3238     XtSetArg(args[0], XtNlabel, _("Drop"));
3239     dropMenu = XtCreatePopupShell("menuD", simpleMenuWidgetClass,
3240                                   boardWidget, args, 1);
3241     for (i = 0; i < DROP_MENU_SIZE; i++) {
3242         String item = dropMenuStrings[i];
3243
3244         if (strcmp(item, "----") == 0) {
3245             entry = XtCreateManagedWidget(item, smeLineObjectClass,
3246                                           dropMenu, NULL, 0);
3247         } else {
3248           XtSetArg(args[0], XtNlabel, XtNewString(_(item)));
3249             entry = XtCreateManagedWidget(item, smeBSBObjectClass,
3250                                 dropMenu, args, 1);
3251             selection = dropMenuTranslation[i];
3252             XtAddCallback(entry, XtNcallback,
3253                           (XtCallbackProc) DropMenuSelect,
3254                           (caddr_t) selection);
3255         }
3256     }
3257 }
3258
3259 void
3260 SetupDropMenu ()
3261 {
3262     int i, j, count;
3263     char label[32];
3264     Arg args[16];
3265     Widget entry;
3266     char* p;
3267
3268     for (i=0; i<sizeof(dmEnables)/sizeof(DropMenuEnables); i++) {
3269         entry = XtNameToWidget(dropMenu, dmEnables[i].widget);
3270         p = strchr(gameMode == IcsPlayingWhite ? white_holding : black_holding,
3271                    dmEnables[i].piece);
3272         XtSetSensitive(entry, p != NULL || !appData.testLegality
3273                        /*!!temp:*/ || (gameInfo.variant == VariantCrazyhouse
3274                                        && !appData.icsActive));
3275         count = 0;
3276         while (p && *p++ == dmEnables[i].piece) count++;
3277         snprintf(label, sizeof(label), "%s  %d", dmEnables[i].widget, count);
3278         j = 0;
3279         XtSetArg(args[j], XtNlabel, label); j++;
3280         XtSetValues(entry, args, j);
3281     }
3282 }
3283
3284 void
3285 PieceMenuPopup (Widget w, XEvent *event, String *params, Cardinal *num_params)
3286 {
3287     String whichMenu; int menuNr = -2;
3288     shiftKey = strcmp(params[0], "menuW"); // used to indicate black
3289     if (event->type == ButtonRelease)
3290         menuNr = RightClick(Release, event->xbutton.x, event->xbutton.y, &pmFromX, &pmFromY);
3291     else if (event->type == ButtonPress)
3292         menuNr = RightClick(Press,   event->xbutton.x, event->xbutton.y, &pmFromX, &pmFromY);
3293     switch(menuNr) {
3294       case 0: whichMenu = params[0]; break;
3295       case 1: SetupDropMenu(); whichMenu = "menuD"; break;
3296       case 2:
3297       case -1: if (errorUp) ErrorPopDown();
3298       default: return;
3299     }
3300     XtPopupSpringLoaded(XtNameToWidget(boardWidget, whichMenu));
3301 }
3302
3303 static void
3304 PieceMenuSelect (Widget w, ChessSquare piece, caddr_t junk)
3305 {
3306     if (pmFromX < 0 || pmFromY < 0) return;
3307     EditPositionMenuEvent(piece, pmFromX, pmFromY);
3308 }
3309
3310 static void
3311 DropMenuSelect (Widget w, ChessSquare piece, caddr_t junk)
3312 {
3313     if (pmFromX < 0 || pmFromY < 0) return;
3314     DropMenuEvent(piece, pmFromX, pmFromY);
3315 }
3316
3317 void
3318 WhiteClock (Widget w, XEvent *event, String *prms, Cardinal *nprms)
3319 {
3320     shiftKey = prms[0][0] & 1;
3321     ClockClick(0);
3322 }
3323
3324 void
3325 BlackClock (Widget w, XEvent *event, String *prms, Cardinal *nprms)
3326 {
3327     shiftKey = prms[0][0] & 1;
3328     ClockClick(1);
3329 }
3330
3331
3332 static void
3333 do_flash_delay (unsigned long msec)
3334 {
3335     TimeDelay(msec);
3336 }
3337
3338 void
3339 DrawBorder (int x, int y, int type)
3340 {
3341     GC gc = lineGC;
3342
3343     if(type == 1) gc = highlineGC; else if(type == 2) gc = prelineGC;
3344
3345     XDrawRectangle(xDisplay, xBoardWindow, gc, x, y,
3346                    squareSize+lineGap, squareSize+lineGap);
3347 }
3348
3349 static int
3350 CutOutSquare (int x, int y, int *x0, int *y0, int  kind)
3351 {
3352     int W = BOARD_WIDTH, H = BOARD_HEIGHT;
3353     int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
3354     *x0 = 0; *y0 = 0;
3355     if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
3356     if(textureW[kind] < W*squareSize)
3357         *x0 = (textureW[kind] - squareSize) * nx/(W-1);
3358     else
3359         *x0 = textureW[kind]*nx / W + (textureW[kind] - W*squareSize) / (2*W);
3360     if(textureH[kind] < H*squareSize)
3361         *y0 = (textureH[kind] - squareSize) * ny/(H-1);
3362     else
3363         *y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
3364     return 1;
3365 }
3366
3367 static void
3368 BlankSquare (int x, int y, int color, ChessSquare piece, Drawable dest, int fac)
3369 {   // [HGM] extra param 'fac' for forcing destination to (0,0) for copying to animation buffer
3370     int x0, y0;
3371     if (useImages && color != 2 && (useTexture & color+1) && CutOutSquare(x, y, &x0, &y0, color)) {
3372         XCopyArea(xDisplay, xpmBoardBitmap[color], dest, wlPieceGC, x0, y0,
3373                   squareSize, squareSize, x*fac, y*fac);
3374     } else
3375     if (useImages && useImageSqs) {
3376         Pixmap pm;
3377         switch (color) {
3378           case 1: /* light */
3379             pm = xpmLightSquare;
3380             break;
3381           case 0: /* dark */
3382             pm = xpmDarkSquare;
3383             break;
3384           case 2: /* neutral */
3385           default:
3386             pm = xpmJailSquare;
3387             break;
3388         }
3389         XCopyArea(xDisplay, pm, dest, wlPieceGC, 0, 0,
3390                   squareSize, squareSize, x*fac, y*fac);
3391     } else {
3392         GC gc;
3393         switch (color) {
3394           case 1: /* light */
3395             gc = lightSquareGC;
3396             break;
3397           case 0: /* dark */
3398             gc = darkSquareGC;
3399             break;
3400           case 2: /* neutral */
3401           default:
3402             gc = jailSquareGC;
3403             break;
3404         }
3405         XFillRectangle(xDisplay, dest, gc, x*fac, y*fac, squareSize, squareSize);
3406     }
3407 }
3408
3409 /*
3410    I split out the routines to draw a piece so that I could
3411    make a generic flash routine.
3412 */
3413 static void
3414 monoDrawPiece_1bit (ChessSquare piece, int square_color, int x, int y, Drawable dest)
3415 {
3416     /* Avoid XCopyPlane on 1-bit screens to work around Sun bug */
3417     switch (square_color) {
3418       case 1: /* light */
3419       case 2: /* neutral */
3420       default:
3421         XCopyArea(xDisplay, (int) piece < (int) BlackPawn
3422                   ? *pieceToOutline(piece)
3423                   : *pieceToSolid(piece),
3424                   dest, bwPieceGC, 0, 0,
3425                   squareSize, squareSize, x, y);
3426         break;
3427       case 0: /* dark */
3428         XCopyArea(xDisplay, (int) piece < (int) BlackPawn
3429                   ? *pieceToSolid(piece)
3430                   : *pieceToOutline(piece),
3431                   dest, wbPieceGC, 0, 0,
3432                   squareSize, squareSize, x, y);
3433         break;
3434     }
3435 }
3436
3437 static void
3438 monoDrawPiece (ChessSquare piece, int square_color, int x, int y, Drawable dest)
3439 {
3440     switch (square_color) {
3441       case 1: /* light */
3442       case 2: /* neutral */
3443       default:
3444         XCopyPlane(xDisplay, (int) piece < (int) BlackPawn
3445                    ? *pieceToOutline(piece)
3446                    : *pieceToSolid(piece),
3447                    dest, bwPieceGC, 0, 0,
3448                    squareSize, squareSize, x, y, 1);
3449         break;
3450       case 0: /* dark */
3451         XCopyPlane(xDisplay, (int) piece < (int) BlackPawn
3452                    ? *pieceToSolid(piece)
3453                    : *pieceToOutline(piece),
3454                    dest, wbPieceGC, 0, 0,
3455                    squareSize, squareSize, x, y, 1);
3456         break;
3457     }
3458 }
3459
3460 static void
3461 colorDrawPiece (ChessSquare piece, int square_color, int x, int y, Drawable dest)
3462 {
3463     if(pieceToSolid(piece) == NULL) return; // [HGM] bitmaps: make it non-fatal if we have no bitmap;
3464     switch (square_color) {
3465       case 1: /* light */
3466         XCopyPlane(xDisplay, *pieceToSolid(piece),
3467                    dest, (int) piece < (int) BlackPawn
3468                    ? wlPieceGC : blPieceGC, 0, 0,
3469                    squareSize, squareSize, x, y, 1);
3470         break;
3471       case 0: /* dark */
3472         XCopyPlane(xDisplay, *pieceToSolid(piece),
3473                    dest, (int) piece < (int) BlackPawn
3474                    ? wdPieceGC : bdPieceGC, 0, 0,
3475                    squareSize, squareSize, x, y, 1);
3476         break;
3477       case 2: /* neutral */
3478       default:
3479         XCopyPlane(xDisplay, *pieceToSolid(piece),
3480                    dest, (int) piece < (int) BlackPawn
3481                    ? wjPieceGC : bjPieceGC, 0, 0,
3482                    squareSize, squareSize, x, y, 1);
3483         break;
3484     }
3485 }
3486
3487 static void
3488 colorDrawPieceImage (ChessSquare piece, int square_color, int x, int y, Drawable dest)
3489 {
3490     int kind, p = piece;
3491
3492     switch (square_color) {
3493       case 1: /* light */
3494       case 2: /* neutral */
3495       default:
3496         if ((int)piece < (int) BlackPawn) {
3497             kind = 0;
3498         } else {
3499             kind = 2;
3500             piece -= BlackPawn;
3501         }
3502         break;
3503       case 0: /* dark */
3504         if ((int)piece < (int) BlackPawn) {
3505             kind = 1;
3506         } else {
3507             kind = 3;
3508             piece -= BlackPawn;
3509         }
3510         break;
3511     }
3512     if(appData.upsideDown && flipView) { kind ^= 2; p += p < BlackPawn ? BlackPawn : -BlackPawn; }// swap white and black pieces
3513     if(useTexture & square_color+1) {
3514         BlankSquare(x, y, square_color, piece, dest, 1); // erase previous contents with background
3515         XSetClipMask(xDisplay, wlPieceGC, xpmMask[p]);
3516         XSetClipOrigin(xDisplay, wlPieceGC, x, y);
3517         XCopyArea(xDisplay, xpmPieceBitmap[kind][piece], dest, wlPieceGC, 0, 0, squareSize, squareSize, x, y);
3518         XSetClipMask(xDisplay, wlPieceGC, None);
3519         XSetClipOrigin(xDisplay, wlPieceGC, 0, 0);
3520     } else
3521     XCopyArea(xDisplay, xpmPieceBitmap[kind][piece],
3522               dest, wlPieceGC, 0, 0,
3523               squareSize, squareSize, x, y);
3524 }
3525
3526 typedef void (*DrawFunc)();
3527
3528 DrawFunc
3529 ChooseDrawFunc ()
3530 {
3531     if (appData.monoMode) {
3532         if (DefaultDepth(xDisplay, xScreen) == 1) {
3533             return monoDrawPiece_1bit;
3534         } else {
3535             return monoDrawPiece;
3536         }
3537     } else {
3538         if (useImages)
3539           return colorDrawPieceImage;
3540         else
3541           return colorDrawPiece;
3542     }
3543 }
3544
3545 void
3546 DrawDot (int marker, int x, int y, int r)
3547 {
3548         if(appData.monoMode) {
3549             XFillArc(xDisplay, xBoardWindow, marker == 2 ? darkSquareGC : lightSquareGC,
3550                     x, y, r, r, 0, 64*360);
3551             XDrawArc(xDisplay, xBoardWindow, marker == 2 ? lightSquareGC : darkSquareGC,
3552                     x, y, r, r, 0, 64*360);
3553         } else
3554         XFillArc(xDisplay, xBoardWindow, marker == 2 ? prelineGC : highlineGC,
3555                     x, y, r, r, 0, 64*360);
3556 }
3557
3558 void
3559 DrawOneSquare (int x, int y, ChessSquare piece, int square_color, int marker, char *string, int align)
3560 {   // basic front-end board-draw function: takes care of everything that can be in square:
3561     // piece, background, coordinate/count, marker dot
3562     int direction, font_ascent, font_descent;
3563     XCharStruct overall;
3564     DrawFunc drawfunc;
3565
3566     if (piece == EmptySquare) {
3567         BlankSquare(x, y, square_color, piece, xBoardWindow, 1);
3568     } else {
3569         drawfunc = ChooseDrawFunc();
3570         drawfunc(piece, square_color, x, y, xBoardWindow);
3571     }
3572
3573     if(align) { // square carries inscription (coord or piece count)
3574         int xx = x, yy = y;
3575         GC hGC = align < 3 ? coordGC : countGC;
3576         // first calculate where it goes
3577         XTextExtents(countFontStruct, string, 1, &direction,
3578                          &font_ascent, &font_descent, &overall);
3579         if (align == 1) {
3580             xx += squareSize - overall.width - 2;
3581             yy += squareSize - font_descent - 1;
3582         } else if (align == 2) {
3583             xx += 2, yy += font_ascent + 1;
3584         } else if (align == 3) {
3585             xx += squareSize - overall.width - 2;
3586             yy += font_ascent + 1;
3587         } else if (align == 4) {
3588             xx += 2, yy += font_ascent + 1;
3589         }
3590         // then draw it
3591         if (appData.monoMode) {
3592             XDrawImageString(xDisplay, xBoardWindow, hGC, xx, yy, string, 1);
3593         } else {
3594             XDrawString(xDisplay, xBoardWindow, hGC, xx, yy, string, 1);
3595         }
3596     }
3597
3598     if(marker) { // print fat marker dot, if requested
3599         DrawDot(marker, x + squareSize/4, y+squareSize/4, squareSize/2);
3600     }
3601 }
3602
3603 void
3604 FlashDelay (int flash_delay)
3605 {
3606         XSync(xDisplay, False);
3607         if(flash_delay) do_flash_delay(flash_delay);
3608 }
3609
3610 double
3611 Fraction (int x, int start, int stop)
3612 {
3613    double f = ((double) x - start)/(stop - start);
3614    if(f > 1.) f = 1.; else if(f < 0.) f = 0.;
3615    return f;
3616 }
3617
3618 static WindowPlacement wpNew;
3619
3620 void
3621 CoDrag (Widget sh, WindowPlacement *wp)
3622 {
3623     Arg args[16];
3624     int j=0, touch=0, fudge = 2;
3625     GetActualPlacement(sh, wp);
3626     if(abs(wpMain.x + wpMain.width + 2*frameX - wp->x)         < fudge) touch = 1; else // right touch
3627     if(abs(wp->x + wp->width + 2*frameX - wpMain.x)            < fudge) touch = 2; else // left touch
3628     if(abs(wpMain.y + wpMain.height + frameX + frameY - wp->y) < fudge) touch = 3; else // bottom touch
3629     if(abs(wp->y + wp->height + frameX + frameY - wpMain.y)    < fudge) touch = 4;      // top touch
3630     if(!touch ) return; // only windows that touch co-move
3631     if(touch < 3 && wpNew.height != wpMain.height) { // left or right and height changed
3632         int heightInc = wpNew.height - wpMain.height;
3633         double fracTop = Fraction(wp->y, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
3634         double fracBot = Fraction(wp->y + wp->height + frameX + frameY + 1, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
3635         wp->y += fracTop * heightInc;
3636         heightInc = (int) (fracBot * heightInc) - (int) (fracTop * heightInc);
3637         if(heightInc) XtSetArg(args[j], XtNheight, wp->height + heightInc), j++;
3638     } else if(touch > 2 && wpNew.width != wpMain.width) { // top or bottom and width changed
3639         int widthInc = wpNew.width - wpMain.width;
3640         double fracLeft = Fraction(wp->x, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
3641         double fracRght = Fraction(wp->x + wp->width + 2*frameX + 1, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
3642         wp->y += fracLeft * widthInc;
3643         widthInc = (int) (fracRght * widthInc) - (int) (fracLeft * widthInc);
3644         if(widthInc) XtSetArg(args[j], XtNwidth, wp->width + widthInc), j++;
3645     }
3646     wp->x += wpNew.x - wpMain.x;
3647     wp->y += wpNew.y - wpMain.y;
3648     if(touch == 1) wp->x += wpNew.width - wpMain.width; else
3649     if(touch == 3) wp->y += wpNew.height - wpMain.height;
3650     XtSetArg(args[j], XtNx, wp->x); j++;
3651     XtSetArg(args[j], XtNy, wp->y); j++;
3652     XtSetValues(sh, args, j);
3653 }
3654
3655 static XtIntervalId delayedDragID = 0;
3656
3657 void
3658 DragProc ()
3659 {
3660         GetActualPlacement(shellWidget, &wpNew);
3661         if(wpNew.x == wpMain.x && wpNew.y == wpMain.y && // not moved
3662            wpNew.width == wpMain.width && wpNew.height == wpMain.height) // not sized
3663             return; // false alarm
3664         if(EngineOutputIsUp()) CoDrag(engineOutputShell, &wpEngineOutput);
3665         if(MoveHistoryIsUp()) CoDrag(shells[7], &wpMoveHistory);
3666         if(EvalGraphIsUp()) CoDrag(evalGraphShell, &wpEvalGraph);
3667         if(GameListIsUp()) CoDrag(gameListShell, &wpGameList);
3668         wpMain = wpNew;
3669         DrawPosition(True, NULL);
3670         delayedDragID = 0; // now drag executed, make sure next DelayedDrag will not cancel timer event (which could now be used by other)
3671 }
3672
3673
3674 void
3675 DelayedDrag ()
3676 {
3677     if(delayedDragID) XtRemoveTimeOut(delayedDragID); // cancel pending
3678     delayedDragID =
3679       XtAppAddTimeOut(appContext, 50, (XtTimerCallbackProc) DragProc, (XtPointer) 0); // and schedule new one 50 msec later
3680 }
3681
3682 /* Why is this needed on some versions of X? */
3683 void
3684 EventProc (Widget widget, caddr_t unused, XEvent *event)
3685 {
3686     if (!XtIsRealized(widget))
3687       return;
3688     switch (event->type) {
3689       case ConfigureNotify: // main window is being dragged: drag attached windows with it
3690         if(appData.useStickyWindows)
3691             DelayedDrag(); // as long as events keep coming in faster than 50 msec, they destroy each other
3692         break;
3693       case Expose:
3694         if (event->xexpose.count > 0) return;  /* no clipping is done */
3695         DrawPosition(True, NULL);
3696         if(twoBoards) { // [HGM] dual: draw other board in other orientation
3697             flipView = !flipView; partnerUp = !partnerUp;
3698             DrawPosition(True, NULL);
3699             flipView = !flipView; partnerUp = !partnerUp;
3700         }
3701         break;
3702       case MotionNotify:
3703         if(SeekGraphClick(Press, event->xbutton.x, event->xbutton.y, 1)) break;
3704       default:
3705         return;
3706     }
3707 }
3708 /* end why */
3709
3710 // [HGM] seekgraph: some low-level drawing routines cloned from xevalgraph
3711 void
3712 DrawSeekAxis (int x, int y, int xTo, int yTo)
3713 {
3714       XDrawLine(xDisplay, xBoardWindow, lineGC, x, y, xTo, yTo);
3715 }
3716
3717 void
3718 DrawSeekBackground (int left, int top, int right, int bottom)
3719 {
3720     XFillRectangle(xDisplay, xBoardWindow, lightSquareGC, left, top, right-left, bottom-top);
3721 }
3722
3723 void
3724 DrawSeekText (char *buf, int x, int y)
3725 {
3726     XDrawString(xDisplay, xBoardWindow, coordGC, x, y+4, buf, strlen(buf));
3727 }
3728
3729 void
3730 DrawSeekDot (int x, int y, int colorNr)
3731 {
3732     int square = colorNr & 0x80;
3733     GC color;
3734     colorNr &= 0x7F;
3735     color = colorNr == 0 ? prelineGC : colorNr == 1 ? darkSquareGC : highlineGC;
3736     if(square)
3737         XFillRectangle(xDisplay, xBoardWindow, color,
3738                 x-squareSize/9, y-squareSize/9, 2*squareSize/9, 2*squareSize/9);
3739     else
3740         XFillArc(xDisplay, xBoardWindow, color,
3741                 x-squareSize/8, y-squareSize/8, squareSize/4, squareSize/4, 0, 64*360);
3742 }
3743
3744 void
3745 DrawGrid (int second)
3746 {
3747           XDrawSegments(xDisplay, xBoardWindow, lineGC,
3748                         second ? secondSegments : // [HGM] dual
3749                         gridSegments, BOARD_HEIGHT + BOARD_WIDTH + 2);
3750 }
3751
3752
3753 /*
3754  * event handler for redrawing the board
3755  */
3756 void
3757 DrawPositionProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
3758 {
3759     DrawPosition(True, NULL);
3760 }
3761
3762
3763 /*
3764  * event handler for parsing user moves
3765  */
3766 // [HGM] This routine will need quite some reworking. Although the backend still supports the old
3767 //       way of doing things, by calling UserMoveEvent() to test the legality of the move and then perform
3768 //       it at the end, and doing all kind of preliminary tests here (e.g. to weed out self-captures), it
3769 //       should be made to use the new way, of calling UserMoveTest early  to determine the legality of the
3770 //       move, (which will weed out the illegal selfcaptures and moves into the holdings, and flag promotions),
3771 //       and at the end FinishMove() to perform the move after optional promotion popups.
3772 //       For now I patched it to allow self-capture with King, and suppress clicks between board and holdings.
3773 void
3774 HandleUserMove (Widget w, XEvent *event, String *prms, Cardinal *nprms)
3775 {
3776     if (w != boardWidget || errorExitStatus != -1) return;
3777     if(nprms) shiftKey = !strcmp(prms[0], "1");
3778
3779     if (promotionUp) {
3780         if (event->type == ButtonPress) {
3781             XtPopdown(promotionShell);
3782             XtDestroyWidget(promotionShell);
3783             promotionUp = False;
3784             ClearHighlights();
3785             fromX = fromY = -1;
3786         } else {
3787             return;
3788         }
3789     }
3790
3791     // [HGM] mouse: the rest of the mouse handler is moved to the backend, and called here
3792     if(event->type == ButtonPress)   LeftClick(Press,   event->xbutton.x, event->xbutton.y);
3793     if(event->type == ButtonRelease) LeftClick(Release, event->xbutton.x, event->xbutton.y);
3794 }
3795
3796 void
3797 AnimateUserMove (Widget w, XEvent *event, String *params, Cardinal *nParams)
3798 {
3799     if(!PromoScroll(event->xmotion.x, event->xmotion.y))
3800     DragPieceMove(event->xmotion.x, event->xmotion.y);
3801 }
3802
3803 void
3804 HandlePV (Widget w, XEvent * event, String * params, Cardinal * nParams)
3805 {   // [HGM] pv: walk PV
3806     MovePV(event->xmotion.x, event->xmotion.y, lineGap + BOARD_HEIGHT * (squareSize + lineGap));
3807 }
3808
3809 static int savedIndex;  /* gross that this is global */
3810
3811 void
3812 CommentClick (Widget w, XEvent * event, String * params, Cardinal * nParams)
3813 {
3814         String val;
3815         XawTextPosition index, dummy;
3816         Arg arg;
3817
3818         XawTextGetSelectionPos(w, &index, &dummy);
3819         XtSetArg(arg, XtNstring, &val);
3820         XtGetValues(w, &arg, 1);
3821         ReplaceComment(savedIndex, val);
3822         if(savedIndex != currentMove) ToNrEvent(savedIndex);
3823         LoadVariation( index, val ); // [HGM] also does the actual moving to it, now
3824 }
3825
3826 void
3827 EditCommentPopUp (int index, char *title, char *text)
3828 {
3829     savedIndex = index;
3830     if (text == NULL) text = "";
3831     NewCommentPopup(title, text, index);
3832 }
3833
3834 void
3835 ICSInputBoxPopUp ()
3836 {
3837     InputBoxPopup();
3838 }
3839
3840 extern Option boxOptions[];
3841
3842 void
3843 ICSInputSendText ()
3844 {
3845     Widget edit;
3846     int j;
3847     Arg args[16];
3848     String val;
3849
3850     edit = boxOptions[0].handle;
3851     j = 0;
3852     XtSetArg(args[j], XtNstring, &val); j++;
3853     XtGetValues(edit, args, j);
3854     SaveInHistory(val);
3855     SendMultiLineToICS(val);
3856     XtCallActionProc(edit, "select-all", NULL, NULL, 0);
3857     XtCallActionProc(edit, "kill-selection", NULL, NULL, 0);
3858 }
3859
3860 void
3861 ICSInputBoxPopDown ()
3862 {
3863     PopDown(4);
3864 }
3865
3866 void
3867 CommentPopUp (char *title, char *text)
3868 {
3869     savedIndex = currentMove; // [HGM] vari
3870     NewCommentPopup(title, text, currentMove);
3871 }
3872
3873 void
3874 CommentPopDown ()
3875 {
3876     PopDown(1);
3877 }
3878
3879 static char *openName;
3880 FILE *openFP;
3881
3882 void
3883 DelayedLoad ()
3884 {
3885   (void) (*fileProc)(openFP, 0, openName);
3886 }
3887
3888 void
3889 FileNamePopUp (char *label, char *def, char *filter, FileProc proc, char *openMode)
3890 {
3891     fileProc = proc;            /* I can't see a way not */
3892     fileOpenMode = openMode;    /*   to use globals here */
3893     {   // [HGM] use file-selector dialog stolen from Ghostview
3894         int index; // this is not supported yet
3895         if(openFP = XsraSelFile(shellWidget, label, NULL, NULL, _("could not open: "),
3896                            (def[0] ? def : NULL), filter, openMode, NULL, &openName))
3897           // [HGM] delay to give expose event opportunity to redraw board after browser-dialog popdown before lengthy load starts
3898           ScheduleDelayedEvent(&DelayedLoad, 50);
3899     }
3900 }
3901
3902 void
3903 FileNamePopDown ()
3904 {
3905     if (!filenameUp) return;
3906     XtPopdown(fileNameShell);
3907     XtDestroyWidget(fileNameShell);
3908     filenameUp = False;
3909     ModeHighlight();
3910 }
3911
3912 void
3913 FileNameCallback (Widget w, XtPointer client_data, XtPointer call_data)
3914 {
3915     String name;
3916     Arg args[16];
3917
3918     XtSetArg(args[0], XtNlabel, &name);
3919     XtGetValues(w, args, 1);
3920
3921     if (strcmp(name, _("cancel")) == 0) {
3922         FileNamePopDown();
3923         return;
3924     }
3925
3926     FileNameAction(w, NULL, NULL, NULL);
3927 }
3928
3929 void
3930 FileNameAction (Widget w, XEvent *event, String *prms, Cardinal *nprms)
3931 {
3932     char buf[MSG_SIZ];
3933     String name;
3934     FILE *f;
3935     char *p, *fullname;
3936     int index;
3937
3938     name = XawDialogGetValueString(w = XtParent(w));
3939
3940     if ((name != NULL) && (*name != NULLCHAR)) {
3941         safeStrCpy(buf, name, sizeof(buf)/sizeof(buf[0]) );
3942         XtPopdown(w = XtParent(XtParent(w)));
3943         XtDestroyWidget(w);
3944         filenameUp = False;
3945
3946         p = strrchr(buf, ' ');
3947         if (p == NULL) {
3948             index = 0;
3949         } else {
3950             *p++ = NULLCHAR;
3951             index = atoi(p);
3952         }
3953         fullname = ExpandPathName(buf);
3954         if (!fullname) {
3955             ErrorPopUp(_("Error"), _("Can't open file"), FALSE);
3956         }
3957         else {
3958             f = fopen(fullname, fileOpenMode);
3959             if (f == NULL) {
3960                 DisplayError(_("Failed to open file"), errno);
3961             } else {
3962                 (void) (*fileProc)(f, index, buf);
3963             }
3964         }
3965         ModeHighlight();
3966         return;
3967     }
3968
3969     XtPopdown(w = XtParent(XtParent(w)));
3970     XtDestroyWidget(w);
3971     filenameUp = False;
3972     ModeHighlight();
3973 }
3974
3975 void
3976 PromotionPopUp ()
3977 {
3978     Arg args[16];
3979     Widget dialog, layout;
3980     Position x, y;
3981     Dimension bw_width, pw_width;
3982     int j;
3983     char *PromoChars = "wglcqrbnkac+=\0";
3984
3985     j = 0;
3986     XtSetArg(args[j], XtNwidth, &bw_width); j++;
3987     XtGetValues(boardWidget, args, j);
3988
3989     j = 0;
3990     XtSetArg(args[j], XtNresizable, True); j++;
3991     XtSetArg(args[j], XtNtitle, XtNewString(_("Promotion"))); j++;
3992     promotionShell =
3993       XtCreatePopupShell("Promotion", transientShellWidgetClass,
3994                          shellWidget, args, j);
3995     layout =
3996       XtCreateManagedWidget(layoutName, formWidgetClass, promotionShell,
3997                             layoutArgs, XtNumber(layoutArgs));
3998
3999     j = 0;
4000     XtSetArg(args[j], XtNlabel, _("Promote to what?")); j++;
4001     XtSetArg(args[j], XtNborderWidth, 0); j++;
4002     dialog = XtCreateManagedWidget("promotion", dialogWidgetClass,
4003                                    layout, args, j);
4004
4005   if(gameInfo.variant != VariantShogi) {
4006    if(gameInfo.variant == VariantSpartan && !WhiteOnMove(currentMove)) {
4007       XawDialogAddButton(dialog, _("Warlord"), PromotionCallback, PromoChars + 0);
4008       XawDialogAddButton(dialog, _("General"), PromotionCallback, PromoChars + 1);
4009       XawDialogAddButton(dialog, _("Lieutenant"), PromotionCallback, PromoChars + 2);
4010       XawDialogAddButton(dialog, _("Captain"), PromotionCallback, PromoChars + 3);
4011     } else {
4012     XawDialogAddButton(dialog, _("Queen"), PromotionCallback, PromoChars + 4);
4013     XawDialogAddButton(dialog, _("Rook"), PromotionCallback, PromoChars + 5);
4014     XawDialogAddButton(dialog, _("Bishop"), PromotionCallback, PromoChars + 6);
4015     XawDialogAddButton(dialog, _("Knight"), PromotionCallback, PromoChars + 7);
4016     }
4017     if (!appData.testLegality || gameInfo.variant == VariantSuicide ||
4018         gameInfo.variant == VariantSpartan && !WhiteOnMove(currentMove) ||
4019         gameInfo.variant == VariantGiveaway) {
4020       XawDialogAddButton(dialog, _("King"), PromotionCallback, PromoChars + 8);
4021     }
4022     if(gameInfo.variant == VariantCapablanca ||
4023        gameInfo.variant == VariantGothic ||
4024        gameInfo.variant == VariantCapaRandom) {
4025       XawDialogAddButton(dialog, _("Archbishop"), PromotionCallback, PromoChars + 9);
4026       XawDialogAddButton(dialog, _("Chancellor"), PromotionCallback, PromoChars + 10);
4027     }
4028   } else // [HGM] shogi
4029   {
4030       XawDialogAddButton(dialog, _("Promote"), PromotionCallback, PromoChars + 11);
4031       XawDialogAddButton(dialog, _("Defer"), PromotionCallback, PromoChars + 12);
4032   }
4033     XawDialogAddButton(dialog, _("cancel"), PromotionCallback, PromoChars + 13);
4034
4035     XtRealizeWidget(promotionShell);
4036     CatchDeleteWindow(promotionShell, "PromotionPopDown");
4037
4038     j = 0;
4039     XtSetArg(args[j], XtNwidth, &pw_width); j++;
4040     XtGetValues(promotionShell, args, j);
4041
4042     XtTranslateCoords(boardWidget, (bw_width - pw_width) / 2,
4043                       lineGap + squareSize/3 +
4044                       ((toY == BOARD_HEIGHT-1) ^ (flipView) ?
4045                        0 : 6*(squareSize + lineGap)), &x, &y);
4046
4047     j = 0;
4048     XtSetArg(args[j], XtNx, x); j++;
4049     XtSetArg(args[j], XtNy, y); j++;
4050     XtSetValues(promotionShell, args, j);
4051
4052     XtPopup(promotionShell, XtGrabNone);
4053
4054     promotionUp = True;
4055 }
4056
4057 void
4058 PromotionPopDown ()
4059 {
4060     if (!promotionUp) return;
4061     XtPopdown(promotionShell);
4062     XtDestroyWidget(promotionShell);
4063     promotionUp = False;
4064 }
4065
4066 void
4067 PromotionCallback (Widget w, XtPointer client_data, XtPointer call_data)
4068 {
4069     int promoChar = * (const char *) client_data;
4070
4071     PromotionPopDown();
4072
4073     if (fromX == -1) return;
4074
4075     if (! promoChar) {
4076         fromX = fromY = -1;
4077         ClearHighlights();
4078         return;
4079     }
4080     UserMoveEvent(fromX, fromY, toX, toY, promoChar);
4081
4082     if (!appData.highlightLastMove || gotPremove) ClearHighlights();
4083     if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
4084     fromX = fromY = -1;
4085 }
4086
4087
4088 void
4089 ErrorCallback (Widget w, XtPointer client_data, XtPointer call_data)
4090 {
4091     dialogError = errorUp = False;
4092     XtPopdown(w = XtParent(XtParent(XtParent(w))));
4093     XtDestroyWidget(w);
4094     if (errorExitStatus != -1) ExitEvent(errorExitStatus);
4095 }
4096
4097
4098 void
4099 ErrorPopDown ()
4100 {
4101     if (!errorUp) return;
4102     dialogError = errorUp = False;
4103     XtPopdown(errorShell);
4104     XtDestroyWidget(errorShell);
4105     if (errorExitStatus != -1) ExitEvent(errorExitStatus);
4106 }
4107
4108 void
4109 ErrorPopUp (char *title, char *label, int modal)
4110 {
4111     Arg args[16];
4112     Widget dialog, layout;
4113     Position x, y;
4114     int xx, yy;
4115     Window junk;
4116     Dimension bw_width, pw_width;
4117     Dimension pw_height;
4118     int i;
4119
4120     i = 0;
4121     XtSetArg(args[i], XtNresizable, True);  i++;
4122     XtSetArg(args[i], XtNtitle, title); i++;
4123     errorShell =
4124       XtCreatePopupShell("errorpopup", transientShellWidgetClass,
4125                          shellUp[0] ? (dialogError = modal = TRUE, shells[0]) : shellWidget, args, i);
4126     layout =
4127       XtCreateManagedWidget(layoutName, formWidgetClass, errorShell,
4128                             layoutArgs, XtNumber(layoutArgs));
4129
4130     i = 0;
4131     XtSetArg(args[i], XtNlabel, label); i++;
4132     XtSetArg(args[i], XtNborderWidth, 0); i++;
4133     dialog = XtCreateManagedWidget("dialog", dialogWidgetClass,
4134                                    layout, args, i);
4135
4136     XawDialogAddButton(dialog, _("ok"), ErrorCallback, (XtPointer) dialog);
4137
4138     XtRealizeWidget(errorShell);
4139     CatchDeleteWindow(errorShell, "ErrorPopDown");
4140
4141     i = 0;
4142     XtSetArg(args[i], XtNwidth, &bw_width);  i++;
4143     XtGetValues(boardWidget, args, i);
4144     i = 0;
4145     XtSetArg(args[i], XtNwidth, &pw_width);  i++;
4146     XtSetArg(args[i], XtNheight, &pw_height);  i++;
4147     XtGetValues(errorShell, args, i);
4148
4149 #ifdef NOTDEF
4150     /* This code seems to tickle an X bug if it is executed too soon
4151        after xboard starts up.  The coordinates get transformed as if
4152        the main window was positioned at (0, 0).
4153        */
4154     XtTranslateCoords(boardWidget, (bw_width - pw_width) / 2,
4155                       0 - pw_height + squareSize / 3, &x, &y);
4156 #else
4157     XTranslateCoordinates(xDisplay, XtWindow(boardWidget),
4158                           RootWindowOfScreen(XtScreen(boardWidget)),
4159                           (bw_width - pw_width) / 2,
4160                           0 - pw_height + squareSize / 3, &xx, &yy, &junk);
4161     x = xx;
4162     y = yy;
4163 #endif
4164     if (y < 0) y = 0; /*avoid positioning top offscreen*/
4165
4166     i = 0;
4167     XtSetArg(args[i], XtNx, x);  i++;
4168     XtSetArg(args[i], XtNy, y);  i++;
4169     XtSetValues(errorShell, args, i);
4170
4171     errorUp = True;
4172     XtPopup(errorShell, modal ? XtGrabExclusive : XtGrabNone);
4173 }
4174
4175 /* Disable all user input other than deleting the window */
4176 static int frozen = 0;
4177
4178 void
4179 FreezeUI ()
4180 {
4181   if (frozen) return;
4182   /* Grab by a widget that doesn't accept input */
4183   XtAddGrab(messageWidget, TRUE, FALSE);
4184   frozen = 1;
4185 }
4186
4187 /* Undo a FreezeUI */
4188 void
4189 ThawUI ()
4190 {
4191   if (!frozen) return;
4192   XtRemoveGrab(messageWidget);
4193   frozen = 0;
4194 }
4195
4196 void
4197 ModeHighlight ()
4198 {
4199     Arg args[16];
4200     static int oldPausing = FALSE;
4201     static GameMode oldmode = (GameMode) -1;
4202     char *wname;
4203
4204     if (!boardWidget || !XtIsRealized(boardWidget)) return;
4205
4206     if (pausing != oldPausing) {
4207         oldPausing = pausing;
4208         MarkMenuItem("Pause", pausing);
4209
4210         if (appData.showButtonBar) {
4211           /* Always toggle, don't set.  Previous code messes up when
4212              invoked while the button is pressed, as releasing it
4213              toggles the state again. */
4214           {
4215             Pixel oldbg, oldfg;
4216             XtSetArg(args[0], XtNbackground, &oldbg);
4217             XtSetArg(args[1], XtNforeground, &oldfg);
4218             XtGetValues(XtNameToWidget(buttonBarWidget, PAUSE_BUTTON),
4219                         args, 2);
4220             XtSetArg(args[0], XtNbackground, oldfg);
4221             XtSetArg(args[1], XtNforeground, oldbg);
4222           }
4223           XtSetValues(XtNameToWidget(buttonBarWidget, PAUSE_BUTTON), args, 2);
4224         }
4225     }
4226
4227     wname = ModeToWidgetName(oldmode);
4228     if (wname != NULL) {
4229         MarkMenuItem(wname, False);
4230     }
4231     wname = ModeToWidgetName(gameMode);
4232     if (wname != NULL) {
4233         MarkMenuItem(wname, True);
4234     }
4235     oldmode = gameMode;
4236     MarkMenuItem("Machine Match", matchMode && matchGame < appData.matchGames);
4237
4238     /* Maybe all the enables should be handled here, not just this one */
4239     EnableMenuItem("Training", gameMode == Training || gameMode == PlayFromGameFile);
4240 }
4241
4242
4243 /*
4244  * Button/menu procedures
4245  */
4246 int
4247 LoadGamePopUp (FILE *f, int gameNumber, char *title)
4248 {
4249     cmailMsgLoaded = FALSE;
4250     if (gameNumber == 0) {
4251         int error = GameListBuild(f);
4252         if (error) {
4253             DisplayError(_("Cannot build game list"), error);
4254         } else if (!ListEmpty(&gameList) &&
4255                    ((ListGame *) gameList.tailPred)->number > 1) {
4256             GameListPopUp(f, title);
4257             return TRUE;
4258         }
4259         GameListDestroy();
4260         gameNumber = 1;
4261     }
4262     return LoadGame(f, gameNumber, title, FALSE);
4263 }
4264
4265 /* this variable is shared between CopyPositionProc and SendPositionSelection */
4266 char *selected_fen_position=NULL;
4267
4268 Boolean
4269 SendPositionSelection (Widget w, Atom *selection, Atom *target,
4270                        Atom *type_return, XtPointer *value_return,
4271                        unsigned long *length_return, int *format_return)
4272 {
4273   char *selection_tmp;
4274
4275   if (!selected_fen_position) return False; /* should never happen */
4276   if (*target == XA_STRING || *target == XA_UTF8_STRING(xDisplay)){
4277     /* note: since no XtSelectionDoneProc was registered, Xt will
4278      * automatically call XtFree on the value returned.  So have to
4279      * make a copy of it allocated with XtMalloc */
4280     selection_tmp= XtMalloc(strlen(selected_fen_position)+16);
4281     safeStrCpy(selection_tmp, selected_fen_position, strlen(selected_fen_position)+16 );
4282
4283     *value_return=selection_tmp;
4284     *length_return=strlen(selection_tmp);
4285     *type_return=*target;
4286     *format_return = 8; /* bits per byte */
4287     return True;
4288   } else if (*target == XA_TARGETS(xDisplay)) {
4289     Atom *targets_tmp = (Atom *) XtMalloc(2 * sizeof(Atom));
4290     targets_tmp[0] = XA_UTF8_STRING(xDisplay);
4291     targets_tmp[1] = XA_STRING;
4292     *value_return = targets_tmp;
4293     *type_return = XA_ATOM;
4294     *length_return = 2;
4295 #if 0
4296     // This code leads to a read of value_return out of bounds on 64-bit systems.
4297     // Other code which I have seen always sets *format_return to 32 independent of
4298     // sizeof(Atom) without adjusting *length_return. For instance see TextConvertSelection()
4299     // at http://cgit.freedesktop.org/xorg/lib/libXaw/tree/src/Text.c -- BJ
4300     *format_return = 8 * sizeof(Atom);
4301     if (*format_return > 32) {
4302       *length_return *= *format_return / 32;
4303       *format_return = 32;
4304     }
4305 #else
4306     *format_return = 32;
4307 #endif
4308     return True;
4309   } else {
4310     return False;
4311   }
4312 }
4313
4314 /* note: when called from menu all parameters are NULL, so no clue what the
4315  * Widget which was clicked on was, or what the click event was
4316  */
4317 void
4318 CopyPositionProc ()
4319 {
4320     /*
4321      * Set both PRIMARY (the selection) and CLIPBOARD, since we don't
4322      * have a notion of a position that is selected but not copied.
4323      * See http://www.freedesktop.org/wiki/Specifications/ClipboardsWiki
4324      */
4325     if(gameMode == EditPosition) EditPositionDone(TRUE);
4326     if (selected_fen_position) free(selected_fen_position);
4327     selected_fen_position = (char *)PositionToFEN(currentMove, NULL);
4328     if (!selected_fen_position) return;
4329     XtOwnSelection(menuBarWidget, XA_PRIMARY,
4330                    CurrentTime,
4331                    SendPositionSelection,
4332                    NULL/* lose_ownership_proc */ ,
4333                    NULL/* transfer_done_proc */);
4334     XtOwnSelection(menuBarWidget, XA_CLIPBOARD(xDisplay),
4335                    CurrentTime,
4336                    SendPositionSelection,
4337                    NULL/* lose_ownership_proc */ ,
4338                    NULL/* transfer_done_proc */);
4339 }
4340
4341 /* function called when the data to Paste is ready */
4342 static void
4343 PastePositionCB (Widget w, XtPointer client_data, Atom *selection,
4344                  Atom *type, XtPointer value, unsigned long *len, int *format)
4345 {
4346   char *fenstr=value;
4347   if (value==NULL || *len==0) return; /* nothing had been selected to copy */
4348   fenstr[*len]='\0'; /* normally this string is terminated, but be safe */
4349   EditPositionPasteFEN(fenstr);
4350   XtFree(value);
4351 }
4352
4353 /* called when Paste Position button is pressed,
4354  * all parameters will be NULL */
4355 void
4356 PastePositionProc ()
4357 {
4358     XtGetSelectionValue(menuBarWidget,
4359       appData.pasteSelection ? XA_PRIMARY: XA_CLIPBOARD(xDisplay), XA_STRING,
4360       /* (XtSelectionCallbackProc) */ PastePositionCB,
4361       NULL, /* client_data passed to PastePositionCB */
4362
4363       /* better to use the time field from the event that triggered the
4364        * call to this function, but that isn't trivial to get
4365        */
4366       CurrentTime
4367     );
4368     return;
4369 }
4370
4371 static Boolean
4372 SendGameSelection (Widget w, Atom *selection, Atom *target,
4373                    Atom *type_return, XtPointer *value_return,
4374                    unsigned long *length_return, int *format_return)
4375 {
4376   char *selection_tmp;
4377
4378   if (*target == XA_STRING || *target == XA_UTF8_STRING(xDisplay)){
4379     FILE* f = fopen(gameCopyFilename, "r");
4380     long len;
4381     size_t count;
4382     if (f == NULL) return False;
4383     fseek(f, 0, 2);
4384     len = ftell(f);
4385     rewind(f);
4386     selection_tmp = XtMalloc(len + 1);
4387     count = fread(selection_tmp, 1, len, f);
4388     fclose(f);
4389     if (len != count) {
4390       XtFree(selection_tmp);
4391       return False;
4392     }
4393     selection_tmp[len] = NULLCHAR;
4394     *value_return = selection_tmp;
4395     *length_return = len;
4396     *type_return = *target;
4397     *format_return = 8; /* bits per byte */
4398     return True;
4399   } else if (*target == XA_TARGETS(xDisplay)) {
4400     Atom *targets_tmp = (Atom *) XtMalloc(2 * sizeof(Atom));
4401     targets_tmp[0] = XA_UTF8_STRING(xDisplay);
4402     targets_tmp[1] = XA_STRING;
4403     *value_return = targets_tmp;
4404     *type_return = XA_ATOM;
4405     *length_return = 2;
4406 #if 0
4407     // This code leads to a read of value_return out of bounds on 64-bit systems.
4408     // Other code which I have seen always sets *format_return to 32 independent of
4409     // sizeof(Atom) without adjusting *length_return. For instance see TextConvertSelection()
4410     // at http://cgit.freedesktop.org/xorg/lib/libXaw/tree/src/Text.c -- BJ
4411     *format_return = 8 * sizeof(Atom);
4412     if (*format_return > 32) {
4413       *length_return *= *format_return / 32;
4414       *format_return = 32;
4415     }
4416 #else
4417     *format_return = 32;
4418 #endif
4419     return True;
4420   } else {
4421     return False;
4422   }
4423 }
4424
4425 void
4426 CopySomething ()
4427 {
4428   /*
4429    * Set both PRIMARY (the selection) and CLIPBOARD, since we don't
4430    * have a notion of a game that is selected but not copied.
4431    * See http://www.freedesktop.org/wiki/Specifications/ClipboardsWiki
4432    */
4433   XtOwnSelection(menuBarWidget, XA_PRIMARY,
4434                  CurrentTime,
4435                  SendGameSelection,
4436                  NULL/* lose_ownership_proc */ ,
4437                  NULL/* transfer_done_proc */);
4438   XtOwnSelection(menuBarWidget, XA_CLIPBOARD(xDisplay),
4439                  CurrentTime,
4440                  SendGameSelection,
4441                  NULL/* lose_ownership_proc */ ,
4442                  NULL/* transfer_done_proc */);
4443 }
4444
4445 /* note: when called from menu all parameters are NULL, so no clue what the
4446  * Widget which was clicked on was, or what the click event was
4447  */
4448 /* function called when the data to Paste is ready */
4449 static void
4450 PasteGameCB (Widget w, XtPointer client_data, Atom *selection,
4451              Atom *type, XtPointer value, unsigned long *len, int *format)
4452 {
4453   FILE* f;
4454   if (value == NULL || *len == 0) {
4455     return; /* nothing had been selected to copy */
4456   }
4457   f = fopen(gamePasteFilename, "w");
4458   if (f == NULL) {
4459     DisplayError(_("Can't open temp file"), errno);
4460     return;
4461   }
4462   fwrite(value, 1, *len, f);
4463   fclose(f);
4464   XtFree(value);
4465   LoadGameFromFile(gamePasteFilename, 0, gamePasteFilename, TRUE);
4466 }
4467
4468 /* called when Paste Game button is pressed,
4469  * all parameters will be NULL */
4470 void
4471 PasteGameProc ()
4472 {
4473     XtGetSelectionValue(menuBarWidget,
4474       appData.pasteSelection ? XA_PRIMARY: XA_CLIPBOARD(xDisplay), XA_STRING,
4475       /* (XtSelectionCallbackProc) */ PasteGameCB,
4476       NULL, /* client_data passed to PasteGameCB */
4477
4478       /* better to use the time field from the event that triggered the
4479        * call to this function, but that isn't trivial to get
4480        */
4481       CurrentTime
4482     );
4483     return;
4484 }
4485
4486
4487 void
4488 QuitWrapper (Widget w, XEvent *event, String *prms, Cardinal *nprms)
4489 {
4490     QuitProc();
4491 }
4492
4493 void
4494 UpKeyProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
4495 {   // [HGM] input: let up-arrow recall previous line from history
4496     Widget edit;
4497     int j;
4498     Arg args[16];
4499     String val;
4500     XawTextBlock t;
4501
4502     if (!shellUp[4]) return;
4503     edit = boxOptions[0].handle;
4504     j = 0;
4505     XtSetArg(args[j], XtNstring, &val); j++;
4506     XtGetValues(edit, args, j);
4507     val = PrevInHistory(val);
4508     XtCallActionProc(edit, "select-all", NULL, NULL, 0);
4509     XtCallActionProc(edit, "kill-selection", NULL, NULL, 0);
4510     if(val) {
4511         t.ptr = val; t.firstPos = 0; t.length = strlen(val); t.format = XawFmt8Bit;
4512         XawTextReplace(edit, 0, 0, &t);
4513         XawTextSetInsertionPoint(edit, 9999);
4514     }
4515 }
4516
4517 void
4518 DownKeyProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
4519 {   // [HGM] input: let down-arrow recall next line from history
4520     Widget edit;
4521     String val;
4522     XawTextBlock t;
4523
4524     if (!shellUp[4]) return;
4525     edit = boxOptions[0].handle;
4526     val = NextInHistory();
4527     XtCallActionProc(edit, "select-all", NULL, NULL, 0);
4528     XtCallActionProc(edit, "kill-selection", NULL, NULL, 0);
4529     if(val) {
4530         t.ptr = val; t.firstPos = 0; t.length = strlen(val); t.format = XawFmt8Bit;
4531         XawTextReplace(edit, 0, 0, &t);
4532         XawTextSetInsertionPoint(edit, 9999);
4533     }
4534 }
4535
4536 void
4537 EnterKeyProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
4538 {
4539     if (shellUp[4] == True)
4540       ICSInputSendText();
4541 }
4542
4543 void
4544 TempBackwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
4545 {
4546         if (!TempBackwardActive) {
4547                 TempBackwardActive = True;
4548                 BackwardEvent();
4549         }
4550 }
4551
4552 void
4553 TempForwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
4554 {
4555         /* Check to see if triggered by a key release event for a repeating key.
4556          * If so the next queued event will be a key press of the same key at the same time */
4557         if (XEventsQueued(xDisplay, QueuedAfterReading)) {
4558                 XEvent next;
4559                 XPeekEvent(xDisplay, &next);
4560                 if (next.type == KeyPress && next.xkey.time == event->xkey.time &&
4561                         next.xkey.keycode == event->xkey.keycode)
4562                                 return;
4563         }
4564     ForwardEvent();
4565         TempBackwardActive = False;
4566 }
4567
4568 void
4569 ManInner (Widget w, XEvent *event, String *prms, Cardinal *nprms)
4570 {   // called as key binding
4571     char buf[MSG_SIZ];
4572     String name;
4573     if (nprms && *nprms > 0)
4574       name = prms[0];
4575     else
4576       name = "xboard";
4577     snprintf(buf, sizeof(buf), "xterm -e man %s &", name);
4578     system(buf);
4579 }
4580
4581 void
4582 DisplayMessage (char *message, char *extMessage)
4583 {
4584   /* display a message in the message widget */
4585
4586   char buf[MSG_SIZ];
4587   Arg arg;
4588
4589   if (extMessage)
4590     {
4591       if (*message)
4592         {
4593           snprintf(buf, sizeof(buf), "%s  %s", message, extMessage);
4594           message = buf;
4595         }
4596       else
4597         {
4598           message = extMessage;
4599         };
4600     };
4601
4602     safeStrCpy(lastMsg, message, MSG_SIZ); // [HGM] make available
4603
4604   /* need to test if messageWidget already exists, since this function
4605      can also be called during the startup, if for example a Xresource
4606      is not set up correctly */
4607   if(messageWidget)
4608     {
4609       XtSetArg(arg, XtNlabel, message);
4610       XtSetValues(messageWidget, &arg, 1);
4611     };
4612
4613   return;
4614 }
4615
4616 void
4617 DisplayTitle (char *text)
4618 {
4619     Arg args[16];
4620     int i;
4621     char title[MSG_SIZ];
4622     char icon[MSG_SIZ];
4623
4624     if (text == NULL) text = "";
4625
4626     if (appData.titleInWindow) {
4627         i = 0;
4628         XtSetArg(args[i], XtNlabel, text);   i++;
4629         XtSetValues(titleWidget, args, i);
4630     }
4631
4632     if (*text != NULLCHAR) {
4633       safeStrCpy(icon, text, sizeof(icon)/sizeof(icon[0]) );
4634       safeStrCpy(title, text, sizeof(title)/sizeof(title[0]) );
4635     } else if (appData.icsActive) {
4636         snprintf(icon, sizeof(icon), "%s", appData.icsHost);
4637         snprintf(title, sizeof(title), "%s: %s", programName, appData.icsHost);
4638     } else if (appData.cmailGameName[0] != NULLCHAR) {
4639         snprintf(icon, sizeof(icon), "%s", "CMail");
4640         snprintf(title,sizeof(title), "%s: %s", programName, "CMail");
4641 #ifdef GOTHIC
4642     // [HGM] license: This stuff should really be done in back-end, but WinBoard already had a pop-up for it
4643     } else if (gameInfo.variant == VariantGothic) {
4644       safeStrCpy(icon,  programName, sizeof(icon)/sizeof(icon[0]) );
4645       safeStrCpy(title, GOTHIC,     sizeof(title)/sizeof(title[0]) );
4646 #endif
4647 #ifdef FALCON
4648     } else if (gameInfo.variant == VariantFalcon) {
4649       safeStrCpy(icon, programName, sizeof(icon)/sizeof(icon[0]) );
4650       safeStrCpy(title, FALCON, sizeof(title)/sizeof(title[0]) );
4651 #endif
4652     } else if (appData.noChessProgram) {
4653       safeStrCpy(icon, programName, sizeof(icon)/sizeof(icon[0]) );
4654       safeStrCpy(title, programName, sizeof(title)/sizeof(title[0]) );
4655     } else {
4656       safeStrCpy(icon, first.tidy, sizeof(icon)/sizeof(icon[0]) );
4657         snprintf(title,sizeof(title), "%s: %s", programName, first.tidy);
4658     }
4659     i = 0;
4660     XtSetArg(args[i], XtNiconName, (XtArgVal) icon);    i++;
4661     XtSetArg(args[i], XtNtitle, (XtArgVal) title);      i++;
4662     XtSetValues(shellWidget, args, i);
4663     XSync(xDisplay, False);
4664 }
4665
4666
4667 void
4668 DisplayError (String message, int error)
4669 {
4670     char buf[MSG_SIZ];
4671
4672     if (error == 0) {
4673         if (appData.debugMode || appData.matchMode) {
4674             fprintf(stderr, "%s: %s\n", programName, message);
4675         }
4676     } else {
4677         if (appData.debugMode || appData.matchMode) {
4678             fprintf(stderr, "%s: %s: %s\n",
4679                     programName, message, strerror(error));
4680         }
4681         snprintf(buf, sizeof(buf), "%s: %s", message, strerror(error));
4682         message = buf;
4683     }
4684     ErrorPopUp(_("Error"), message, FALSE);
4685 }
4686
4687
4688 void
4689 DisplayMoveError (String message)
4690 {
4691     fromX = fromY = -1;
4692     ClearHighlights();
4693     DrawPosition(FALSE, NULL);
4694     if (appData.debugMode || appData.matchMode) {
4695         fprintf(stderr, "%s: %s\n", programName, message);
4696     }
4697     if (appData.popupMoveErrors) {
4698         ErrorPopUp(_("Error"), message, FALSE);
4699     } else {
4700         DisplayMessage(message, "");
4701     }
4702 }
4703
4704
4705 void
4706 DisplayFatalError (String message, int error, int status)
4707 {
4708     char buf[MSG_SIZ];
4709
4710     errorExitStatus = status;
4711     if (error == 0) {
4712         fprintf(stderr, "%s: %s\n", programName, message);
4713     } else {
4714         fprintf(stderr, "%s: %s: %s\n",
4715                 programName, message, strerror(error));
4716         snprintf(buf, sizeof(buf), "%s: %s", message, strerror(error));
4717         message = buf;
4718     }
4719     if (appData.popupExitMessage && boardWidget && XtIsRealized(boardWidget)) {
4720       ErrorPopUp(status ? _("Fatal Error") : _("Exiting"), message, TRUE);
4721     } else {
4722       ExitEvent(status);
4723     }
4724 }
4725
4726 void
4727 DisplayInformation (String message)
4728 {
4729     ErrorPopDown();
4730     ErrorPopUp(_("Information"), message, TRUE);
4731 }
4732
4733 void
4734 DisplayNote (String message)
4735 {
4736     ErrorPopDown();
4737     ErrorPopUp(_("Note"), message, FALSE);
4738 }
4739
4740 static int
4741 NullXErrorCheck (Display *dpy, XErrorEvent *error_event)
4742 {
4743     return 0;
4744 }
4745
4746 void
4747 DisplayIcsInteractionTitle (String message)
4748 {
4749   if (oldICSInteractionTitle == NULL) {
4750     /* Magic to find the old window title, adapted from vim */
4751     char *wina = getenv("WINDOWID");
4752     if (wina != NULL) {
4753       Window win = (Window) atoi(wina);
4754       Window root, parent, *children;
4755       unsigned int nchildren;
4756       int (*oldHandler)() = XSetErrorHandler(NullXErrorCheck);
4757       for (;;) {
4758         if (XFetchName(xDisplay, win, &oldICSInteractionTitle)) break;
4759         if (!XQueryTree(xDisplay, win, &root, &parent,
4760                         &children, &nchildren)) break;
4761         if (children) XFree((void *)children);
4762         if (parent == root || parent == 0) break;
4763         win = parent;
4764       }
4765       XSetErrorHandler(oldHandler);
4766     }
4767     if (oldICSInteractionTitle == NULL) {
4768       oldICSInteractionTitle = "xterm";
4769     }
4770   }
4771   printf("\033]0;%s\007", message);
4772   fflush(stdout);
4773 }
4774
4775 char pendingReplyPrefix[MSG_SIZ];
4776 ProcRef pendingReplyPR;
4777
4778 void
4779 AskQuestionProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
4780 {
4781     if (*nprms != 4) {
4782         fprintf(stderr, _("AskQuestionProc needed 4 parameters, got %d\n"),
4783                 *nprms);
4784         return;
4785     }
4786     AskQuestionEvent(prms[0], prms[1], prms[2], prms[3]);
4787 }
4788
4789 void
4790 AskQuestionPopDown ()
4791 {
4792     if (!askQuestionUp) return;
4793     XtPopdown(askQuestionShell);
4794     XtDestroyWidget(askQuestionShell);
4795     askQuestionUp = False;
4796 }
4797
4798 void
4799 AskQuestionReplyAction (Widget w, XEvent *event, String *prms, Cardinal *nprms)
4800 {
4801     char buf[MSG_SIZ];
4802     int err;
4803     String reply;
4804
4805     reply = XawDialogGetValueString(w = XtParent(w));
4806     safeStrCpy(buf, pendingReplyPrefix, sizeof(buf)/sizeof(buf[0]) );
4807     if (*buf) strncat(buf, " ", MSG_SIZ - strlen(buf) - 1);
4808     strncat(buf, reply, MSG_SIZ - strlen(buf) - 1);
4809     strncat(buf, "\n",  MSG_SIZ - strlen(buf) - 1);
4810     OutputToProcess(pendingReplyPR, buf, strlen(buf), &err);
4811     AskQuestionPopDown();
4812
4813     if (err) DisplayFatalError(_("Error writing to chess program"), err, 0);
4814 }
4815
4816 void
4817 AskQuestionCallback (Widget w, XtPointer client_data, XtPointer call_data)
4818 {
4819     String name;
4820     Arg args[16];
4821
4822     XtSetArg(args[0], XtNlabel, &name);
4823     XtGetValues(w, args, 1);
4824
4825     if (strcmp(name, _("cancel")) == 0) {
4826         AskQuestionPopDown();
4827     } else {
4828         AskQuestionReplyAction(w, NULL, NULL, NULL);
4829     }
4830 }
4831
4832 void
4833 AskQuestion (char *title, char *question, char *replyPrefix, ProcRef pr)
4834 {
4835     Arg args[16];
4836     Widget popup, layout, dialog, edit;
4837     Window root, child;
4838     int x, y, i;
4839     int win_x, win_y;
4840     unsigned int mask;
4841
4842     safeStrCpy(pendingReplyPrefix, replyPrefix, sizeof(pendingReplyPrefix)/sizeof(pendingReplyPrefix[0]) );
4843     pendingReplyPR = pr;
4844
4845     i = 0;
4846     XtSetArg(args[i], XtNresizable, True); i++;
4847     XtSetArg(args[i], XtNwidth, DIALOG_SIZE); i++;
4848     askQuestionShell = popup =
4849       XtCreatePopupShell(title, transientShellWidgetClass,
4850                          shellWidget, args, i);
4851
4852     layout =
4853       XtCreateManagedWidget(layoutName, formWidgetClass, popup,
4854                             layoutArgs, XtNumber(layoutArgs));
4855
4856     i = 0;
4857     XtSetArg(args[i], XtNlabel, question); i++;
4858     XtSetArg(args[i], XtNvalue, ""); i++;
4859     XtSetArg(args[i], XtNborderWidth, 0); i++;
4860     dialog = XtCreateManagedWidget("question", dialogWidgetClass,
4861                                    layout, args, i);
4862
4863     XawDialogAddButton(dialog, _("enter"), AskQuestionCallback,
4864                        (XtPointer) dialog);
4865     XawDialogAddButton(dialog, _("cancel"), AskQuestionCallback,
4866                        (XtPointer) dialog);
4867
4868     XtRealizeWidget(popup);
4869     CatchDeleteWindow(popup, "AskQuestionPopDown");
4870
4871     XQueryPointer(xDisplay, xBoardWindow, &root, &child,
4872                   &x, &y, &win_x, &win_y, &mask);
4873
4874     XtSetArg(args[0], XtNx, x - 10);
4875     XtSetArg(args[1], XtNy, y - 30);
4876     XtSetValues(popup, args, 2);
4877
4878     XtPopup(popup, XtGrabExclusive);
4879     askQuestionUp = True;
4880
4881     edit = XtNameToWidget(dialog, "*value");
4882     XtSetKeyboardFocus(popup, edit);
4883 }
4884
4885
4886 void
4887 PlaySound (char *name)
4888 {
4889   if (*name == NULLCHAR) {
4890     return;
4891   } else if (strcmp(name, "$") == 0) {
4892     putc(BELLCHAR, stderr);
4893   } else {
4894     char buf[2048];
4895     char *prefix = "", *sep = "";
4896     if(appData.soundProgram[0] == NULLCHAR) return;
4897     if(!strchr(name, '/')) { prefix = appData.soundDirectory; sep = "/"; }
4898     snprintf(buf, sizeof(buf), "%s '%s%s%s' &", appData.soundProgram, prefix, sep, name);
4899     system(buf);
4900   }
4901 }
4902
4903 void
4904 RingBell ()
4905 {
4906   PlaySound(appData.soundMove);
4907 }
4908
4909 void
4910 PlayIcsWinSound ()
4911 {
4912   PlaySound(appData.soundIcsWin);
4913 }
4914
4915 void
4916 PlayIcsLossSound ()
4917 {
4918   PlaySound(appData.soundIcsLoss);
4919 }
4920
4921 void
4922 PlayIcsDrawSound ()
4923 {
4924   PlaySound(appData.soundIcsDraw);
4925 }
4926
4927 void
4928 PlayIcsUnfinishedSound ()
4929 {
4930   PlaySound(appData.soundIcsUnfinished);
4931 }
4932
4933 void
4934 PlayAlarmSound ()
4935 {
4936   PlaySound(appData.soundIcsAlarm);
4937 }
4938
4939 void
4940 PlayTellSound ()
4941 {
4942   PlaySound(appData.soundTell);
4943 }
4944
4945 void
4946 EchoOn ()
4947 {
4948     system("stty echo");
4949     noEcho = False;
4950 }
4951
4952 void
4953 EchoOff ()
4954 {
4955     system("stty -echo");
4956     noEcho = True;
4957 }
4958
4959 void
4960 RunCommand (char *buf)
4961 {
4962     system(buf);
4963 }
4964
4965 void
4966 Colorize (ColorClass cc, int continuation)
4967 {
4968     char buf[MSG_SIZ];
4969     int count, outCount, error;
4970
4971     if (textColors[(int)cc].bg > 0) {
4972         if (textColors[(int)cc].fg > 0) {
4973           snprintf(buf, MSG_SIZ, "\033[0;%d;%d;%dm", textColors[(int)cc].attr,
4974                    textColors[(int)cc].fg, textColors[(int)cc].bg);
4975         } else {
4976           snprintf(buf, MSG_SIZ, "\033[0;%d;%dm", textColors[(int)cc].attr,
4977                    textColors[(int)cc].bg);
4978         }
4979     } else {
4980         if (textColors[(int)cc].fg > 0) {
4981           snprintf(buf, MSG_SIZ, "\033[0;%d;%dm", textColors[(int)cc].attr,
4982                     textColors[(int)cc].fg);
4983         } else {
4984           snprintf(buf, MSG_SIZ, "\033[0;%dm", textColors[(int)cc].attr);
4985         }
4986     }
4987     count = strlen(buf);
4988     outCount = OutputToProcess(NoProc, buf, count, &error);
4989     if (outCount < count) {
4990         DisplayFatalError(_("Error writing to display"), error, 1);
4991     }
4992
4993     if (continuation) return;
4994     switch (cc) {
4995     case ColorShout:
4996       PlaySound(appData.soundShout);
4997       break;
4998     case ColorSShout:
4999       PlaySound(appData.soundSShout);
5000       break;
5001     case ColorChannel1:
5002       PlaySound(appData.soundChannel1);
5003       break;
5004     case ColorChannel:
5005       PlaySound(appData.soundChannel);
5006       break;
5007     case ColorKibitz:
5008       PlaySound(appData.soundKibitz);
5009       break;
5010     case ColorTell:
5011       PlaySound(appData.soundTell);
5012       break;
5013     case ColorChallenge:
5014       PlaySound(appData.soundChallenge);
5015       break;
5016     case ColorRequest:
5017       PlaySound(appData.soundRequest);
5018       break;
5019     case ColorSeek:
5020       PlaySound(appData.soundSeek);
5021       break;
5022     case ColorNormal:
5023     case ColorNone:
5024     default:
5025       break;
5026     }
5027 }
5028
5029 char *
5030 UserName ()
5031 {
5032     return getpwuid(getuid())->pw_name;
5033 }
5034
5035 static char *
5036 ExpandPathName (char *path)
5037 {
5038     static char static_buf[4*MSG_SIZ];
5039     char *d, *s, buf[4*MSG_SIZ];
5040     struct passwd *pwd;
5041
5042     s = path;
5043     d = static_buf;
5044
5045     while (*s && isspace(*s))
5046       ++s;
5047
5048     if (!*s) {
5049         *d = 0;
5050         return static_buf;
5051     }
5052
5053     if (*s == '~') {
5054         if (*(s+1) == '/') {
5055           safeStrCpy(d, getpwuid(getuid())->pw_dir, 4*MSG_SIZ );
5056           strcat(d, s+1);
5057         }
5058         else {
5059           safeStrCpy(buf, s+1, sizeof(buf)/sizeof(buf[0]) );
5060           { char *p; if(p = strchr(buf, '/')) *p = 0; }
5061           pwd = getpwnam(buf);
5062           if (!pwd)
5063             {
5064               fprintf(stderr, _("ERROR: Unknown user %s (in path %s)\n"),
5065                       buf, path);
5066               return NULL;
5067             }
5068           safeStrCpy(d, pwd->pw_dir, 4*MSG_SIZ );
5069           strcat(d, strchr(s+1, '/'));
5070         }
5071     }
5072     else
5073       safeStrCpy(d, s, 4*MSG_SIZ );
5074
5075     return static_buf;
5076 }
5077
5078 char *
5079 HostName ()
5080 {
5081     static char host_name[MSG_SIZ];
5082
5083 #if HAVE_GETHOSTNAME
5084     gethostname(host_name, MSG_SIZ);
5085     return host_name;
5086 #else  /* not HAVE_GETHOSTNAME */
5087 # if HAVE_SYSINFO && HAVE_SYS_SYSTEMINFO_H
5088     sysinfo(SI_HOSTNAME, host_name, MSG_SIZ);
5089     return host_name;
5090 # else /* not (HAVE_SYSINFO && HAVE_SYS_SYSTEMINFO_H) */
5091     return "localhost";
5092 # endif/* not (HAVE_SYSINFO && HAVE_SYS_SYSTEMINFO_H) */
5093 #endif /* not HAVE_GETHOSTNAME */
5094 }
5095
5096 XtIntervalId delayedEventTimerXID = 0;
5097 DelayedEventCallback delayedEventCallback = 0;
5098
5099 void
5100 FireDelayedEvent ()
5101 {
5102     delayedEventTimerXID = 0;
5103     delayedEventCallback();
5104 }
5105
5106 void
5107 ScheduleDelayedEvent (DelayedEventCallback cb, long millisec)
5108 {
5109     if(delayedEventTimerXID && delayedEventCallback == cb)
5110         // [HGM] alive: replace, rather than add or flush identical event
5111         XtRemoveTimeOut(delayedEventTimerXID);
5112     delayedEventCallback = cb;
5113     delayedEventTimerXID =
5114       XtAppAddTimeOut(appContext, millisec,
5115                       (XtTimerCallbackProc) FireDelayedEvent, (XtPointer) 0);
5116 }
5117
5118 DelayedEventCallback
5119 GetDelayedEvent ()
5120 {
5121   if (delayedEventTimerXID) {
5122     return delayedEventCallback;
5123   } else {
5124     return NULL;
5125   }
5126 }
5127
5128 void
5129 CancelDelayedEvent ()
5130 {
5131   if (delayedEventTimerXID) {
5132     XtRemoveTimeOut(delayedEventTimerXID);
5133     delayedEventTimerXID = 0;
5134   }
5135 }
5136
5137 XtIntervalId loadGameTimerXID = 0;
5138
5139 int
5140 LoadGameTimerRunning ()
5141 {
5142     return loadGameTimerXID != 0;
5143 }
5144
5145 int
5146 StopLoadGameTimer ()
5147 {
5148     if (loadGameTimerXID != 0) {
5149         XtRemoveTimeOut(loadGameTimerXID);
5150         loadGameTimerXID = 0;
5151         return TRUE;
5152     } else {
5153         return FALSE;
5154     }
5155 }
5156
5157 void
5158 LoadGameTimerCallback (XtPointer arg, XtIntervalId *id)
5159 {
5160     loadGameTimerXID = 0;
5161     AutoPlayGameLoop();
5162 }
5163
5164 void
5165 StartLoadGameTimer (long millisec)
5166 {
5167     loadGameTimerXID =
5168       XtAppAddTimeOut(appContext, millisec,
5169                       (XtTimerCallbackProc) LoadGameTimerCallback,
5170                       (XtPointer) 0);
5171 }
5172
5173 XtIntervalId analysisClockXID = 0;
5174
5175 void
5176 AnalysisClockCallback (XtPointer arg, XtIntervalId *id)
5177 {
5178     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
5179          || appData.icsEngineAnalyze) { // [DM]
5180         AnalysisPeriodicEvent(0);
5181         StartAnalysisClock();
5182     }
5183 }
5184
5185 void
5186 StartAnalysisClock ()
5187 {
5188     analysisClockXID =
5189       XtAppAddTimeOut(appContext, 2000,
5190                       (XtTimerCallbackProc) AnalysisClockCallback,
5191                       (XtPointer) 0);
5192 }
5193
5194 XtIntervalId clockTimerXID = 0;
5195
5196 int
5197 ClockTimerRunning ()
5198 {
5199     return clockTimerXID != 0;
5200 }
5201
5202 int
5203 StopClockTimer ()
5204 {
5205     if (clockTimerXID != 0) {
5206         XtRemoveTimeOut(clockTimerXID);
5207         clockTimerXID = 0;
5208         return TRUE;
5209     } else {
5210         return FALSE;
5211     }
5212 }
5213
5214 void
5215 ClockTimerCallback (XtPointer arg, XtIntervalId *id)
5216 {
5217     clockTimerXID = 0;
5218     DecrementClocks();
5219 }
5220
5221 void
5222 StartClockTimer (long millisec)
5223 {
5224     clockTimerXID =
5225       XtAppAddTimeOut(appContext, millisec,
5226                       (XtTimerCallbackProc) ClockTimerCallback,
5227                       (XtPointer) 0);
5228 }
5229
5230 void
5231 DisplayTimerLabel (Widget w, char *color, long timer, int highlight)
5232 {
5233     char buf[MSG_SIZ];
5234     Arg args[16];
5235
5236     /* check for low time warning */
5237     Pixel foregroundOrWarningColor = timerForegroundPixel;
5238
5239     if (timer > 0 &&
5240         appData.lowTimeWarning &&
5241         (timer / 1000) < appData.icsAlarmTime)
5242       foregroundOrWarningColor = lowTimeWarningColor;
5243
5244     if (appData.clockMode) {
5245       snprintf(buf, MSG_SIZ, "%s: %s", color, TimeString(timer));
5246       XtSetArg(args[0], XtNlabel, buf);
5247     } else {
5248       snprintf(buf, MSG_SIZ, "%s  ", color);
5249       XtSetArg(args[0], XtNlabel, buf);
5250     }
5251
5252     if (highlight) {
5253
5254         XtSetArg(args[1], XtNbackground, foregroundOrWarningColor);
5255         XtSetArg(args[2], XtNforeground, timerBackgroundPixel);
5256     } else {
5257         XtSetArg(args[1], XtNbackground, timerBackgroundPixel);
5258         XtSetArg(args[2], XtNforeground, foregroundOrWarningColor);
5259     }
5260
5261     XtSetValues(w, args, 3);
5262 }
5263
5264 void
5265 DisplayWhiteClock (long timeRemaining, int highlight)
5266 {
5267     Arg args[16];
5268
5269     if(appData.noGUI) return;
5270     DisplayTimerLabel(whiteTimerWidget, _("White"), timeRemaining, highlight);
5271     if (highlight && iconPixmap == bIconPixmap) {
5272         iconPixmap = wIconPixmap;
5273         XtSetArg(args[0], XtNiconPixmap, iconPixmap);
5274         XtSetValues(shellWidget, args, 1);
5275     }
5276 }
5277
5278 void
5279 DisplayBlackClock (long timeRemaining, int highlight)
5280 {
5281     Arg args[16];
5282
5283     if(appData.noGUI) return;
5284     DisplayTimerLabel(blackTimerWidget, _("Black"), timeRemaining, highlight);
5285     if (highlight && iconPixmap == wIconPixmap) {
5286         iconPixmap = bIconPixmap;
5287         XtSetArg(args[0], XtNiconPixmap, iconPixmap);
5288         XtSetValues(shellWidget, args, 1);
5289     }
5290 }
5291
5292 #define CPNone 0
5293 #define CPReal 1
5294 #define CPComm 2
5295 #define CPSock 3
5296 #define CPLoop 4
5297 typedef int CPKind;
5298
5299 typedef struct {
5300     CPKind kind;
5301     int pid;
5302     int fdTo, fdFrom;
5303 } ChildProc;
5304
5305
5306 int
5307 StartChildProcess (char *cmdLine, char *dir, ProcRef *pr)
5308 {
5309     char *argv[64], *p;
5310     int i, pid;
5311     int to_prog[2], from_prog[2];
5312     ChildProc *cp;
5313     char buf[MSG_SIZ];
5314
5315     if (appData.debugMode) {
5316         fprintf(debugFP, "StartChildProcess (dir=\"%s\") %s\n",dir, cmdLine);
5317     }
5318
5319     /* We do NOT feed the cmdLine to the shell; we just
5320        parse it into blank-separated arguments in the
5321        most simple-minded way possible.
5322        */
5323     i = 0;
5324     safeStrCpy(buf, cmdLine, sizeof(buf)/sizeof(buf[0]) );
5325     p = buf;
5326     for (;;) {
5327         while(*p == ' ') p++;
5328         argv[i++] = p;
5329         if(*p == '"' || *p == '\'')
5330              p = strchr(++argv[i-1], *p);
5331         else p = strchr(p, ' ');
5332         if (p == NULL) break;
5333         *p++ = NULLCHAR;
5334     }
5335     argv[i] = NULL;
5336
5337     SetUpChildIO(to_prog, from_prog);
5338
5339     if ((pid = fork()) == 0) {
5340         /* Child process */
5341         // [HGM] PSWBTM: made order resistant against case where fd of created pipe was 0 or 1
5342         close(to_prog[1]);     // first close the unused pipe ends
5343         close(from_prog[0]);
5344         dup2(to_prog[0], 0);   // to_prog was created first, nd is the only one to use 0 or 1
5345         dup2(from_prog[1], 1);
5346         if(to_prog[0] >= 2) close(to_prog[0]); // if 0 or 1, the dup2 already cosed the original
5347         close(from_prog[1]);                   // and closing again loses one of the pipes!
5348         if(fileno(stderr) >= 2) // better safe than sorry...
5349                 dup2(1, fileno(stderr)); /* force stderr to the pipe */
5350
5351         if (dir[0] != NULLCHAR && chdir(dir) != 0) {
5352             perror(dir);
5353             exit(1);
5354         }
5355
5356         nice(appData.niceEngines); // [HGM] nice: adjust priority of engine proc
5357
5358         execvp(argv[0], argv);
5359
5360         /* If we get here, exec failed */
5361         perror(argv[0]);
5362         exit(1);
5363     }
5364
5365     /* Parent process */
5366     close(to_prog[0]);
5367     close(from_prog[1]);
5368
5369     cp = (ChildProc *) calloc(1, sizeof(ChildProc));
5370     cp->kind = CPReal;
5371     cp->pid = pid;
5372     cp->fdFrom = from_prog[0];
5373     cp->fdTo = to_prog[1];
5374     *pr = (ProcRef) cp;
5375     return 0;
5376 }
5377
5378 // [HGM] kill: implement the 'hard killing' of AS's Winboard_x
5379 static RETSIGTYPE
5380 AlarmCallBack (int n)
5381 {
5382     return;
5383 }
5384
5385 void
5386 DestroyChildProcess (ProcRef pr, int signalType)
5387 {
5388     ChildProc *cp = (ChildProc *) pr;
5389
5390     if (cp->kind != CPReal) return;
5391     cp->kind = CPNone;
5392     if (signalType == 10) { // [HGM] kill: if it does not terminate in 3 sec, kill
5393         signal(SIGALRM, AlarmCallBack);
5394         alarm(3);
5395         if(wait((int *) 0) == -1) { // process does not terminate on its own accord
5396             kill(cp->pid, SIGKILL); // kill it forcefully
5397             wait((int *) 0);        // and wait again
5398         }
5399     } else {
5400         if (signalType) {
5401             kill(cp->pid, signalType == 9 ? SIGKILL : SIGTERM); // [HGM] kill: use hard kill if so requested
5402         }
5403         /* Process is exiting either because of the kill or because of
5404            a quit command sent by the backend; either way, wait for it to die.
5405         */
5406         wait((int *) 0);
5407     }
5408     close(cp->fdFrom);
5409     close(cp->fdTo);
5410 }
5411
5412 void
5413 InterruptChildProcess (ProcRef pr)
5414 {
5415     ChildProc *cp = (ChildProc *) pr;
5416
5417     if (cp->kind != CPReal) return;
5418     (void) kill(cp->pid, SIGINT); /* stop it thinking */
5419 }
5420
5421 int
5422 OpenTelnet (char *host, char *port, ProcRef *pr)
5423 {
5424     char cmdLine[MSG_SIZ];
5425
5426     if (port[0] == NULLCHAR) {
5427       snprintf(cmdLine, sizeof(cmdLine), "%s %s", appData.telnetProgram, host);
5428     } else {
5429       snprintf(cmdLine, sizeof(cmdLine), "%s %s %s", appData.telnetProgram, host, port);
5430     }
5431     return StartChildProcess(cmdLine, "", pr);
5432 }
5433
5434 int
5435 OpenTCP (char *host, char *port, ProcRef *pr)
5436 {
5437 #if OMIT_SOCKETS
5438     DisplayFatalError(_("Socket support is not configured in"), 0, 2);
5439 #else  /* !OMIT_SOCKETS */
5440     struct addrinfo hints;
5441     struct addrinfo *ais, *ai;
5442     int error;
5443     int s=0;
5444     ChildProc *cp;
5445
5446     memset(&hints, 0, sizeof(hints));
5447     hints.ai_family = AF_UNSPEC;
5448     hints.ai_socktype = SOCK_STREAM;
5449
5450     error = getaddrinfo(host, port, &hints, &ais);
5451     if (error != 0) {
5452       /* a getaddrinfo error is not an errno, so can't return it */
5453       fprintf(debugFP, "getaddrinfo(%s, %s): %s\n",
5454               host, port, gai_strerror(error));
5455       return ENOENT;
5456     }
5457      
5458     for (ai = ais; ai != NULL; ai = ai->ai_next) {
5459       if ((s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) < 0) {
5460         error = errno;
5461         continue;
5462       }
5463       if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0) {
5464         error = errno;
5465         continue;
5466       }
5467       error = 0;
5468       break;
5469     }
5470     freeaddrinfo(ais);
5471
5472     if (error != 0) {
5473       return error;
5474     }
5475
5476     cp = (ChildProc *) calloc(1, sizeof(ChildProc));
5477     cp->kind = CPSock;
5478     cp->pid = 0;
5479     cp->fdFrom = s;
5480     cp->fdTo = s;
5481     *pr = (ProcRef) cp;
5482 #endif /* !OMIT_SOCKETS */
5483
5484     return 0;
5485 }
5486
5487 int
5488 OpenCommPort (char *name, ProcRef *pr)
5489 {
5490     int fd;
5491     ChildProc *cp;
5492
5493     fd = open(name, 2, 0);
5494     if (fd < 0) return errno;
5495
5496     cp = (ChildProc *) calloc(1, sizeof(ChildProc));
5497     cp->kind = CPComm;
5498     cp->pid = 0;
5499     cp->fdFrom = fd;
5500     cp->fdTo = fd;
5501     *pr = (ProcRef) cp;
5502
5503     return 0;
5504 }
5505
5506 int
5507 OpenLoopback (ProcRef *pr)
5508 {
5509     ChildProc *cp;
5510     int to[2], from[2];
5511
5512     SetUpChildIO(to, from);
5513
5514     cp = (ChildProc *) calloc(1, sizeof(ChildProc));
5515     cp->kind = CPLoop;
5516     cp->pid = 0;
5517     cp->fdFrom = to[0];         /* note not from[0]; we are doing a loopback */
5518     cp->fdTo = to[1];
5519     *pr = (ProcRef) cp;
5520
5521     return 0;
5522 }
5523
5524 int
5525 OpenRcmd (char *host, char *user, char *cmd, ProcRef *pr)
5526 {
5527     DisplayFatalError(_("internal rcmd not implemented for Unix"), 0, 1);
5528     return -1;
5529 }
5530
5531 #define INPUT_SOURCE_BUF_SIZE 8192
5532
5533 typedef struct {
5534     CPKind kind;
5535     int fd;
5536     int lineByLine;
5537     char *unused;
5538     InputCallback func;
5539     XtInputId xid;
5540     char buf[INPUT_SOURCE_BUF_SIZE];
5541     VOIDSTAR closure;
5542 } InputSource;
5543
5544 void
5545 DoInputCallback (caddr_t closure, int *source, XtInputId *xid)
5546 {
5547     InputSource *is = (InputSource *) closure;
5548     int count;
5549     int error;
5550     char *p, *q;
5551
5552     if (is->lineByLine) {
5553         count = read(is->fd, is->unused,
5554                      INPUT_SOURCE_BUF_SIZE - (is->unused - is->buf));
5555         if (count <= 0) {
5556             (is->func)(is, is->closure, is->buf, count, count ? errno : 0);
5557             return;
5558         }
5559         is->unused += count;
5560         p = is->buf;
5561         while (p < is->unused) {
5562             q = memchr(p, '\n', is->unused - p);
5563             if (q == NULL) break;
5564             q++;
5565             (is->func)(is, is->closure, p, q - p, 0);
5566             p = q;
5567         }
5568         q = is->buf;
5569         while (p < is->unused) {
5570             *q++ = *p++;
5571         }
5572         is->unused = q;
5573     } else {
5574         count = read(is->fd, is->buf, INPUT_SOURCE_BUF_SIZE);
5575         if (count == -1)
5576           error = errno;
5577         else
5578           error = 0;
5579         (is->func)(is, is->closure, is->buf, count, error);
5580     }
5581 }
5582
5583 InputSourceRef
5584 AddInputSource (ProcRef pr, int lineByLine, InputCallback func, VOIDSTAR closure)
5585 {
5586     InputSource *is;
5587     ChildProc *cp = (ChildProc *) pr;
5588
5589     is = (InputSource *) calloc(1, sizeof(InputSource));
5590     is->lineByLine = lineByLine;
5591     is->func = func;
5592     if (pr == NoProc) {
5593         is->kind = CPReal;
5594         is->fd = fileno(stdin);
5595     } else {
5596         is->kind = cp->kind;
5597         is->fd = cp->fdFrom;
5598     }
5599     if (lineByLine) {
5600         is->unused = is->buf;
5601     }
5602
5603     is->xid = XtAppAddInput(appContext, is->fd,
5604                             (XtPointer) (XtInputReadMask),
5605                             (XtInputCallbackProc) DoInputCallback,
5606                             (XtPointer) is);
5607     is->closure = closure;
5608     return (InputSourceRef) is;
5609 }
5610
5611 void
5612 RemoveInputSource (InputSourceRef isr)
5613 {
5614     InputSource *is = (InputSource *) isr;
5615
5616     if (is->xid == 0) return;
5617     XtRemoveInput(is->xid);
5618     is->xid = 0;
5619 }
5620
5621 int
5622 OutputToProcess (ProcRef pr, char *message, int count, int *outError)
5623 {
5624     static int line = 0;
5625     ChildProc *cp = (ChildProc *) pr;
5626     int outCount;
5627
5628     if (pr == NoProc)
5629     {
5630         if (appData.noJoin || !appData.useInternalWrap)
5631             outCount = fwrite(message, 1, count, stdout);
5632         else
5633         {
5634             int width = get_term_width();
5635             int len = wrap(NULL, message, count, width, &line);
5636             char *msg = malloc(len);
5637             int dbgchk;
5638
5639             if (!msg)
5640                 outCount = fwrite(message, 1, count, stdout);
5641             else
5642             {
5643                 dbgchk = wrap(msg, message, count, width, &line);
5644                 if (dbgchk != len && appData.debugMode)
5645                     fprintf(debugFP, "wrap(): dbgchk(%d) != len(%d)\n", dbgchk, len);
5646                 outCount = fwrite(msg, 1, dbgchk, stdout);
5647                 free(msg);
5648             }
5649         }
5650     }
5651     else
5652       outCount = write(cp->fdTo, message, count);
5653
5654     if (outCount == -1)
5655       *outError = errno;
5656     else
5657       *outError = 0;
5658
5659     return outCount;
5660 }
5661
5662 /* Output message to process, with "ms" milliseconds of delay
5663    between each character. This is needed when sending the logon
5664    script to ICC, which for some reason doesn't like the
5665    instantaneous send. */
5666 int
5667 OutputToProcessDelayed (ProcRef pr, char *message, int count, int *outError, long msdelay)
5668 {
5669     ChildProc *cp = (ChildProc *) pr;
5670     int outCount = 0;
5671     int r;
5672
5673     while (count--) {
5674         r = write(cp->fdTo, message++, 1);
5675         if (r == -1) {
5676             *outError = errno;
5677             return outCount;
5678         }
5679         ++outCount;
5680         if (msdelay >= 0)
5681           TimeDelay(msdelay);
5682     }
5683
5684     return outCount;
5685 }
5686
5687 /****   Animation code by Hugh Fisher, DCS, ANU. ****/
5688
5689 /*      Masks for XPM pieces. Black and white pieces can have
5690         different shapes, but in the interest of retaining my
5691         sanity pieces must have the same outline on both light
5692         and dark squares, and all pieces must use the same
5693         background square colors/images.                */
5694
5695 static int xpmDone = 0;
5696 static Pixmap animBufs[3*NrOfAnims]; // newBuf, saveBuf
5697 static GC animGCs[3*NrOfAnims]; // blitGC, pieceGC, outlineGC;
5698
5699 static void
5700 CreateAnimMasks (int pieceDepth)
5701 {
5702   ChessSquare   piece;
5703   Pixmap        buf;
5704   GC            bufGC, maskGC;
5705   int           kind, n;
5706   unsigned long plane;
5707   XGCValues     values;
5708
5709   /* Need a bitmap just to get a GC with right depth */
5710   buf = XCreatePixmap(xDisplay, xBoardWindow,
5711                         8, 8, 1);
5712   values.foreground = 1;
5713   values.background = 0;
5714   /* Don't use XtGetGC, not read only */
5715   maskGC = XCreateGC(xDisplay, buf,
5716                     GCForeground | GCBackground, &values);
5717   XFreePixmap(xDisplay, buf);
5718
5719   buf = XCreatePixmap(xDisplay, xBoardWindow,
5720                       squareSize, squareSize, pieceDepth);
5721   values.foreground = XBlackPixel(xDisplay, xScreen);
5722   values.background = XWhitePixel(xDisplay, xScreen);
5723   bufGC = XCreateGC(xDisplay, buf,
5724                     GCForeground | GCBackground, &values);
5725
5726   for (piece = WhitePawn; piece <= BlackKing; piece++) {
5727     /* Begin with empty mask */
5728     if(!xpmDone) // [HGM] pieces: keep using existing
5729     xpmMask[piece] = XCreatePixmap(xDisplay, xBoardWindow,
5730                                  squareSize, squareSize, 1);
5731     XSetFunction(xDisplay, maskGC, GXclear);
5732     XFillRectangle(xDisplay, xpmMask[piece], maskGC,
5733                    0, 0, squareSize, squareSize);
5734
5735     /* Take a copy of the piece */
5736     if (White(piece))
5737       kind = 0;
5738     else
5739       kind = 2;
5740     XSetFunction(xDisplay, bufGC, GXcopy);
5741     XCopyArea(xDisplay, xpmPieceBitmap[kind][((int)piece) % (int)BlackPawn],
5742               buf, bufGC,
5743               0, 0, squareSize, squareSize, 0, 0);
5744
5745     /* XOR the background (light) over the piece */
5746     XSetFunction(xDisplay, bufGC, GXxor);
5747     if (useImageSqs)
5748       XCopyArea(xDisplay, xpmLightSquare, buf, bufGC,
5749                 0, 0, squareSize, squareSize, 0, 0);
5750     else {
5751       XSetForeground(xDisplay, bufGC, lightSquareColor);
5752       XFillRectangle(xDisplay, buf, bufGC, 0, 0, squareSize, squareSize);
5753     }
5754
5755     /* We now have an inverted piece image with the background
5756        erased. Construct mask by just selecting all the non-zero
5757        pixels - no need to reconstruct the original image.      */
5758     XSetFunction(xDisplay, maskGC, GXor);
5759     plane = 1;
5760     /* Might be quicker to download an XImage and create bitmap
5761        data from it rather than this N copies per piece, but it
5762        only takes a fraction of a second and there is a much
5763        longer delay for loading the pieces.             */
5764     for (n = 0; n < pieceDepth; n ++) {
5765       XCopyPlane(xDisplay, buf, xpmMask[piece], maskGC,
5766                  0, 0, squareSize, squareSize,
5767                  0, 0, plane);
5768       plane = plane << 1;
5769     }
5770   }
5771   /* Clean up */
5772   XFreePixmap(xDisplay, buf);
5773   XFreeGC(xDisplay, bufGC);
5774   XFreeGC(xDisplay, maskGC);
5775 }
5776
5777 static void
5778 InitAnimState (AnimNr anr, XWindowAttributes *info)
5779 {
5780   XtGCMask  mask;
5781   XGCValues values;
5782
5783   /* Each buffer is square size, same depth as window */
5784   animBufs[anr+4] = xBoardWindow;
5785   animBufs[anr+2] = XCreatePixmap(xDisplay, xBoardWindow,
5786                         squareSize, squareSize, info->depth);
5787   animBufs[anr] = XCreatePixmap(xDisplay, xBoardWindow,
5788                         squareSize, squareSize, info->depth);
5789
5790   /* Create a plain GC for blitting */
5791   mask = GCForeground | GCBackground | GCFunction |
5792          GCPlaneMask | GCGraphicsExposures;
5793   values.foreground = XBlackPixel(xDisplay, xScreen);
5794   values.background = XWhitePixel(xDisplay, xScreen);
5795   values.function   = GXcopy;
5796   values.plane_mask = AllPlanes;
5797   values.graphics_exposures = False;
5798   animGCs[anr] = XCreateGC(xDisplay, xBoardWindow, mask, &values);
5799
5800   /* Piece will be copied from an existing context at
5801      the start of each new animation/drag. */
5802   animGCs[anr+2] = XCreateGC(xDisplay, xBoardWindow, 0, &values);
5803
5804   /* Outline will be a read-only copy of an existing */
5805   animGCs[anr+4] = None;
5806 }
5807
5808 void
5809 CreateAnimVars ()
5810 {
5811   XWindowAttributes info;
5812
5813   if (xpmDone && gameInfo.variant == oldVariant) return;
5814   if(xpmDone) oldVariant = gameInfo.variant; // first time pieces might not be created yet
5815   XGetWindowAttributes(xDisplay, xBoardWindow, &info);
5816
5817   InitAnimState(Game, &info);
5818   InitAnimState(Player, &info);
5819
5820   /* For XPM pieces, we need bitmaps to use as masks. */
5821   if (useImages)
5822     CreateAnimMasks(info.depth), xpmDone = 1;
5823 }
5824
5825 #ifndef HAVE_USLEEP
5826
5827 static Boolean frameWaiting;
5828
5829 static RETSIGTYPE
5830 FrameAlarm (int sig)
5831 {
5832   frameWaiting = False;
5833   /* In case System-V style signals.  Needed?? */
5834   signal(SIGALRM, FrameAlarm);
5835 }
5836
5837 void
5838 FrameDelay (int time)
5839 {
5840   struct itimerval delay;
5841
5842   XSync(xDisplay, False);
5843
5844   if (time > 0) {
5845     frameWaiting = True;
5846     signal(SIGALRM, FrameAlarm);
5847     delay.it_interval.tv_sec =
5848       delay.it_value.tv_sec = time / 1000;
5849     delay.it_interval.tv_usec =
5850       delay.it_value.tv_usec = (time % 1000) * 1000;
5851     setitimer(ITIMER_REAL, &delay, NULL);
5852     while (frameWaiting) pause();
5853     delay.it_interval.tv_sec = delay.it_value.tv_sec = 0;
5854     delay.it_interval.tv_usec = delay.it_value.tv_usec = 0;
5855     setitimer(ITIMER_REAL, &delay, NULL);
5856   }
5857 }
5858
5859 #else
5860
5861 void
5862 FrameDelay (int time)
5863 {
5864   XSync(xDisplay, False);
5865   if (time > 0)
5866     usleep(time * 1000);
5867 }
5868
5869 #endif
5870
5871 static void
5872 SelectGCMask (ChessSquare piece, GC *clip, GC *outline, Pixmap *mask)
5873 {
5874   GC source;
5875
5876   /* Bitmap for piece being moved. */
5877   if (appData.monoMode) {
5878       *mask = *pieceToSolid(piece);
5879   } else if (useImages) {
5880 #if HAVE_LIBXPM
5881       *mask = xpmMask[piece];
5882 #else
5883       *mask = ximMaskPm[piece];
5884 #endif
5885   } else {
5886       *mask = *pieceToSolid(piece);
5887   }
5888
5889   /* GC for piece being moved. Square color doesn't matter, but
5890      since it gets modified we make a copy of the original. */
5891   if (White(piece)) {
5892     if (appData.monoMode)
5893       source = bwPieceGC;
5894     else
5895       source = wlPieceGC;
5896   } else {
5897     if (appData.monoMode)
5898       source = wbPieceGC;
5899     else
5900       source = blPieceGC;
5901   }
5902   XCopyGC(xDisplay, source, 0xFFFFFFFF, *clip);
5903
5904   /* Outline only used in mono mode and is not modified */
5905   if (White(piece))
5906     *outline = bwPieceGC;
5907   else
5908     *outline = wbPieceGC;
5909 }
5910
5911 static void
5912 OverlayPiece (ChessSquare piece, GC clip, GC outline,  Drawable dest)
5913 {
5914   int   kind;
5915
5916   if (!useImages) {
5917     /* Draw solid rectangle which will be clipped to shape of piece */
5918     XFillRectangle(xDisplay, dest, clip,
5919                    0, 0, squareSize, squareSize);
5920     if (appData.monoMode)
5921       /* Also draw outline in contrasting color for black
5922          on black / white on white cases                */
5923       XCopyPlane(xDisplay, *pieceToOutline(piece), dest, outline,
5924                  0, 0, squareSize, squareSize, 0, 0, 1);
5925   } else {
5926     /* Copy the piece */
5927     if (White(piece))
5928       kind = 0;
5929     else
5930       kind = 2;
5931     if(appData.upsideDown && flipView) kind ^= 2;
5932     XCopyArea(xDisplay, xpmPieceBitmap[kind][piece],
5933               dest, clip,
5934               0, 0, squareSize, squareSize,
5935               0, 0);
5936   }
5937 }
5938
5939 void
5940 InsertPiece (AnimNr anr, ChessSquare piece)
5941 {
5942   OverlayPiece(piece, animGCs[anr+2], animGCs[anr+4], animBufs[anr]);
5943 }
5944
5945 void
5946 DrawBlank (AnimNr anr, int x, int y, int startColor)
5947 {
5948     BlankSquare(x, y, startColor, EmptySquare, animBufs[anr+2], 0);
5949 }
5950
5951 void CopyRectangle (AnimNr anr, int srcBuf, int destBuf,
5952                  int srcX, int srcY, int width, int height, int destX, int destY)
5953 {
5954     XCopyArea(xDisplay, animBufs[anr+srcBuf], animBufs[anr+destBuf], animGCs[anr],
5955                 srcX, srcY, width, height, destX, destY);
5956 }
5957
5958 void
5959 SetDragPiece (AnimNr anr, ChessSquare piece)
5960 {
5961   Pixmap mask;
5962   /* The piece will be drawn using its own bitmap as a matte    */
5963   SelectGCMask(piece, &animGCs[anr+2], &animGCs[anr+4], &mask);
5964   XSetClipMask(xDisplay, animGCs[anr+2], mask);
5965 }
5966
5967 #include <sys/ioctl.h>
5968 int
5969 get_term_width ()
5970 {
5971     int fd, default_width;
5972
5973     fd = STDIN_FILENO;
5974     default_width = 79; // this is FICS default anyway...
5975
5976 #if !defined(TIOCGWINSZ) && defined(TIOCGSIZE)
5977     struct ttysize win;
5978     if (!ioctl(fd, TIOCGSIZE, &win))
5979         default_width = win.ts_cols;
5980 #elif defined(TIOCGWINSZ)
5981     struct winsize win;
5982     if (!ioctl(fd, TIOCGWINSZ, &win))
5983         default_width = win.ws_col;
5984 #endif
5985     return default_width;
5986 }
5987
5988 void
5989 update_ics_width ()
5990 {
5991   static int old_width = 0;
5992   int new_width = get_term_width();
5993
5994   if (old_width != new_width)
5995     ics_printf("set width %d\n", new_width);
5996   old_width = new_width;
5997 }
5998
5999 void
6000 NotifyFrontendLogin ()
6001 {
6002     update_ics_width();
6003 }
6004
6005 /* [AS] Arrow highlighting support */
6006
6007 void
6008 DrawPolygon (Pnt arrow[], int nr)
6009 {
6010     XPoint pts[10];
6011     int i;
6012     for(i=0; i<10; i++) pts[i].x = arrow[i].x, pts[i].y = arrow[i].y;
6013     XFillPolygon(xDisplay, xBoardWindow, highlineGC, pts, nr, Nonconvex, CoordModeOrigin);
6014     if(appData.monoMode) arrow[nr] = arrow[0], XDrawLines(xDisplay, xBoardWindow, darkSquareGC, pts, nr+1, CoordModeOrigin);
6015 }
6016
6017 void
6018 UpdateLogos (int displ)
6019 {
6020     return; // no logos in XBoard yet
6021 }
6022