Implement -installEngine option
[xboard.git] / xaw / 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, 2013 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 #include <cairo/cairo.h>
65 #include <cairo/cairo-xlib.h>
66
67 #if !OMIT_SOCKETS
68 # if HAVE_SYS_SOCKET_H
69 #  include <sys/socket.h>
70 #  include <netinet/in.h>
71 #  include <netdb.h>
72 # else /* not HAVE_SYS_SOCKET_H */
73 #  if HAVE_LAN_SOCKET_H
74 #   include <lan/socket.h>
75 #   include <lan/in.h>
76 #   include <lan/netdb.h>
77 #  else /* not HAVE_LAN_SOCKET_H */
78 #   define OMIT_SOCKETS 1
79 #  endif /* not HAVE_LAN_SOCKET_H */
80 # endif /* not HAVE_SYS_SOCKET_H */
81 #endif /* !OMIT_SOCKETS */
82
83 #if STDC_HEADERS
84 # include <stdlib.h>
85 # include <string.h>
86 #else /* not STDC_HEADERS */
87 extern char *getenv();
88 # if HAVE_STRING_H
89 #  include <string.h>
90 # else /* not HAVE_STRING_H */
91 #  include <strings.h>
92 # endif /* not HAVE_STRING_H */
93 #endif /* not STDC_HEADERS */
94
95 #if HAVE_SYS_FCNTL_H
96 # include <sys/fcntl.h>
97 #else /* not HAVE_SYS_FCNTL_H */
98 # if HAVE_FCNTL_H
99 #  include <fcntl.h>
100 # endif /* HAVE_FCNTL_H */
101 #endif /* not HAVE_SYS_FCNTL_H */
102
103 #if HAVE_SYS_SYSTEMINFO_H
104 # include <sys/systeminfo.h>
105 #endif /* HAVE_SYS_SYSTEMINFO_H */
106
107 #if TIME_WITH_SYS_TIME
108 # include <sys/time.h>
109 # include <time.h>
110 #else
111 # if HAVE_SYS_TIME_H
112 #  include <sys/time.h>
113 # else
114 #  include <time.h>
115 # endif
116 #endif
117
118 #if HAVE_UNISTD_H
119 # include <unistd.h>
120 #endif
121
122 #if HAVE_SYS_WAIT_H
123 # include <sys/wait.h>
124 #endif
125
126 #if HAVE_DIRENT_H
127 # include <dirent.h>
128 # define NAMLEN(dirent) strlen((dirent)->d_name)
129 # define HAVE_DIR_STRUCT
130 #else
131 # define dirent direct
132 # define NAMLEN(dirent) (dirent)->d_namlen
133 # if HAVE_SYS_NDIR_H
134 #  include <sys/ndir.h>
135 #  define HAVE_DIR_STRUCT
136 # endif
137 # if HAVE_SYS_DIR_H
138 #  include <sys/dir.h>
139 #  define HAVE_DIR_STRUCT
140 # endif
141 # if HAVE_NDIR_H
142 #  include <ndir.h>
143 #  define HAVE_DIR_STRUCT
144 # endif
145 #endif
146
147 #if ENABLE_NLS
148 #include <locale.h>
149 #endif
150
151 #include <X11/Intrinsic.h>
152 #include <X11/StringDefs.h>
153 #include <X11/Shell.h>
154 #include <X11/cursorfont.h>
155 #include <X11/Xatom.h>
156 #include <X11/Xmu/Atoms.h>
157 #if USE_XAW3D
158 #include <X11/Xaw3d/Dialog.h>
159 #include <X11/Xaw3d/Form.h>
160 #include <X11/Xaw3d/List.h>
161 #include <X11/Xaw3d/Label.h>
162 #include <X11/Xaw3d/SimpleMenu.h>
163 #include <X11/Xaw3d/SmeBSB.h>
164 #include <X11/Xaw3d/SmeLine.h>
165 #include <X11/Xaw3d/Box.h>
166 #include <X11/Xaw3d/MenuButton.h>
167 #include <X11/Xaw3d/Text.h>
168 #include <X11/Xaw3d/AsciiText.h>
169 #else
170 #include <X11/Xaw/Dialog.h>
171 #include <X11/Xaw/Form.h>
172 #include <X11/Xaw/List.h>
173 #include <X11/Xaw/Label.h>
174 #include <X11/Xaw/SimpleMenu.h>
175 #include <X11/Xaw/SmeBSB.h>
176 #include <X11/Xaw/SmeLine.h>
177 #include <X11/Xaw/Box.h>
178 #include <X11/Xaw/MenuButton.h>
179 #include <X11/Xaw/Text.h>
180 #include <X11/Xaw/AsciiText.h>
181 #endif
182
183 // [HGM] bitmaps: put before incuding the bitmaps / pixmaps, to know how many piece types there are.
184 #include "common.h"
185
186 #include "bitmaps/icon_white.bm"
187 #include "bitmaps/icon_black.bm"
188 #include "bitmaps/checkmark.bm"
189
190 #include "frontend.h"
191 #include "backend.h"
192 #include "backendz.h"
193 #include "moves.h"
194 #include "xboard.h"
195 #include "childio.h"
196 #include "xgamelist.h"
197 #include "xhistory.h"
198 #include "menus.h"
199 #include "board.h"
200 #include "dialogs.h"
201 #include "engineoutput.h"
202 #include "usystem.h"
203 #include "gettext.h"
204 #include "draw.h"
205
206
207 #ifdef __EMX__
208 #ifndef HAVE_USLEEP
209 #define HAVE_USLEEP
210 #endif
211 #define usleep(t)   _sleep2(((t)+500)/1000)
212 #endif
213
214 #ifdef ENABLE_NLS
215 # define  _(s) gettext (s)
216 # define N_(s) gettext_noop (s)
217 #else
218 # define  _(s) (s)
219 # define N_(s)  s
220 #endif
221
222 int main P((int argc, char **argv));
223 RETSIGTYPE CmailSigHandler P((int sig));
224 RETSIGTYPE IntSigHandler P((int sig));
225 RETSIGTYPE TermSizeSigHandler P((int sig));
226 Widget CreateMenuBar P((Menu *mb, int boardWidth));
227 #if ENABLE_NLS
228 char *InsertPxlSize P((char *pattern, int targetPxlSize));
229 XFontSet CreateFontSet P((char *base_fnt_lst));
230 #else
231 char *FindFont P((char *pattern, int targetPxlSize));
232 #endif
233 void ReadBitmap P((Pixmap *pm, String name, unsigned char bits[],
234                    u_int wreq, u_int hreq));
235 void EventProc P((Widget widget, caddr_t unused, XEvent *event));
236 void DelayedDrag P((void));
237 static void MoveTypeInProc P((Widget widget, caddr_t unused, XEvent *event));
238 void HandlePV P((Widget w, XEvent * event,
239                      String * params, Cardinal * nParams));
240 void DrawPositionProc P((Widget w, XEvent *event,
241                      String *prms, Cardinal *nprms));
242 void CommentClick P((Widget w, XEvent * event,
243                    String * params, Cardinal * nParams));
244 void ICSInputBoxPopUp P((void));
245 void SelectCommand P((Widget w, XtPointer client_data, XtPointer call_data));
246 void KeyBindingProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
247 void QuitWrapper P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
248 static void EnterKeyProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
249 static void UpKeyProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
250 static void DownKeyProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
251 void TempBackwardProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
252 void TempForwardProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
253 Boolean TempBackwardActive = False;
254 void ManInner P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
255 void DisplayMove P((int moveNumber));
256 void update_ics_width P(());
257 int CopyMemoProc P(());
258
259 /*
260 * XBoard depends on Xt R4 or higher
261 */
262 int xtVersion = XtSpecificationRelease;
263
264 int xScreen;
265 Display *xDisplay;
266 Window xBoardWindow;
267 Pixel lowTimeWarningColor, dialogColor, buttonColor; // used in widgets
268 Pixmap iconPixmap, wIconPixmap, bIconPixmap, xMarkPixmap;
269 Widget shellWidget, formWidget, boardWidget, titleWidget, dropMenu, menuBarWidget;
270 Option *optList; // contains all widgets of main window
271 #if ENABLE_NLS
272 XFontSet fontSet, clockFontSet;
273 #else
274 Font clockFontID;
275 XFontStruct *clockFontStruct;
276 #endif
277 Font coordFontID, countFontID;
278 XFontStruct *coordFontStruct, *countFontStruct;
279 XtAppContext appContext;
280 char *layoutName;
281
282 char installDir[] = "."; // [HGM] UCI: needed for UCI; probably needs run-time initializtion
283
284 Position commentX = -1, commentY = -1;
285 Dimension commentW, commentH;
286 typedef unsigned int BoardSize;
287 BoardSize boardSize;
288 Boolean chessProgram;
289
290 int  minX, minY; // [HGM] placement: volatile limits on upper-left corner
291 int smallLayout = 0, tinyLayout = 0,
292   marginW, marginH, // [HGM] for run-time resizing
293   fromX = -1, fromY = -1, toX, toY, commentUp = False,
294   errorExitStatus = -1, defaultLineGap;
295 Dimension textHeight;
296 Pixel timerForegroundPixel, timerBackgroundPixel;
297 Pixel buttonForegroundPixel, buttonBackgroundPixel;
298 char *chessDir, *programName, *programVersion;
299 Boolean alwaysOnTop = False;
300 char *icsTextMenuString;
301 char *icsNames;
302 char *firstChessProgramNames;
303 char *secondChessProgramNames;
304
305 WindowPlacement wpMain;
306 WindowPlacement wpConsole;
307 WindowPlacement wpComment;
308 WindowPlacement wpMoveHistory;
309 WindowPlacement wpEvalGraph;
310 WindowPlacement wpEngineOutput;
311 WindowPlacement wpGameList;
312 WindowPlacement wpTags;
313 WindowPlacement wpDualBoard;
314
315
316 /* This magic number is the number of intermediate frames used
317    in each half of the animation. For short moves it's reduced
318    by 1. The total number of frames will be factor * 2 + 1.  */
319 #define kFactor    4
320
321 SizeDefaults sizeDefaults[] = SIZE_DEFAULTS;
322
323 typedef struct {
324     char piece;
325     char* widget;
326 } DropMenuEnables;
327
328 DropMenuEnables dmEnables[] = {
329     { 'P', "Pawn" },
330     { 'N', "Knight" },
331     { 'B', "Bishop" },
332     { 'R', "Rook" },
333     { 'Q', "Queen" }
334 };
335
336 Arg shellArgs[] = {
337     { XtNwidth, 0 },
338     { XtNheight, 0 },
339     { XtNminWidth, 0 },
340     { XtNminHeight, 0 },
341     { XtNmaxWidth, 0 },
342     { XtNmaxHeight, 0 }
343 };
344
345 XtResource clientResources[] = {
346     { "flashCount", "flashCount", XtRInt, sizeof(int),
347         XtOffset(AppDataPtr, flashCount), XtRImmediate,
348         (XtPointer) FLASH_COUNT  },
349 };
350
351 XrmOptionDescRec shellOptions[] = {
352     { "-flashCount", "flashCount", XrmoptionSepArg, NULL },
353     { "-flash", "flashCount", XrmoptionNoArg, "3" },
354     { "-xflash", "flashCount", XrmoptionNoArg, "0" },
355 };
356
357 XtActionsRec boardActions[] = {
358     { "DrawPosition", DrawPositionProc },
359     { "HandlePV", HandlePV },
360     { "SelectPV", SelectPV },
361     { "StopPV", StopPV },
362     { "MenuItem", KeyBindingProc }, // [HGM] generic handler for key bindings
363     { "QuitProc", QuitWrapper },
364     { "ManProc", ManInner },
365     { "TempBackwardProc", TempBackwardProc },
366     { "TempForwardProc", TempForwardProc },
367     { "CommentClick", (XtActionProc) CommentClick },
368     { "GenericPopDown", (XtActionProc) GenericPopDown },
369     { "ErrorPopDown", (XtActionProc) ErrorPopDown },
370     { "CopyMemoProc", (XtActionProc) CopyMemoProc },
371     { "SelectMove", (XtActionProc) SelectMoveX },
372     { "LoadSelectedProc", LoadSelectedProc },
373     { "SetFilterProc", SetFilterProc },
374     { "TypeInProc", TypeInProc },
375     { "EnterKeyProc", EnterKeyProc },
376     { "UpKeyProc", UpKeyProc },
377     { "DownKeyProc", DownKeyProc },
378     { "WheelProc", WheelProc },
379     { "TabProc", TabProc },
380 };
381
382 char globalTranslations[] =
383   ":Meta<Key>Next: MenuItem(LoadNextGameProc) \n \
384    :Meta<Key>Prior: MenuItem(LoadPrevGameProc) \n \
385    :Ctrl<Key>Down: LoadSelectedProc(3) \n \
386    :Ctrl<Key>Up: LoadSelectedProc(-3) \n \
387    :Shift<Key>Next: MenuItem(LoadNextPositionProc) \n \
388    :Shift<Key>Prior: MenuItem(LoadPrevPositionProc) \n \
389    :<Key>Pause: MenuItem(Mode.Pause) \n \
390    :Ctrl<Key>d: MenuItem(DebugProc) \n \
391    :Meta Ctrl<Key>F12: MenuItem(DebugProc) \n \
392    :<Key>Left: MenuItem(Edit.Backward) \n \
393    :<Key>Right: MenuItem(Edit.Forward) \n \
394    :Ctrl<Key>P: MenuItem(PonderNextMove) \n "
395 #ifndef OPTIONSDIALOG
396     "\
397    :Ctrl<Key>Q: MenuItem(AlwaysQueenProc) \n \
398    :Ctrl<Key>F: MenuItem(AutoflagProc) \n \
399    :Ctrl<Key>A: MenuItem(AnimateMovingProc) \n \
400    :Ctrl<Key>L: MenuItem(TestLegalityProc) \n \
401    :Ctrl<Key>H: MenuItem(HideThinkingProc) \n "
402 #endif
403    "\
404    :<KeyDown>Return: TempBackwardProc() \n \
405    :<KeyUp>Return: TempForwardProc() \n";
406
407 char ICSInputTranslations[] =
408     "<Key>Up: UpKeyProc() \n "
409     "<Key>Down: DownKeyProc() \n "
410     "<Key>Return: EnterKeyProc() \n";
411
412 // [HGM] vari: another hideous kludge: call extend-end first so we can be sure select-start works,
413 //             as the widget is destroyed before the up-click can call extend-end
414 char commentTranslations[] = "<Btn3Down>: extend-end(PRIMARY) select-start() CommentClick() \n";
415
416 String xboardResources[] = {
417     "*Error*translations: #override\\n <Key>Return: ErrorPopDown()",
418     NULL
419   };
420
421
422 /* Max possible square size */
423 #define MAXSQSIZE 256
424
425 /* Arrange to catch delete-window events */
426 Atom wm_delete_window;
427 void
428 CatchDeleteWindow (Widget w, String procname)
429 {
430   char buf[MSG_SIZ];
431   XSetWMProtocols(xDisplay, XtWindow(w), &wm_delete_window, 1);
432   snprintf(buf, sizeof(buf), "<Message>WM_PROTOCOLS: %s() \n", procname);
433   XtAugmentTranslations(w, XtParseTranslationTable(buf));
434 }
435
436 void
437 BoardToTop ()
438 {
439   Arg args[16];
440   XtSetArg(args[0], XtNiconic, False);
441   XtSetValues(shellWidget, args, 1);
442
443   XtPopup(shellWidget, XtGrabNone); /* Raise if lowered  */
444 }
445
446 //---------------------------------------------------------------------------------------------------------
447 // some symbol definitions to provide the proper (= XBoard) context for the code in args.h
448 #define XBOARD True
449 #define JAWS_ARGS
450 #define CW_USEDEFAULT (1<<31)
451 #define ICS_TEXT_MENU_SIZE 90
452 #define DEBUG_FILE "xboard.debug"
453 #define SetCurrentDirectory chdir
454 #define GetCurrentDirectory(SIZE, NAME) getcwd(NAME, SIZE)
455 #define OPTCHAR "-"
456 #define SEPCHAR " "
457
458 // The option definition and parsing code common to XBoard and WinBoard is collected in this file
459 #include "args.h"
460
461 // front-end part of option handling
462
463 // [HGM] This platform-dependent table provides the location for storing the color info
464 extern char *crWhite, * crBlack;
465
466 void *
467 colorVariable[] = {
468   &appData.whitePieceColor,
469   &appData.blackPieceColor,
470   &appData.lightSquareColor,
471   &appData.darkSquareColor,
472   &appData.highlightSquareColor,
473   &appData.premoveHighlightColor,
474   &appData.lowTimeWarningColor,
475   NULL,
476   NULL,
477   NULL,
478   NULL,
479   NULL,
480   &crWhite,
481   &crBlack,
482   NULL
483 };
484
485 // [HGM] font: keep a font for each square size, even non-stndard ones
486 #define NUM_SIZES 18
487 #define MAX_SIZE 130
488 Boolean fontIsSet[NUM_FONTS], fontValid[NUM_FONTS][MAX_SIZE];
489 char *fontTable[NUM_FONTS][MAX_SIZE];
490
491 void
492 ParseFont (char *name, int number)
493 { // in XBoard, only 2 of the fonts are currently implemented, and we just copy their name
494   int size;
495   if(sscanf(name, "size%d:", &size)) {
496     // [HGM] font: font is meant for specific boardSize (likely from settings file);
497     //       defer processing it until we know if it matches our board size
498     if(size >= 0 && size<MAX_SIZE) { // for now, fixed limit
499         fontTable[number][size] = strdup(strchr(name, ':')+1);
500         fontValid[number][size] = True;
501     }
502     return;
503   }
504   switch(number) {
505     case 0: // CLOCK_FONT
506         appData.clockFont = strdup(name);
507       break;
508     case 1: // MESSAGE_FONT
509         appData.font = strdup(name);
510       break;
511     case 2: // COORD_FONT
512         appData.coordFont = strdup(name);
513       break;
514     default:
515       return;
516   }
517   fontIsSet[number] = True; // [HGM] font: indicate a font was specified (not from settings file)
518 }
519
520 void
521 SetFontDefaults ()
522 { // only 2 fonts currently
523   appData.clockFont = CLOCK_FONT_NAME;
524   appData.coordFont = COORD_FONT_NAME;
525   appData.font  =   DEFAULT_FONT_NAME;
526 }
527
528 void
529 CreateFonts ()
530 { // no-op, until we identify the code for this already in XBoard and move it here
531 }
532
533 void
534 ParseColor (int n, char *name)
535 { // in XBoard, just copy the color-name string
536   if(colorVariable[n]) *(char**)colorVariable[n] = strdup(name);
537 }
538
539 void
540 ParseTextAttribs (ColorClass cc, char *s)
541 {
542     (&appData.colorShout)[cc] = strdup(s);
543 }
544
545 void
546 ParseBoardSize (void *addr, char *name)
547 {
548     appData.boardSize = strdup(name);
549 }
550
551 void
552 LoadAllSounds ()
553 { // In XBoard the sound-playing program takes care of obtaining the actual sound
554 }
555
556 void
557 SetCommPortDefaults ()
558 { // for now, this is a no-op, as the corresponding option does not exist in XBoard
559 }
560
561 // [HGM] args: these three cases taken out to stay in front-end
562 void
563 SaveFontArg (FILE *f, ArgDescriptor *ad)
564 {
565   char *name;
566   int i, n = (int)(intptr_t)ad->argLoc;
567   switch(n) {
568     case 0: // CLOCK_FONT
569         name = appData.clockFont;
570       break;
571     case 1: // MESSAGE_FONT
572         name = appData.font;
573       break;
574     case 2: // COORD_FONT
575         name = appData.coordFont;
576       break;
577     default:
578       return;
579   }
580   for(i=0; i<NUM_SIZES; i++) // [HGM] font: current font becomes standard for current size
581     if(sizeDefaults[i].squareSize == squareSize) { // only for standard sizes!
582         fontTable[n][squareSize] = strdup(name);
583         fontValid[n][squareSize] = True;
584         break;
585   }
586   for(i=0; i<MAX_SIZE; i++) if(fontValid[n][i]) // [HGM] font: store all standard fonts
587     fprintf(f, OPTCHAR "%s" SEPCHAR "\"size%d:%s\"\n", ad->argName, i, fontTable[n][i]);
588 }
589
590 void
591 ExportSounds ()
592 { // nothing to do, as the sounds are at all times represented by their text-string names already
593 }
594
595 void
596 SaveAttribsArg (FILE *f, ArgDescriptor *ad)
597 {       // here the "argLoc" defines a table index. It could have contained the 'ta' pointer itself, though
598         fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, (&appData.colorShout)[(int)(intptr_t)ad->argLoc]);
599 }
600
601 void
602 SaveColor (FILE *f, ArgDescriptor *ad)
603 {       // in WinBoard the color is an int and has to be converted to text. In X it would be a string already?
604         if(colorVariable[(int)(intptr_t)ad->argLoc])
605         fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, *(char**)colorVariable[(int)(intptr_t)ad->argLoc]);
606 }
607
608 void
609 SaveBoardSize (FILE *f, char *name, void *addr)
610 { // wrapper to shield back-end from BoardSize & sizeInfo
611   fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", name, appData.boardSize);
612 }
613
614 void
615 ParseCommPortSettings (char *s)
616 { // no such option in XBoard (yet)
617 }
618
619 int frameX, frameY;
620
621 void
622 GetActualPlacement (Widget wg, WindowPlacement *wp)
623 {
624   XWindowAttributes winAt;
625   Window win, dummy;
626   int rx, ry;
627
628   if(!wg) return;
629
630   win = XtWindow(wg);
631   XGetWindowAttributes(xDisplay, win, &winAt); // this works, where XtGetValues on XtNx, XtNy does not!
632   XTranslateCoordinates (xDisplay, win, winAt.root, -winAt.border_width, -winAt.border_width, &rx, &ry, &dummy);
633   wp->x = rx - winAt.x;
634   wp->y = ry - winAt.y;
635   wp->height = winAt.height;
636   wp->width = winAt.width;
637   frameX = winAt.x; frameY = winAt.y; // remember to decide if windows touch
638 }
639
640 void
641 GetWindowCoords ()
642 { // wrapper to shield use of window handles from back-end (make addressible by number?)
643   // In XBoard this will have to wait until awareness of window parameters is implemented
644   GetActualPlacement(shellWidget, &wpMain);
645   if(shellUp[EngOutDlg]) GetActualPlacement(shells[EngOutDlg], &wpEngineOutput);
646   if(shellUp[HistoryDlg]) GetActualPlacement(shells[HistoryDlg], &wpMoveHistory);
647   if(shellUp[EvalGraphDlg]) GetActualPlacement(shells[EvalGraphDlg], &wpEvalGraph);
648   if(shellUp[GameListDlg]) GetActualPlacement(shells[GameListDlg], &wpGameList);
649   if(shellUp[CommentDlg]) GetActualPlacement(shells[CommentDlg], &wpComment);
650   if(shellUp[TagsDlg]) GetActualPlacement(shells[TagsDlg], &wpTags);
651 }
652
653 void
654 PrintCommPortSettings (FILE *f, char *name)
655 { // This option does not exist in XBoard
656 }
657
658 void
659 EnsureOnScreen (int *x, int *y, int minX, int minY)
660 {
661   return;
662 }
663
664 int
665 MainWindowUp ()
666 { // [HGM] args: allows testing if main window is realized from back-end
667   return xBoardWindow != 0;
668 }
669
670 void
671 PopUpStartupDialog ()
672 {  // start menu not implemented in XBoard
673 }
674
675 char *
676 ConvertToLine (int argc, char **argv)
677 {
678   static char line[128*1024], buf[1024];
679   int i;
680
681   line[0] = NULLCHAR;
682   for(i=1; i<argc; i++)
683     {
684       if( (strchr(argv[i], ' ') || strchr(argv[i], '\n') ||strchr(argv[i], '\t') || argv[i][0] == NULLCHAR)
685           && argv[i][0] != '{' )
686         snprintf(buf, sizeof(buf)/sizeof(buf[0]), "{%s} ", argv[i]);
687       else
688         snprintf(buf, sizeof(buf)/sizeof(buf[0]), "%s ", argv[i]);
689       strncat(line, buf, 128*1024 - strlen(line) - 1 );
690     }
691
692   line[strlen(line)-1] = NULLCHAR;
693   return line;
694 }
695
696 //--------------------------------------------------------------------------------------------
697
698 void
699 ResizeBoardWindow (int w, int h, int inhibit)
700 {
701     w += marginW + 1; // [HGM] not sure why the +1 is (sometimes) needed...
702     h += marginH;
703     shellArgs[0].value = w;
704     shellArgs[1].value = h;
705     shellArgs[4].value = shellArgs[2].value = w;
706     shellArgs[5].value = shellArgs[3].value = h;
707     XtSetValues(shellWidget, &shellArgs[0], inhibit ? 6 : 2);
708
709     XSync(xDisplay, False);
710 }
711
712 static int
713 MakeOneColor (char *name, Pixel *color)
714 {
715     XrmValue vFrom, vTo;
716     if (!appData.monoMode) {
717         vFrom.addr = (caddr_t) name;
718         vFrom.size = strlen(name);
719         XtConvert(shellWidget, XtRString, &vFrom, XtRPixel, &vTo);
720         if (vTo.addr == NULL) {
721           appData.monoMode = True;
722           return True;
723         } else {
724           *color = *(Pixel *) vTo.addr;
725         }
726     }
727     return False;
728 }
729
730 int
731 MakeColors ()
732 {   // [HGM] taken out of main(), so it can be called from BoardOptions dialog
733     int forceMono = False;
734
735     if (appData.lowTimeWarning)
736         forceMono |= MakeOneColor(appData.lowTimeWarningColor, &lowTimeWarningColor);
737     if(appData.dialogColor[0]) MakeOneColor(appData.dialogColor, &dialogColor);
738     if(appData.buttonColor[0]) MakeOneColor(appData.buttonColor, &buttonColor);
739
740     return forceMono;
741 }
742
743 void
744 InitializeFonts (int clockFontPxlSize, int coordFontPxlSize, int fontPxlSize)
745 {   // detervtomine what fonts to use, and create them
746     XrmValue vTo;
747     XrmDatabase xdb;
748
749     if(!fontIsSet[CLOCK_FONT] && fontValid[CLOCK_FONT][squareSize])
750         appData.clockFont = fontTable[CLOCK_FONT][squareSize];
751     if(!fontIsSet[MESSAGE_FONT] && fontValid[MESSAGE_FONT][squareSize])
752         appData.font = fontTable[MESSAGE_FONT][squareSize];
753     if(!fontIsSet[COORD_FONT] && fontValid[COORD_FONT][squareSize])
754         appData.coordFont = fontTable[COORD_FONT][squareSize];
755
756 #if ENABLE_NLS
757     appData.font = InsertPxlSize(appData.font, fontPxlSize);
758     appData.clockFont = InsertPxlSize(appData.clockFont, clockFontPxlSize);
759     appData.coordFont = InsertPxlSize(appData.coordFont, coordFontPxlSize);
760     fontSet = CreateFontSet(appData.font);
761     clockFontSet = CreateFontSet(appData.clockFont);
762     {
763       /* For the coordFont, use the 0th font of the fontset. */
764       XFontSet coordFontSet = CreateFontSet(appData.coordFont);
765       XFontStruct **font_struct_list;
766       XFontSetExtents *fontSize;
767       char **font_name_list;
768       XFontsOfFontSet(coordFontSet, &font_struct_list, &font_name_list);
769       coordFontID = XLoadFont(xDisplay, font_name_list[0]);
770       coordFontStruct = XQueryFont(xDisplay, coordFontID);
771       fontSize = XExtentsOfFontSet(fontSet); // [HGM] figure out how much vertical space font takes
772       textHeight = fontSize->max_logical_extent.height + 5; // add borderWidth
773     }
774 #else
775     appData.font = FindFont(appData.font, fontPxlSize);
776     appData.clockFont = FindFont(appData.clockFont, clockFontPxlSize);
777     appData.coordFont = FindFont(appData.coordFont, coordFontPxlSize);
778     clockFontID = XLoadFont(xDisplay, appData.clockFont);
779     clockFontStruct = XQueryFont(xDisplay, clockFontID);
780     coordFontID = XLoadFont(xDisplay, appData.coordFont);
781     coordFontStruct = XQueryFont(xDisplay, coordFontID);
782     // textHeight in !NLS mode!
783 #endif
784     countFontID = coordFontID;  // [HGM] holdings
785     countFontStruct = coordFontStruct;
786
787     xdb = XtDatabase(xDisplay);
788 #if ENABLE_NLS
789     XrmPutLineResource(&xdb, "*international: True");
790     vTo.size = sizeof(XFontSet);
791     vTo.addr = (XtPointer) &fontSet;
792     XrmPutResource(&xdb, "*fontSet", XtRFontSet, &vTo);
793 #else
794     XrmPutStringResource(&xdb, "*font", appData.font);
795 #endif
796 }
797
798 char *
799 PrintArg (ArgType t)
800 {
801   char *p="";
802   switch(t) {
803     case ArgZ:
804     case ArgInt:      p = " N"; break;
805     case ArgString:   p = " STR"; break;
806     case ArgBoolean:  p = " TF"; break;
807     case ArgSettingsFilename:
808     case ArgBackupSettingsFile:
809     case ArgFilename: p = " FILE"; break;
810     case ArgX:        p = " Nx"; break;
811     case ArgY:        p = " Ny"; break;
812     case ArgAttribs:  p = " TEXTCOL"; break;
813     case ArgColor:    p = " COL"; break;
814     case ArgFont:     p = " FONT"; break;
815     case ArgBoardSize: p = " SIZE"; break;
816     case ArgFloat: p = " FLOAT"; break;
817     case ArgTrue:
818     case ArgFalse:
819     case ArgTwo:
820     case ArgNone:
821     case ArgCommSettings:
822     case ArgInstall:
823       break;
824   }
825   return p;
826 }
827
828 char *
829 GenerateGlobalTranslationTable (void)
830 {
831   /* go through all menu items and extract the keyboard shortcuts, so that X11 can load them */
832   char *output;
833
834   int i,j;
835   MenuItem *mi;
836
837   output = strdup("");
838
839   /* loop over all menu entries */
840   for( i=0; menuBar[i].mi ; i++)
841     {
842       mi = menuBar[i].mi;
843       for(j=0; mi[j].proc; j++)
844         {
845           if (mi[j].accel)
846             {
847               int ctrl  = 0;
848               int shift = 0;
849               int alt   = 0;
850
851               char *key,*test, *mods;
852
853               /* check for Ctrl/Alt */
854               if( strstr(mi[j].accel, "<Ctrl>")  ) ctrl  = 1;
855               if( strstr(mi[j].accel, "<Shift>") ) shift = 1;
856               if( strstr(mi[j].accel, "<Alt>")   ) alt   = 1;
857
858               /* remove all <...> */
859               test = strrchr(mi[j].accel, '>');
860               if ( test==NULL )
861                 key = strdup(mi[j].accel);
862               else
863                 key = strdup(++test); // remove ">"
864
865               /* instead of shift X11 uses the uppercase letter directly*/
866               if (shift && strlen(key)==1 )
867                 {
868                   *key  = toupper(*key);
869                   shift = 0;
870                 }
871
872               /* handle some special cases which have different names in X11 */
873               if ( strncmp(key, "Page_Down", 9) == 0 )
874                 {
875                   free(key);
876                   key=strdup("Next");
877                 }
878               else if ( strncmp(key, "Page_Up", 7) == 0 )
879                 {
880                   free(key);
881                   key=strdup("Prior");
882                 };
883
884               /* create string of mods */
885               if (ctrl)
886                 mods = strdup("Ctrl ");
887               else
888                 mods = strdup("");
889
890               if(alt)
891                 {
892                   mods = realloc(mods, strlen(mods) + strlen("Meta ")+1);
893                   strncat(mods, "Meta ", 5);
894                 };
895
896               if(shift)
897                 {
898                   mods = realloc(mods, strlen(mods) + strlen("Shift ")+1);
899                   strncat(mods, "Shift ", 6);
900                 };
901
902               // remove trailing space
903               if( isspace(mods[strlen(mods)-1]) )
904                 mods[strlen(mods)-1]='\0';
905
906               /* get the name for the callback, we can use MenuItem() here that will call KeyBindingProc */
907               size_t namesize = snprintf(NULL, 0, "%s.%s", menuBar[i].ref, mi[j].ref);
908               char *name = malloc(namesize+1);
909               snprintf(name, namesize+1, "%s.%s", menuBar[i].ref, mi[j].ref);
910
911               size_t buffersize = snprintf(NULL, 0, ":%s<Key>%s: MenuItem(%s) \n ", mods, key, name);
912               char *buffer = malloc(buffersize+1);
913               snprintf(buffer, buffersize+1, ":%s<Key>%s: MenuItem(%s) \n ", mods, key, name);
914
915               /* add string to the output */
916               output = realloc(output, strlen(output) + strlen(buffer)+1);
917               strncat(output, buffer, strlen(buffer));
918
919               /* clean up */
920               free(key);
921               free(buffer);
922               free(name);
923               free(mods);
924             }
925         }
926     }
927   return output;
928 }
929
930
931 void
932 PrintOptions ()
933 {
934   char buf[MSG_SIZ];
935   int len=0;
936   ArgDescriptor *q, *p = argDescriptors+5;
937   printf("\nXBoard accepts the following options:\n"
938          "(N = integer, TF = true or false, STR = text string, FILE = filename,\n"
939          " Nx, Ny = relative coordinates, COL = color, FONT = X-font spec,\n"
940          " SIZE = board-size spec(s)\n"
941          " Within parentheses are short forms, or options to set to true or false.\n"
942          " Persistent options (saved in the settings file) are marked with *)\n\n");
943   while(p->argName) {
944     if(p->argType == ArgCommSettings) { p++; continue; } // XBoard has no comm port
945     snprintf(buf+len, MSG_SIZ, "-%s%s", p->argName, PrintArg(p->argType));
946     if(p->save) strcat(buf+len, "*");
947     for(q=p+1; q->argLoc == p->argLoc; q++) {
948       if(q->argName[0] == '-') continue;
949       strcat(buf+len, q == p+1 ? " (" : " ");
950       sprintf(buf+strlen(buf), "-%s%s", q->argName, PrintArg(q->argType));
951     }
952     if(q != p+1) strcat(buf+len, ")");
953     len = strlen(buf);
954     if(len > 39) len = 0, printf("%s\n", buf); else while(len < 39) buf[len++] = ' ';
955     p = q;
956   }
957   if(len) buf[len] = NULLCHAR, printf("%s\n", buf);
958 }
959
960 void
961 SlaveResize (Option *opt)
962 {
963 }
964
965 int
966 main (int argc, char **argv)
967 {
968     int i, clockFontPxlSize, coordFontPxlSize, fontPxlSize;
969     XSetWindowAttributes window_attributes;
970     Arg args[16];
971     Dimension boardWidth, boardHeight, w, h;
972     char *p;
973     int forceMono = False;
974
975     srandom(time(0)); // [HGM] book: make random truly random
976
977     setbuf(stdout, NULL);
978     setbuf(stderr, NULL);
979     debugFP = stderr;
980
981     if(argc > 1 && (!strcmp(argv[1], "-v" ) || !strcmp(argv[1], "--version" ))) {
982       printf("%s version %s\n\n  configure options: %s\n", PACKAGE_NAME, PACKAGE_VERSION, CONFIGURE_OPTIONS);
983         exit(0);
984     }
985
986     if(argc > 1 && !strcmp(argv[1], "--help" )) {
987         PrintOptions();
988         exit(0);
989     }
990
991     programName = strrchr(argv[0], '/');
992     if (programName == NULL)
993       programName = argv[0];
994     else
995       programName++;
996
997 #ifdef ENABLE_NLS
998     XtSetLanguageProc(NULL, NULL, NULL);
999     if (appData.debugMode) {
1000       fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
1001     }
1002
1003     bindtextdomain(PACKAGE, LOCALEDIR);
1004     textdomain(PACKAGE);
1005 #endif
1006
1007     appData.boardSize = "";
1008     InitAppData(ConvertToLine(argc, argv));
1009     p = getenv("HOME");
1010     if (p == NULL) p = "/tmp";
1011     i = strlen(p) + strlen("/.xboardXXXXXx.pgn") + 1;
1012     gameCopyFilename = (char*) malloc(i);
1013     gamePasteFilename = (char*) malloc(i);
1014     snprintf(gameCopyFilename,i, "%s/.xboard%05uc.pgn", p, getpid());
1015     snprintf(gamePasteFilename,i, "%s/.xboard%05up.pgn", p, getpid());
1016
1017     { // [HGM] initstring: kludge to fix bad bug. expand '\n' characters in init string and computer string.
1018         static char buf[MSG_SIZ];
1019         EscapeExpand(buf, appData.firstInitString);
1020         appData.firstInitString = strdup(buf);
1021         EscapeExpand(buf, appData.secondInitString);
1022         appData.secondInitString = strdup(buf);
1023         EscapeExpand(buf, appData.firstComputerString);
1024         appData.firstComputerString = strdup(buf);
1025         EscapeExpand(buf, appData.secondComputerString);
1026         appData.secondComputerString = strdup(buf);
1027     }
1028
1029     if ((chessDir = (char *) getenv("CHESSDIR")) == NULL) {
1030         chessDir = ".";
1031     } else {
1032         if (chdir(chessDir) != 0) {
1033             fprintf(stderr, _("%s: can't cd to CHESSDIR: "), programName);
1034             perror(chessDir);
1035             exit(1);
1036         }
1037     }
1038
1039     if (appData.debugMode && appData.nameOfDebugFile && strcmp(appData.nameOfDebugFile, "stderr")) {
1040         /* [DM] debug info to file [HGM] make the filename a command-line option, and allow it to remain stderr */
1041         if ((debugFP = fopen(appData.nameOfDebugFile, "w")) == NULL)  {
1042            printf(_("Failed to open file '%s'\n"), appData.nameOfDebugFile);
1043            exit(errno);
1044         }
1045         setbuf(debugFP, NULL);
1046     }
1047
1048     /* [HGM,HR] make sure board size is acceptable */
1049     if(appData.NrFiles > BOARD_FILES ||
1050        appData.NrRanks > BOARD_RANKS   )
1051          DisplayFatalError(_("Recompile with larger BOARD_RANKS or BOARD_FILES to support this size"), 0, 2);
1052
1053 #if !HIGHDRAG
1054     /* This feature does not work; animation needs a rewrite */
1055     appData.highlightDragging = FALSE;
1056 #endif
1057     InitBackEnd1();
1058
1059         gameInfo.variant = StringToVariant(appData.variant);
1060         InitPosition(FALSE);
1061
1062     shellWidget =
1063       XtAppInitialize(&appContext, "XBoard", shellOptions,
1064                       XtNumber(shellOptions),
1065                       &argc, argv, xboardResources, NULL, 0);
1066
1067     XtGetApplicationResources(shellWidget, (XtPointer) &appData,
1068                               clientResources, XtNumber(clientResources),
1069                               NULL, 0);
1070
1071     xDisplay = XtDisplay(shellWidget);
1072     xScreen = DefaultScreen(xDisplay);
1073     wm_delete_window = XInternAtom(xDisplay, "WM_DELETE_WINDOW", True);
1074
1075     /*
1076      * determine size, based on supplied or remembered -size, or screen size
1077      */
1078     if (isdigit(appData.boardSize[0])) {
1079         i = sscanf(appData.boardSize, "%d,%d,%d,%d,%d,%d,%d", &squareSize,
1080                    &lineGap, &clockFontPxlSize, &coordFontPxlSize,
1081                    &fontPxlSize, &smallLayout, &tinyLayout);
1082         if (i == 0) {
1083             fprintf(stderr, _("%s: bad boardSize syntax %s\n"),
1084                     programName, appData.boardSize);
1085             exit(2);
1086         }
1087         if (i < 7) {
1088             /* Find some defaults; use the nearest known size */
1089             SizeDefaults *szd, *nearest;
1090             int distance = 99999;
1091             nearest = szd = sizeDefaults;
1092             while (szd->name != NULL) {
1093                 if (abs(szd->squareSize - squareSize) < distance) {
1094                     nearest = szd;
1095                     distance = abs(szd->squareSize - squareSize);
1096                     if (distance == 0) break;
1097                 }
1098                 szd++;
1099             }
1100             if (i < 2) lineGap = nearest->lineGap;
1101             if (i < 3) clockFontPxlSize = nearest->clockFontPxlSize;
1102             if (i < 4) coordFontPxlSize = nearest->coordFontPxlSize;
1103             if (i < 5) fontPxlSize = nearest->fontPxlSize;
1104             if (i < 6) smallLayout = nearest->smallLayout;
1105             if (i < 7) tinyLayout = nearest->tinyLayout;
1106         }
1107     } else {
1108         SizeDefaults *szd = sizeDefaults;
1109         if (*appData.boardSize == NULLCHAR) {
1110             while (DisplayWidth(xDisplay, xScreen) < szd->minScreenSize ||
1111                    DisplayHeight(xDisplay, xScreen) < szd->minScreenSize) {
1112               szd++;
1113             }
1114             if (szd->name == NULL) szd--;
1115             appData.boardSize = strdup(szd->name); // [HGM] settings: remember name for saving settings
1116         } else {
1117             while (szd->name != NULL &&
1118                    StrCaseCmp(szd->name, appData.boardSize) != 0) szd++;
1119             if (szd->name == NULL) {
1120                 fprintf(stderr, _("%s: unrecognized boardSize name %s\n"),
1121                         programName, appData.boardSize);
1122                 exit(2);
1123             }
1124         }
1125         squareSize = szd->squareSize;
1126         lineGap = szd->lineGap;
1127         clockFontPxlSize = szd->clockFontPxlSize;
1128         coordFontPxlSize = szd->coordFontPxlSize;
1129         fontPxlSize = szd->fontPxlSize;
1130         smallLayout = szd->smallLayout;
1131         tinyLayout = szd->tinyLayout;
1132         // [HGM] font: use defaults from settings file if available and not overruled
1133     }
1134
1135     defaultLineGap = lineGap;
1136     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
1137
1138     /* [HR] height treated separately (hacked) */
1139     boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
1140     boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
1141
1142     /*
1143      * Determine what fonts to use.
1144      */
1145     InitializeFonts(clockFontPxlSize, coordFontPxlSize, fontPxlSize);
1146
1147     /*
1148      * Detect if there are not enough colors available and adapt.
1149      */
1150     if (DefaultDepth(xDisplay, xScreen) <= 2) {
1151       appData.monoMode = True;
1152     }
1153
1154     forceMono = MakeColors();
1155
1156     if (forceMono) {
1157       fprintf(stderr, _("%s: too few colors available; trying monochrome mode\n"),
1158               programName);
1159         appData.monoMode = True;
1160     }
1161
1162     if (appData.monoMode && appData.debugMode) {
1163         fprintf(stderr, _("white pixel = 0x%lx, black pixel = 0x%lx\n"),
1164                 (unsigned long) XWhitePixel(xDisplay, xScreen),
1165                 (unsigned long) XBlackPixel(xDisplay, xScreen));
1166     }
1167
1168     ParseIcsTextColors();
1169
1170     XtAppAddActions(appContext, boardActions, XtNumber(boardActions));
1171
1172     /*
1173      * widget hierarchy
1174      */
1175     if (tinyLayout) {
1176         layoutName = "tinyLayout";
1177     } else if (smallLayout) {
1178         layoutName = "smallLayout";
1179     } else {
1180         layoutName = "normalLayout";
1181     }
1182
1183     optList = BoardPopUp(squareSize, lineGap, (void*)
1184 #if ENABLE_NLS
1185                                                 &clockFontSet);
1186 #else
1187                                                 clockFontStruct);
1188 #endif
1189     InitDrawingHandle(optList + W_BOARD);
1190     currBoard        = &optList[W_BOARD];
1191     boardWidget      = optList[W_BOARD].handle;
1192     menuBarWidget    = optList[W_MENU].handle;
1193     dropMenu         = optList[W_DROP].handle;
1194     titleWidget = optList[optList[W_TITLE].type != -1 ? W_TITLE : W_SMALL].handle;
1195     formWidget  = XtParent(boardWidget);
1196     XtSetArg(args[0], XtNbackground, &timerBackgroundPixel);
1197     XtSetArg(args[1], XtNforeground, &timerForegroundPixel);
1198     XtGetValues(optList[W_WHITE].handle, args, 2);
1199     if (appData.showButtonBar) { // can't we use timer pixels for this? (Or better yet, just black & white?)
1200       XtSetArg(args[0], XtNbackground, &buttonBackgroundPixel);
1201       XtSetArg(args[1], XtNforeground, &buttonForegroundPixel);
1202       XtGetValues(optList[W_PAUSE].handle, args, 2);
1203     }
1204
1205     xBoardWindow = XtWindow(boardWidget);
1206
1207     // [HGM] it seems the layout code ends here, but perhaps the color stuff is size independent and would
1208     //       not need to go into InitDrawingSizes().
1209
1210     /*
1211      * Create X checkmark bitmap and initialize option menu checks.
1212      */
1213     ReadBitmap(&xMarkPixmap, "checkmark.bm",
1214                checkmark_bits, checkmark_width, checkmark_height);
1215     InitMenuMarkers();
1216
1217     /*
1218      * Create an icon.
1219      */
1220     ReadBitmap(&wIconPixmap, "icon_white.bm",
1221                icon_white_bits, icon_white_width, icon_white_height);
1222     ReadBitmap(&bIconPixmap, "icon_black.bm",
1223                icon_black_bits, icon_black_width, icon_black_height);
1224     iconPixmap = wIconPixmap;
1225     i = 0;
1226     XtSetArg(args[i], XtNiconPixmap, iconPixmap);  i++;
1227     XtSetValues(shellWidget, args, i);
1228
1229     /*
1230      * Create a cursor for the board widget.
1231      */
1232     window_attributes.cursor = XCreateFontCursor(xDisplay, XC_hand2);
1233     XChangeWindowAttributes(xDisplay, xBoardWindow,
1234                             CWCursor, &window_attributes);
1235
1236     /*
1237      * Inhibit shell resizing.
1238      */
1239     shellArgs[0].value = (XtArgVal) &w;
1240     shellArgs[1].value = (XtArgVal) &h;
1241     XtGetValues(shellWidget, shellArgs, 2);
1242     shellArgs[4].value = shellArgs[2].value = w;
1243     shellArgs[5].value = shellArgs[3].value = h;
1244 //    XtSetValues(shellWidget, &shellArgs[2], 4);
1245     marginW =  w - boardWidth; // [HGM] needed to set new shellWidget size when we resize board
1246     marginH =  h - boardHeight;
1247
1248     CatchDeleteWindow(shellWidget, "QuitProc");
1249
1250     CreateAnyPieces();
1251     CreateGrid();
1252
1253     if(appData.logoSize)
1254     {   // locate and read user logo
1255         char buf[MSG_SIZ];
1256         snprintf(buf, MSG_SIZ, "%s/%s.png", appData.logoDir, UserName());
1257         ASSIGN(userLogo, buf);
1258     }
1259
1260     if (appData.animate || appData.animateDragging)
1261       CreateAnimVars();
1262
1263
1264     char *TranslationsTableMenus=GenerateGlobalTranslationTable ();
1265
1266     XtAugmentTranslations(formWidget,
1267                           XtParseTranslationTable(globalTranslations));
1268     XtAugmentTranslations(formWidget,
1269                           XtParseTranslationTable(TranslationsTableMenus));
1270
1271     XtAddEventHandler(formWidget, KeyPressMask, False,
1272                       (XtEventHandler) MoveTypeInProc, NULL);
1273     XtAddEventHandler(shellWidget, StructureNotifyMask, False,
1274                       (XtEventHandler) EventProc, NULL);
1275
1276     /* [AS] Restore layout */
1277     if( wpMoveHistory.visible ) {
1278       HistoryPopUp();
1279     }
1280
1281     if( wpEvalGraph.visible )
1282       {
1283         EvalGraphPopUp();
1284       };
1285
1286     if( wpEngineOutput.visible ) {
1287       EngineOutputPopUp();
1288     }
1289
1290     InitBackEnd2();
1291
1292     if (errorExitStatus == -1) {
1293         if (appData.icsActive) {
1294             /* We now wait until we see "login:" from the ICS before
1295                sending the logon script (problems with timestamp otherwise) */
1296             /*ICSInitScript();*/
1297             if (appData.icsInputBox) ICSInputBoxPopUp();
1298         }
1299
1300     #ifdef SIGWINCH
1301     signal(SIGWINCH, TermSizeSigHandler);
1302     #endif
1303         signal(SIGINT, IntSigHandler);
1304         signal(SIGTERM, IntSigHandler);
1305         if (*appData.cmailGameName != NULLCHAR) {
1306             signal(SIGUSR1, CmailSigHandler);
1307         }
1308     }
1309
1310     gameInfo.boardWidth = 0; // [HGM] pieces: kludge to ensure InitPosition() calls InitDrawingSizes()
1311     InitPosition(TRUE);
1312     UpdateLogos(TRUE);
1313 //    XtSetKeyboardFocus(shellWidget, formWidget);
1314     XSetInputFocus(xDisplay, XtWindow(formWidget), RevertToPointerRoot, CurrentTime);
1315
1316     XtAppMainLoop(appContext);
1317     if (appData.debugMode) fclose(debugFP); // [DM] debug
1318     return 0;
1319 }
1320
1321 RETSIGTYPE
1322 TermSizeSigHandler (int sig)
1323 {
1324     update_ics_width();
1325 }
1326
1327 RETSIGTYPE
1328 IntSigHandler (int sig)
1329 {
1330     ExitEvent(sig);
1331 }
1332
1333 RETSIGTYPE
1334 CmailSigHandler (int sig)
1335 {
1336     int dummy = 0;
1337     int error;
1338
1339     signal(SIGUSR1, SIG_IGN);   /* suspend handler     */
1340
1341     /* Activate call-back function CmailSigHandlerCallBack()             */
1342     OutputToProcess(cmailPR, (char *)(&dummy), sizeof(int), &error);
1343
1344     signal(SIGUSR1, CmailSigHandler); /* re-activate handler */
1345 }
1346
1347 void
1348 CmailSigHandlerCallBack (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1349 {
1350     BoardToTop();
1351     ReloadCmailMsgEvent(TRUE);  /* Reload cmail msg  */
1352 }
1353 /**** end signal code ****/
1354
1355
1356 #define Abs(n) ((n)<0 ? -(n) : (n))
1357
1358 #ifdef ENABLE_NLS
1359 char *
1360 InsertPxlSize (char *pattern, int targetPxlSize)
1361 {
1362     char *base_fnt_lst, strInt[12], *p, *q;
1363     int alternatives, i, len, strIntLen;
1364
1365     /*
1366      * Replace the "*" (if present) in the pixel-size slot of each
1367      * alternative with the targetPxlSize.
1368      */
1369     p = pattern;
1370     alternatives = 1;
1371     while ((p = strchr(p, ',')) != NULL) {
1372       alternatives++;
1373       p++;
1374     }
1375     snprintf(strInt, sizeof(strInt), "%d", targetPxlSize);
1376     strIntLen = strlen(strInt);
1377     base_fnt_lst = calloc(1, strlen(pattern) + strIntLen * alternatives + 1);
1378
1379     p = pattern;
1380     q = base_fnt_lst;
1381     while (alternatives--) {
1382       char *comma = strchr(p, ',');
1383       for (i=0; i<14; i++) {
1384         char *hyphen = strchr(p, '-');
1385         if (!hyphen) break;
1386         if (comma && hyphen > comma) break;
1387         len = hyphen + 1 - p;
1388         if (i == 7 && *p == '*' && len == 2) {
1389           p += len;
1390           memcpy(q, strInt, strIntLen);
1391           q += strIntLen;
1392           *q++ = '-';
1393         } else {
1394           memcpy(q, p, len);
1395           p += len;
1396           q += len;
1397         }
1398       }
1399       if (!comma) break;
1400       len = comma + 1 - p;
1401       memcpy(q, p, len);
1402       p += len;
1403       q += len;
1404     }
1405     strcpy(q, p);
1406
1407     return base_fnt_lst;
1408 }
1409
1410 XFontSet
1411 CreateFontSet (char *base_fnt_lst)
1412 {
1413     XFontSet fntSet;
1414     char **missing_list;
1415     int missing_count;
1416     char *def_string;
1417
1418     fntSet = XCreateFontSet(xDisplay, base_fnt_lst,
1419                             &missing_list, &missing_count, &def_string);
1420     if (appData.debugMode) {
1421       int i, count;
1422       XFontStruct **font_struct_list;
1423       char **font_name_list;
1424       fprintf(debugFP, "Requested font set for list %s\n", base_fnt_lst);
1425       if (fntSet) {
1426         fprintf(debugFP, " got list %s, locale %s\n",
1427                 XBaseFontNameListOfFontSet(fntSet),
1428                 XLocaleOfFontSet(fntSet));
1429         count = XFontsOfFontSet(fntSet, &font_struct_list, &font_name_list);
1430         for (i = 0; i < count; i++) {
1431           fprintf(debugFP, " got charset %s\n", font_name_list[i]);
1432         }
1433       }
1434       for (i = 0; i < missing_count; i++) {
1435         fprintf(debugFP, " missing charset %s\n", missing_list[i]);
1436       }
1437     }
1438     if (fntSet == NULL) {
1439       fprintf(stderr, _("Unable to create font set for %s.\n"), base_fnt_lst);
1440       exit(2);
1441     }
1442     return fntSet;
1443 }
1444 #else // not ENABLE_NLS
1445 /*
1446  * Find a font that matches "pattern" that is as close as
1447  * possible to the targetPxlSize.  Prefer fonts that are k
1448  * pixels smaller to fonts that are k pixels larger.  The
1449  * pattern must be in the X Consortium standard format,
1450  * e.g. "-*-helvetica-bold-r-normal--*-*-*-*-*-*-*-*".
1451  * The return value should be freed with XtFree when no
1452  * longer needed.
1453  */
1454 char *
1455 FindFont (char *pattern, int targetPxlSize)
1456 {
1457     char **fonts, *p, *best, *scalable, *scalableTail;
1458     int i, j, nfonts, minerr, err, pxlSize;
1459
1460     fonts = XListFonts(xDisplay, pattern, 999999, &nfonts);
1461     if (nfonts < 1) {
1462         fprintf(stderr, _("%s: no fonts match pattern %s\n"),
1463                 programName, pattern);
1464         exit(2);
1465     }
1466
1467     best = fonts[0];
1468     scalable = NULL;
1469     minerr = 999999;
1470     for (i=0; i<nfonts; i++) {
1471         j = 0;
1472         p = fonts[i];
1473         if (*p != '-') continue;
1474         while (j < 7) {
1475             if (*p == NULLCHAR) break;
1476             if (*p++ == '-') j++;
1477         }
1478         if (j < 7) continue;
1479         pxlSize = atoi(p);
1480         if (pxlSize == 0) {
1481             scalable = fonts[i];
1482             scalableTail = p;
1483         } else {
1484             err = pxlSize - targetPxlSize;
1485             if (Abs(err) < Abs(minerr) ||
1486                 (minerr > 0 && err < 0 && -err == minerr)) {
1487                 best = fonts[i];
1488                 minerr = err;
1489             }
1490         }
1491     }
1492     if (scalable && Abs(minerr) > appData.fontSizeTolerance) {
1493         /* If the error is too big and there is a scalable font,
1494            use the scalable font. */
1495         int headlen = scalableTail - scalable;
1496         p = (char *) XtMalloc(strlen(scalable) + 10);
1497         while (isdigit(*scalableTail)) scalableTail++;
1498         sprintf(p, "%.*s%d%s", headlen, scalable, targetPxlSize, scalableTail);
1499     } else {
1500         p = (char *) XtMalloc(strlen(best) + 2);
1501         safeStrCpy(p, best, strlen(best)+1 );
1502     }
1503     if (appData.debugMode) {
1504         fprintf(debugFP, "resolved %s at pixel size %d\n  to %s\n",
1505                 pattern, targetPxlSize, p);
1506     }
1507     XFreeFontNames(fonts);
1508     return p;
1509 }
1510 #endif
1511
1512 void
1513 ReadBitmap (Pixmap *pm, String name, unsigned char bits[], u_int wreq, u_int hreq)
1514 {
1515     if (bits != NULL) {
1516         *pm = XCreateBitmapFromData(xDisplay, xBoardWindow, (char *) bits,
1517                                     wreq, hreq);
1518     }
1519 }
1520
1521 void
1522 MarkMenuItem (char *menuRef, int state)
1523 {
1524     MenuItem *item = MenuNameToItem(menuRef);
1525
1526     if(item) {
1527         Arg args[2];
1528         XtSetArg(args[0], XtNleftBitmap, state ? xMarkPixmap : None);
1529         XtSetValues(item->handle, args, 1);
1530     }
1531 }
1532
1533 void
1534 EnableNamedMenuItem (char *menuRef, int state)
1535 {
1536     MenuItem *item = MenuNameToItem(menuRef);
1537
1538     if(item) XtSetSensitive(item->handle, state);
1539 }
1540
1541 void
1542 EnableButtonBar (int state)
1543 {
1544     XtSetSensitive(optList[W_BUTTON].handle, state);
1545 }
1546
1547
1548 void
1549 SetMenuEnables (Enables *enab)
1550 {
1551   while (enab->name != NULL) {
1552     EnableNamedMenuItem(enab->name, enab->value);
1553     enab++;
1554   }
1555 }
1556
1557 void
1558 KeyBindingProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1559 {   // [HGM] new method of key binding: specify MenuItem(FlipView) in stead of FlipViewProc in translation string
1560     MenuItem *item;
1561     if(*nprms == 0) return;
1562     item = MenuNameToItem(prms[0]);
1563     if(item) ((MenuProc *) item->proc) ();
1564 }
1565
1566 void
1567 SetupDropMenu ()
1568 {
1569     int i, j, count;
1570     char label[32];
1571     Arg args[16];
1572     Widget entry;
1573     char* p;
1574
1575     for (i=0; i<sizeof(dmEnables)/sizeof(DropMenuEnables); i++) {
1576         entry = XtNameToWidget(dropMenu, dmEnables[i].widget);
1577         p = strchr(gameMode == IcsPlayingWhite ? white_holding : black_holding,
1578                    dmEnables[i].piece);
1579         XtSetSensitive(entry, p != NULL || !appData.testLegality
1580                        /*!!temp:*/ || (gameInfo.variant == VariantCrazyhouse
1581                                        && !appData.icsActive));
1582         count = 0;
1583         while (p && *p++ == dmEnables[i].piece) count++;
1584         snprintf(label, sizeof(label), "%s  %d", dmEnables[i].widget, count);
1585         j = 0;
1586         XtSetArg(args[j], XtNlabel, label); j++;
1587         XtSetValues(entry, args, j);
1588     }
1589 }
1590
1591 static void
1592 do_flash_delay (unsigned long msec)
1593 {
1594     TimeDelay(msec);
1595 }
1596
1597 void
1598 FlashDelay (int flash_delay)
1599 {
1600         XSync(xDisplay, False);
1601         if(flash_delay) do_flash_delay(flash_delay);
1602 }
1603
1604 double
1605 Fraction (int x, int start, int stop)
1606 {
1607    double f = ((double) x - start)/(stop - start);
1608    if(f > 1.) f = 1.; else if(f < 0.) f = 0.;
1609    return f;
1610 }
1611
1612 static WindowPlacement wpNew;
1613
1614 void
1615 CoDrag (Widget sh, WindowPlacement *wp)
1616 {
1617     Arg args[16];
1618     int j=0, touch=0, fudge = 2;
1619     GetActualPlacement(sh, wp);
1620     if(abs(wpMain.x + wpMain.width + 2*frameX - wp->x)         < fudge) touch = 1; else // right touch
1621     if(abs(wp->x + wp->width + 2*frameX - wpMain.x)            < fudge) touch = 2; else // left touch
1622     if(abs(wpMain.y + wpMain.height + frameX + frameY - wp->y) < fudge) touch = 3; else // bottom touch
1623     if(abs(wp->y + wp->height + frameX + frameY - wpMain.y)    < fudge) touch = 4;      // top touch
1624     if(!touch ) return; // only windows that touch co-move
1625     if(touch < 3 && wpNew.height != wpMain.height) { // left or right and height changed
1626         int heightInc = wpNew.height - wpMain.height;
1627         double fracTop = Fraction(wp->y, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1628         double fracBot = Fraction(wp->y + wp->height + frameX + frameY + 1, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1629         wp->y += fracTop * heightInc;
1630         heightInc = (int) (fracBot * heightInc) - (int) (fracTop * heightInc);
1631         if(heightInc) XtSetArg(args[j], XtNheight, wp->height + heightInc), j++;
1632     } else if(touch > 2 && wpNew.width != wpMain.width) { // top or bottom and width changed
1633         int widthInc = wpNew.width - wpMain.width;
1634         double fracLeft = Fraction(wp->x, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1635         double fracRght = Fraction(wp->x + wp->width + 2*frameX + 1, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1636         wp->y += fracLeft * widthInc;
1637         widthInc = (int) (fracRght * widthInc) - (int) (fracLeft * widthInc);
1638         if(widthInc) XtSetArg(args[j], XtNwidth, wp->width + widthInc), j++;
1639     }
1640     wp->x += wpNew.x - wpMain.x;
1641     wp->y += wpNew.y - wpMain.y;
1642     if(touch == 1) wp->x += wpNew.width - wpMain.width; else
1643     if(touch == 3) wp->y += wpNew.height - wpMain.height;
1644     XtSetArg(args[j], XtNx, wp->x); j++;
1645     XtSetArg(args[j], XtNy, wp->y); j++;
1646     XtSetValues(sh, args, j);
1647 }
1648
1649 void
1650 ReSize (WindowPlacement *wp)
1651 {
1652         int sqx, sqy, w, h;
1653         if(wp->width == wpMain.width && wp->height == wpMain.height) return; // not sized
1654         sqx = (wp->width  - lineGap - marginW) / BOARD_WIDTH - lineGap;
1655         sqy = (wp->height - lineGap - marginH) / BOARD_HEIGHT - lineGap;
1656         if(sqy < sqx) sqx = sqy;
1657         if(sqx != squareSize) {
1658             squareSize = sqx; // adopt new square size
1659             CreatePNGPieces(); // make newly scaled pieces
1660             InitDrawingSizes(0, 0); // creates grid etc.
1661         } else ResizeBoardWindow(BOARD_WIDTH * (squareSize + lineGap) + lineGap, BOARD_HEIGHT * (squareSize + lineGap) + lineGap, 0);
1662         w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
1663         h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
1664         if(optList[W_BOARD].max   > w) optList[W_BOARD].max = w;
1665         if(optList[W_BOARD].value > h) optList[W_BOARD].value = h;
1666 }
1667
1668 static XtIntervalId delayedDragID = 0;
1669
1670 void
1671 DragProc ()
1672 {
1673         static int busy;
1674         if(busy) return;
1675
1676         busy = 1;
1677         GetActualPlacement(shellWidget, &wpNew);
1678         if(wpNew.x == wpMain.x && wpNew.y == wpMain.y && // not moved
1679            wpNew.width == wpMain.width && wpNew.height == wpMain.height) { // not sized
1680             busy = 0; return; // false alarm
1681         }
1682         ReSize(&wpNew);
1683         if(shellUp[EngOutDlg]) CoDrag(shells[EngOutDlg], &wpEngineOutput);
1684         if(shellUp[HistoryDlg]) CoDrag(shells[HistoryDlg], &wpMoveHistory);
1685         if(shellUp[EvalGraphDlg]) CoDrag(shells[EvalGraphDlg], &wpEvalGraph);
1686         if(shellUp[GameListDlg]) CoDrag(shells[GameListDlg], &wpGameList);
1687         wpMain = wpNew;
1688         DrawPosition(True, NULL);
1689         delayedDragID = 0; // now drag executed, make sure next DelayedDrag will not cancel timer event (which could now be used by other)
1690         busy = 0;
1691 }
1692
1693
1694 void
1695 DelayedDrag ()
1696 {
1697     if(delayedDragID) XtRemoveTimeOut(delayedDragID); // cancel pending
1698     delayedDragID =
1699       XtAppAddTimeOut(appContext, 200, (XtTimerCallbackProc) DragProc, (XtPointer) 0); // and schedule new one 50 msec later
1700 }
1701
1702 void
1703 EventProc (Widget widget, caddr_t unused, XEvent *event)
1704 {
1705     if(XtIsRealized(widget) && event->type == ConfigureNotify || appData.useStickyWindows)
1706         DelayedDrag(); // as long as events keep coming in faster than 50 msec, they destroy each other
1707 }
1708
1709 /*
1710  * event handler for redrawing the board
1711  */
1712 void
1713 DrawPositionProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1714 {
1715     DrawPosition(True, NULL);
1716 }
1717
1718
1719 void
1720 HandlePV (Widget w, XEvent * event, String * params, Cardinal * nParams)
1721 {   // [HGM] pv: walk PV
1722     MovePV(event->xmotion.x, event->xmotion.y, lineGap + BOARD_HEIGHT * (squareSize + lineGap));
1723 }
1724
1725 extern int savedIndex;  /* gross that this is global */
1726
1727 void
1728 CommentClick (Widget w, XEvent * event, String * params, Cardinal * nParams)
1729 {
1730         String val;
1731         XawTextPosition index, dummy;
1732         Arg arg;
1733
1734         XawTextGetSelectionPos(w, &index, &dummy);
1735         XtSetArg(arg, XtNstring, &val);
1736         XtGetValues(w, &arg, 1);
1737         ReplaceComment(savedIndex, val);
1738         if(savedIndex != currentMove) ToNrEvent(savedIndex);
1739         LoadVariation( index, val ); // [HGM] also does the actual moving to it, now
1740 }
1741
1742
1743 /* Disable all user input other than deleting the window */
1744 static int frozen = 0;
1745
1746 void
1747 FreezeUI ()
1748 {
1749   if (frozen) return;
1750   /* Grab by a widget that doesn't accept input */
1751   XtAddGrab(optList[W_MESSG].handle, TRUE, FALSE);
1752   frozen = 1;
1753 }
1754
1755 /* Undo a FreezeUI */
1756 void
1757 ThawUI ()
1758 {
1759   if (!frozen) return;
1760   XtRemoveGrab(optList[W_MESSG].handle);
1761   frozen = 0;
1762 }
1763
1764 void
1765 ModeHighlight ()
1766 {
1767     Arg args[16];
1768     static int oldPausing = FALSE;
1769     static GameMode oldmode = (GameMode) -1;
1770     char *wname;
1771
1772     if (!boardWidget || !XtIsRealized(boardWidget)) return;
1773
1774     if (pausing != oldPausing) {
1775         oldPausing = pausing;
1776         MarkMenuItem("Mode.Pause", pausing);
1777
1778         if (appData.showButtonBar) {
1779           /* Always toggle, don't set.  Previous code messes up when
1780              invoked while the button is pressed, as releasing it
1781              toggles the state again. */
1782           {
1783             Pixel oldbg, oldfg;
1784             XtSetArg(args[0], XtNbackground, &oldbg);
1785             XtSetArg(args[1], XtNforeground, &oldfg);
1786             XtGetValues(optList[W_PAUSE].handle,
1787                         args, 2);
1788             XtSetArg(args[0], XtNbackground, oldfg);
1789             XtSetArg(args[1], XtNforeground, oldbg);
1790           }
1791           XtSetValues(optList[W_PAUSE].handle, args, 2);
1792         }
1793     }
1794
1795     wname = ModeToWidgetName(oldmode);
1796     if (wname != NULL) {
1797         MarkMenuItem(wname, False);
1798     }
1799     wname = ModeToWidgetName(gameMode);
1800     if (wname != NULL) {
1801         MarkMenuItem(wname, True);
1802     }
1803     oldmode = gameMode;
1804     MarkMenuItem("Mode.MachineMatch", matchMode && matchGame < appData.matchGames);
1805
1806     /* Maybe all the enables should be handled here, not just this one */
1807     EnableNamedMenuItem("Mode.Training", gameMode == Training || gameMode == PlayFromGameFile);
1808
1809     DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
1810 }
1811
1812
1813 /*
1814  * Button/menu procedures
1815  */
1816
1817 /* this variable is shared between CopyPositionProc and SendPositionSelection */
1818 char *selected_fen_position=NULL;
1819
1820 Boolean
1821 SendPositionSelection (Widget w, Atom *selection, Atom *target,
1822                        Atom *type_return, XtPointer *value_return,
1823                        unsigned long *length_return, int *format_return)
1824 {
1825   char *selection_tmp;
1826
1827 //  if (!selected_fen_position) return False; /* should never happen */
1828   if (*target == XA_STRING || *target == XA_UTF8_STRING(xDisplay)){
1829    if (!selected_fen_position) { // since it never happens, we use it for indicating a game is being sent
1830     FILE* f = fopen(gameCopyFilename, "r"); // This code, taken from SendGameSelection, now merges the two
1831     long len;
1832     size_t count;
1833     if (f == NULL) return False;
1834     fseek(f, 0, 2);
1835     len = ftell(f);
1836     rewind(f);
1837     selection_tmp = XtMalloc(len + 1);
1838     count = fread(selection_tmp, 1, len, f);
1839     fclose(f);
1840     if (len != count) {
1841       XtFree(selection_tmp);
1842       return False;
1843     }
1844     selection_tmp[len] = NULLCHAR;
1845    } else {
1846     /* note: since no XtSelectionDoneProc was registered, Xt will
1847      * automatically call XtFree on the value returned.  So have to
1848      * make a copy of it allocated with XtMalloc */
1849     selection_tmp= XtMalloc(strlen(selected_fen_position)+16);
1850     safeStrCpy(selection_tmp, selected_fen_position, strlen(selected_fen_position)+16 );
1851    }
1852
1853     *value_return=selection_tmp;
1854     *length_return=strlen(selection_tmp);
1855     *type_return=*target;
1856     *format_return = 8; /* bits per byte */
1857     return True;
1858   } else if (*target == XA_TARGETS(xDisplay)) {
1859     Atom *targets_tmp = (Atom *) XtMalloc(2 * sizeof(Atom));
1860     targets_tmp[0] = XA_UTF8_STRING(xDisplay);
1861     targets_tmp[1] = XA_STRING;
1862     *value_return = targets_tmp;
1863     *type_return = XA_ATOM;
1864     *length_return = 2;
1865 #if 0
1866     // This code leads to a read of value_return out of bounds on 64-bit systems.
1867     // Other code which I have seen always sets *format_return to 32 independent of
1868     // sizeof(Atom) without adjusting *length_return. For instance see TextConvertSelection()
1869     // at http://cgit.freedesktop.org/xorg/lib/libXaw/tree/src/Text.c -- BJ
1870     *format_return = 8 * sizeof(Atom);
1871     if (*format_return > 32) {
1872       *length_return *= *format_return / 32;
1873       *format_return = 32;
1874     }
1875 #else
1876     *format_return = 32;
1877 #endif
1878     return True;
1879   } else {
1880     return False;
1881   }
1882 }
1883
1884 /* note: when called from menu all parameters are NULL, so no clue what the
1885  * Widget which was clicked on was, or what the click event was
1886  */
1887 void
1888 CopySomething (char *src)
1889 {
1890     selected_fen_position = src;
1891     /*
1892      * Set both PRIMARY (the selection) and CLIPBOARD, since we don't
1893      * have a notion of a position that is selected but not copied.
1894      * See http://www.freedesktop.org/wiki/Specifications/ClipboardsWiki
1895      */
1896     XtOwnSelection(menuBarWidget, XA_PRIMARY,
1897                    CurrentTime,
1898                    SendPositionSelection,
1899                    NULL/* lose_ownership_proc */ ,
1900                    NULL/* transfer_done_proc */);
1901     XtOwnSelection(menuBarWidget, XA_CLIPBOARD(xDisplay),
1902                    CurrentTime,
1903                    SendPositionSelection,
1904                    NULL/* lose_ownership_proc */ ,
1905                    NULL/* transfer_done_proc */);
1906 }
1907
1908 /* function called when the data to Paste is ready */
1909 static void
1910 PastePositionCB (Widget w, XtPointer client_data, Atom *selection,
1911                  Atom *type, XtPointer value, unsigned long *len, int *format)
1912 {
1913   char *fenstr=value;
1914   if (value==NULL || *len==0) return; /* nothing had been selected to copy */
1915   fenstr[*len]='\0'; /* normally this string is terminated, but be safe */
1916   EditPositionPasteFEN(fenstr);
1917   XtFree(value);
1918 }
1919
1920 /* called when Paste Position button is pressed,
1921  * all parameters will be NULL */
1922 void
1923 PastePositionProc ()
1924 {
1925     XtGetSelectionValue(menuBarWidget,
1926       appData.pasteSelection ? XA_PRIMARY: XA_CLIPBOARD(xDisplay), XA_STRING,
1927       /* (XtSelectionCallbackProc) */ PastePositionCB,
1928       NULL, /* client_data passed to PastePositionCB */
1929
1930       /* better to use the time field from the event that triggered the
1931        * call to this function, but that isn't trivial to get
1932        */
1933       CurrentTime
1934     );
1935     return;
1936 }
1937
1938 /* note: when called from menu all parameters are NULL, so no clue what the
1939  * Widget which was clicked on was, or what the click event was
1940  */
1941 /* function called when the data to Paste is ready */
1942 static void
1943 PasteGameCB (Widget w, XtPointer client_data, Atom *selection,
1944              Atom *type, XtPointer value, unsigned long *len, int *format)
1945 {
1946   FILE* f;
1947   if (value == NULL || *len == 0) {
1948     return; /* nothing had been selected to copy */
1949   }
1950   f = fopen(gamePasteFilename, "w");
1951   if (f == NULL) {
1952     DisplayError(_("Can't open temp file"), errno);
1953     return;
1954   }
1955   fwrite(value, 1, *len, f);
1956   fclose(f);
1957   XtFree(value);
1958   LoadGameFromFile(gamePasteFilename, 0, gamePasteFilename, TRUE);
1959 }
1960
1961 /* called when Paste Game button is pressed,
1962  * all parameters will be NULL */
1963 void
1964 PasteGameProc ()
1965 {
1966     XtGetSelectionValue(menuBarWidget,
1967       appData.pasteSelection ? XA_PRIMARY: XA_CLIPBOARD(xDisplay), XA_STRING,
1968       /* (XtSelectionCallbackProc) */ PasteGameCB,
1969       NULL, /* client_data passed to PasteGameCB */
1970
1971       /* better to use the time field from the event that triggered the
1972        * call to this function, but that isn't trivial to get
1973        */
1974       CurrentTime
1975     );
1976     return;
1977 }
1978
1979
1980 void
1981 QuitWrapper (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1982 {
1983     QuitProc();
1984 }
1985
1986 int
1987 ShiftKeys ()
1988 {   // bassic primitive for determining if modifier keys are pressed
1989     long int codes[] = { XK_Meta_L, XK_Meta_R, XK_Control_L, XK_Control_R, XK_Shift_L, XK_Shift_R };
1990     char keys[32];
1991     int i,j,  k=0;
1992     XQueryKeymap(xDisplay,keys);
1993     for(i=0; i<6; i++) {
1994         k <<= 1;
1995         j = XKeysymToKeycode(xDisplay, codes[i]);
1996         k += ( (keys[j>>3]&1<<(j&7)) != 0 );
1997     }
1998     return k;
1999 }
2000
2001 static void
2002 MoveTypeInProc (Widget widget, caddr_t unused, XEvent *event)
2003 {
2004     char buf[10];
2005     KeySym sym;
2006     int n = XLookupString(&(event->xkey), buf, 10, &sym, NULL);
2007     if ( n == 1 && *buf >= 32 // printable
2008          && !(ShiftKeys() & 0x3C) // no Alt, Ctrl
2009         ) BoxAutoPopUp (buf);
2010 }
2011
2012 static void
2013 UpKeyProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
2014 {   // [HGM] input: let up-arrow recall previous line from history
2015     IcsKey(1);
2016 }
2017
2018 static void
2019 DownKeyProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
2020 {   // [HGM] input: let down-arrow recall next line from history
2021     IcsKey(-1);
2022 }
2023
2024 static void
2025 EnterKeyProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
2026 {
2027     IcsKey(0);
2028 }
2029
2030 void
2031 TempBackwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
2032 {
2033         if (!TempBackwardActive) {
2034                 TempBackwardActive = True;
2035                 BackwardEvent();
2036         }
2037 }
2038
2039 void
2040 TempForwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
2041 {
2042         /* Check to see if triggered by a key release event for a repeating key.
2043          * If so the next queued event will be a key press of the same key at the same time */
2044         if (XEventsQueued(xDisplay, QueuedAfterReading)) {
2045                 XEvent next;
2046                 XPeekEvent(xDisplay, &next);
2047                 if (next.type == KeyPress && next.xkey.time == event->xkey.time &&
2048                         next.xkey.keycode == event->xkey.keycode)
2049                                 return;
2050         }
2051     ForwardEvent();
2052         TempBackwardActive = False;
2053 }
2054
2055 void
2056 ManInner (Widget w, XEvent *event, String *prms, Cardinal *nprms)
2057 {   // called as key binding
2058     char buf[MSG_SIZ];
2059     String name;
2060     if (nprms && *nprms > 0)
2061       name = prms[0];
2062     else
2063       name = "xboard";
2064     snprintf(buf, sizeof(buf), "xterm -e man %s &", name);
2065     system(buf);
2066 }
2067
2068 void
2069 ManProc ()
2070 {   // called from menu
2071     ManInner(NULL, NULL, NULL, NULL);
2072 }
2073
2074 void
2075 SetWindowTitle (char *text, char *title, char *icon)
2076 {
2077     Arg args[16];
2078     int i;
2079     if (appData.titleInWindow) {
2080         i = 0;
2081         XtSetArg(args[i], XtNlabel, text);   i++;
2082         XtSetValues(titleWidget, args, i);
2083     }
2084     i = 0;
2085     XtSetArg(args[i], XtNiconName, (XtArgVal) icon);    i++;
2086     XtSetArg(args[i], XtNtitle, (XtArgVal) title);      i++;
2087     XtSetValues(shellWidget, args, i);
2088     XSync(xDisplay, False);
2089 }
2090
2091
2092 static int
2093 NullXErrorCheck (Display *dpy, XErrorEvent *error_event)
2094 {
2095     return 0;
2096 }
2097
2098 void
2099 DisplayIcsInteractionTitle (String message)
2100 {
2101   if (oldICSInteractionTitle == NULL) {
2102     /* Magic to find the old window title, adapted from vim */
2103     char *wina = getenv("WINDOWID");
2104     if (wina != NULL) {
2105       Window win = (Window) atoi(wina);
2106       Window root, parent, *children;
2107       unsigned int nchildren;
2108       int (*oldHandler)() = XSetErrorHandler(NullXErrorCheck);
2109       for (;;) {
2110         if (XFetchName(xDisplay, win, &oldICSInteractionTitle)) break;
2111         if (!XQueryTree(xDisplay, win, &root, &parent,
2112                         &children, &nchildren)) break;
2113         if (children) XFree((void *)children);
2114         if (parent == root || parent == 0) break;
2115         win = parent;
2116       }
2117       XSetErrorHandler(oldHandler);
2118     }
2119     if (oldICSInteractionTitle == NULL) {
2120       oldICSInteractionTitle = "xterm";
2121     }
2122   }
2123   printf("\033]0;%s\007", message);
2124   fflush(stdout);
2125 }
2126
2127
2128 XtIntervalId delayedEventTimerXID = 0;
2129 DelayedEventCallback delayedEventCallback = 0;
2130
2131 void
2132 FireDelayedEvent ()
2133 {
2134     delayedEventTimerXID = 0;
2135     delayedEventCallback();
2136 }
2137
2138 void
2139 ScheduleDelayedEvent (DelayedEventCallback cb, long millisec)
2140 {
2141     if(delayedEventTimerXID && delayedEventCallback == cb)
2142         // [HGM] alive: replace, rather than add or flush identical event
2143         XtRemoveTimeOut(delayedEventTimerXID);
2144     delayedEventCallback = cb;
2145     delayedEventTimerXID =
2146       XtAppAddTimeOut(appContext, millisec,
2147                       (XtTimerCallbackProc) FireDelayedEvent, (XtPointer) 0);
2148 }
2149
2150 DelayedEventCallback
2151 GetDelayedEvent ()
2152 {
2153   if (delayedEventTimerXID) {
2154     return delayedEventCallback;
2155   } else {
2156     return NULL;
2157   }
2158 }
2159
2160 void
2161 CancelDelayedEvent ()
2162 {
2163   if (delayedEventTimerXID) {
2164     XtRemoveTimeOut(delayedEventTimerXID);
2165     delayedEventTimerXID = 0;
2166   }
2167 }
2168
2169 XtIntervalId loadGameTimerXID = 0;
2170
2171 int
2172 LoadGameTimerRunning ()
2173 {
2174     return loadGameTimerXID != 0;
2175 }
2176
2177 int
2178 StopLoadGameTimer ()
2179 {
2180     if (loadGameTimerXID != 0) {
2181         XtRemoveTimeOut(loadGameTimerXID);
2182         loadGameTimerXID = 0;
2183         return TRUE;
2184     } else {
2185         return FALSE;
2186     }
2187 }
2188
2189 void
2190 LoadGameTimerCallback (XtPointer arg, XtIntervalId *id)
2191 {
2192     loadGameTimerXID = 0;
2193     AutoPlayGameLoop();
2194 }
2195
2196 void
2197 StartLoadGameTimer (long millisec)
2198 {
2199     loadGameTimerXID =
2200       XtAppAddTimeOut(appContext, millisec,
2201                       (XtTimerCallbackProc) LoadGameTimerCallback,
2202                       (XtPointer) 0);
2203 }
2204
2205 XtIntervalId analysisClockXID = 0;
2206
2207 void
2208 AnalysisClockCallback (XtPointer arg, XtIntervalId *id)
2209 {
2210     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
2211          || appData.icsEngineAnalyze) { // [DM]
2212         AnalysisPeriodicEvent(0);
2213         StartAnalysisClock();
2214     }
2215 }
2216
2217 void
2218 StartAnalysisClock ()
2219 {
2220     analysisClockXID =
2221       XtAppAddTimeOut(appContext, 2000,
2222                       (XtTimerCallbackProc) AnalysisClockCallback,
2223                       (XtPointer) 0);
2224 }
2225
2226 XtIntervalId clockTimerXID = 0;
2227
2228 int
2229 ClockTimerRunning ()
2230 {
2231     return clockTimerXID != 0;
2232 }
2233
2234 int
2235 StopClockTimer ()
2236 {
2237     if (clockTimerXID != 0) {
2238         XtRemoveTimeOut(clockTimerXID);
2239         clockTimerXID = 0;
2240         return TRUE;
2241     } else {
2242         return FALSE;
2243     }
2244 }
2245
2246 void
2247 ClockTimerCallback (XtPointer arg, XtIntervalId *id)
2248 {
2249     clockTimerXID = 0;
2250     DecrementClocks();
2251 }
2252
2253 void
2254 StartClockTimer (long millisec)
2255 {
2256     clockTimerXID =
2257       XtAppAddTimeOut(appContext, millisec,
2258                       (XtTimerCallbackProc) ClockTimerCallback,
2259                       (XtPointer) 0);
2260 }
2261
2262 void
2263 DisplayTimerLabel (Option *opt, char *color, long timer, int highlight)
2264 {
2265     char buf[MSG_SIZ];
2266     Arg args[16];
2267     Widget w = (Widget) opt->handle;
2268
2269     /* check for low time warning */
2270     Pixel foregroundOrWarningColor = timerForegroundPixel;
2271
2272     if (timer > 0 &&
2273         appData.lowTimeWarning &&
2274         (timer / 1000) < appData.icsAlarmTime)
2275       foregroundOrWarningColor = lowTimeWarningColor;
2276
2277     if (appData.clockMode) {
2278       snprintf(buf, MSG_SIZ, "%s:%s%s", color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
2279       XtSetArg(args[0], XtNlabel, buf);
2280     } else {
2281       snprintf(buf, MSG_SIZ, "%s  ", color);
2282       XtSetArg(args[0], XtNlabel, buf);
2283     }
2284
2285     if (highlight) {
2286
2287         XtSetArg(args[1], XtNbackground, foregroundOrWarningColor);
2288         XtSetArg(args[2], XtNforeground, timerBackgroundPixel);
2289     } else {
2290         XtSetArg(args[1], XtNbackground, timerBackgroundPixel);
2291         XtSetArg(args[2], XtNforeground, foregroundOrWarningColor);
2292     }
2293
2294     XtSetValues(w, args, 3);
2295 }
2296
2297 static Pixmap *clockIcons[] = { &wIconPixmap, &bIconPixmap };
2298
2299 void
2300 SetClockIcon (int color)
2301 {
2302     Arg args[16];
2303     Pixmap pm = *clockIcons[color];
2304     if (iconPixmap != pm) {
2305         iconPixmap = pm;
2306         XtSetArg(args[0], XtNiconPixmap, iconPixmap);
2307         XtSetValues(shellWidget, args, 1);
2308     }
2309 }
2310
2311 #define INPUT_SOURCE_BUF_SIZE 8192
2312
2313 typedef struct {
2314     CPKind kind;
2315     int fd;
2316     int lineByLine;
2317     char *unused;
2318     InputCallback func;
2319     XtInputId xid;
2320     char buf[INPUT_SOURCE_BUF_SIZE];
2321     VOIDSTAR closure;
2322 } InputSource;
2323
2324 void
2325 DoInputCallback (caddr_t closure, int *source, XtInputId *xid)
2326 {
2327     InputSource *is = (InputSource *) closure;
2328     int count;
2329     int error;
2330     char *p, *q;
2331
2332     if (is->lineByLine) {
2333         count = read(is->fd, is->unused,
2334                      INPUT_SOURCE_BUF_SIZE - (is->unused - is->buf));
2335         if (count <= 0) {
2336             (is->func)(is, is->closure, is->buf, count, count ? errno : 0);
2337             return;
2338         }
2339         is->unused += count;
2340         p = is->buf;
2341         while (p < is->unused) {
2342             q = memchr(p, '\n', is->unused - p);
2343             if (q == NULL) break;
2344             q++;
2345             (is->func)(is, is->closure, p, q - p, 0);
2346             p = q;
2347         }
2348         q = is->buf;
2349         while (p < is->unused) {
2350             *q++ = *p++;
2351         }
2352         is->unused = q;
2353     } else {
2354         count = read(is->fd, is->buf, INPUT_SOURCE_BUF_SIZE);
2355         if (count == -1)
2356           error = errno;
2357         else
2358           error = 0;
2359         (is->func)(is, is->closure, is->buf, count, error);
2360     }
2361 }
2362
2363 InputSourceRef
2364 AddInputSource (ProcRef pr, int lineByLine, InputCallback func, VOIDSTAR closure)
2365 {
2366     InputSource *is;
2367     ChildProc *cp = (ChildProc *) pr;
2368
2369     is = (InputSource *) calloc(1, sizeof(InputSource));
2370     is->lineByLine = lineByLine;
2371     is->func = func;
2372     if (pr == NoProc) {
2373         is->kind = CPReal;
2374         is->fd = fileno(stdin);
2375     } else {
2376         is->kind = cp->kind;
2377         is->fd = cp->fdFrom;
2378     }
2379     if (lineByLine) {
2380         is->unused = is->buf;
2381     }
2382
2383     is->xid = XtAppAddInput(appContext, is->fd,
2384                             (XtPointer) (XtInputReadMask),
2385                             (XtInputCallbackProc) DoInputCallback,
2386                             (XtPointer) is);
2387     is->closure = closure;
2388     return (InputSourceRef) is;
2389 }
2390
2391 void
2392 RemoveInputSource (InputSourceRef isr)
2393 {
2394     InputSource *is = (InputSource *) isr;
2395
2396     if (is->xid == 0) return;
2397     XtRemoveInput(is->xid);
2398     is->xid = 0;
2399 }
2400
2401 #ifndef HAVE_USLEEP
2402
2403 static Boolean frameWaiting;
2404
2405 static RETSIGTYPE
2406 FrameAlarm (int sig)
2407 {
2408   frameWaiting = False;
2409   /* In case System-V style signals.  Needed?? */
2410   signal(SIGALRM, FrameAlarm);
2411 }
2412
2413 void
2414 FrameDelay (int time)
2415 {
2416   struct itimerval delay;
2417
2418   XSync(xDisplay, False);
2419
2420   if (time > 0) {
2421     frameWaiting = True;
2422     signal(SIGALRM, FrameAlarm);
2423     delay.it_interval.tv_sec =
2424       delay.it_value.tv_sec = time / 1000;
2425     delay.it_interval.tv_usec =
2426       delay.it_value.tv_usec = (time % 1000) * 1000;
2427     setitimer(ITIMER_REAL, &delay, NULL);
2428     while (frameWaiting) pause();
2429     delay.it_interval.tv_sec = delay.it_value.tv_sec = 0;
2430     delay.it_interval.tv_usec = delay.it_value.tv_usec = 0;
2431     setitimer(ITIMER_REAL, &delay, NULL);
2432   }
2433 }
2434
2435 #else
2436
2437 void
2438 FrameDelay (int time)
2439 {
2440   XSync(xDisplay, False);
2441   if (time > 0)
2442     usleep(time * 1000);
2443 }
2444
2445 #endif
2446
2447 static void
2448 LoadLogo (ChessProgramState *cps, int n, Boolean ics)
2449 {
2450     char buf[MSG_SIZ], *logoName = buf;
2451     if(appData.logo[n][0]) {
2452         logoName = appData.logo[n];
2453     } else if(appData.autoLogo) {
2454         if(ics) { // [HGM] logo: in ICS mode second can be used for ICS
2455             sprintf(buf, "%s/%s.png", appData.logoDir, appData.icsHost);
2456         } else if(appData.directory[n] && appData.directory[n][0]) {
2457             sprintf(buf, "%s/%s.png", appData.logoDir, cps->tidy);
2458         }
2459     }
2460     if(logoName[0])
2461         { ASSIGN(cps->programLogo, logoName); }
2462 }
2463
2464 void
2465 UpdateLogos (int displ)
2466 {
2467     if(optList[W_WHITE-1].handle == NULL) return;
2468     LoadLogo(&first, 0, 0);
2469     LoadLogo(&second, 1, appData.icsActive);
2470     if(displ) DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
2471     return;
2472 }
2473