Expand %s in -openCommand to DATADIR and fix OSX settings-file name
[xboard.git] / gtk / 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, 2014 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 #include <gtk/gtk.h>
67
68 #if !OMIT_SOCKETS
69 # if HAVE_SYS_SOCKET_H
70 #  include <sys/socket.h>
71 #  include <netinet/in.h>
72 #  include <netdb.h>
73 # else /* not HAVE_SYS_SOCKET_H */
74 #  if HAVE_LAN_SOCKET_H
75 #   include <lan/socket.h>
76 #   include <lan/in.h>
77 #   include <lan/netdb.h>
78 #  else /* not HAVE_LAN_SOCKET_H */
79 #   define OMIT_SOCKETS 1
80 #  endif /* not HAVE_LAN_SOCKET_H */
81 # endif /* not HAVE_SYS_SOCKET_H */
82 #endif /* !OMIT_SOCKETS */
83
84 #if STDC_HEADERS
85 # include <stdlib.h>
86 # include <string.h>
87 #else /* not STDC_HEADERS */
88 extern char *getenv();
89 # if HAVE_STRING_H
90 #  include <string.h>
91 # else /* not HAVE_STRING_H */
92 #  include <strings.h>
93 # endif /* not HAVE_STRING_H */
94 #endif /* not STDC_HEADERS */
95
96 #if HAVE_SYS_FCNTL_H
97 # include <sys/fcntl.h>
98 #else /* not HAVE_SYS_FCNTL_H */
99 # if HAVE_FCNTL_H
100 #  include <fcntl.h>
101 # endif /* HAVE_FCNTL_H */
102 #endif /* not HAVE_SYS_FCNTL_H */
103
104 #if HAVE_SYS_SYSTEMINFO_H
105 # include <sys/systeminfo.h>
106 #endif /* HAVE_SYS_SYSTEMINFO_H */
107
108 #if TIME_WITH_SYS_TIME
109 # include <sys/time.h>
110 # include <time.h>
111 #else
112 # if HAVE_SYS_TIME_H
113 #  include <sys/time.h>
114 # else
115 #  include <time.h>
116 # endif
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #if HAVE_SYS_WAIT_H
124 # include <sys/wait.h>
125 #endif
126
127 #if HAVE_DIRENT_H
128 # include <dirent.h>
129 # define NAMLEN(dirent) strlen((dirent)->d_name)
130 # define HAVE_DIR_STRUCT
131 #else
132 # define dirent direct
133 # define NAMLEN(dirent) (dirent)->d_namlen
134 # if HAVE_SYS_NDIR_H
135 #  include <sys/ndir.h>
136 #  define HAVE_DIR_STRUCT
137 # endif
138 # if HAVE_SYS_DIR_H
139 #  include <sys/dir.h>
140 #  define HAVE_DIR_STRUCT
141 # endif
142 # if HAVE_NDIR_H
143 #  include <ndir.h>
144 #  define HAVE_DIR_STRUCT
145 # endif
146 #endif
147
148 #if ENABLE_NLS
149 #include <locale.h>
150 #endif
151
152 // [HGM] bitmaps: put before incuding the bitmaps / pixmaps, to know how many piece types there are.
153 #include "common.h"
154
155 #include "frontend.h"
156 #include "backend.h"
157 #include "backendz.h"
158 #include "moves.h"
159 #include "xboard.h"
160 #include "xboard2.h"
161 #include "childio.h"
162 #include "menus.h"
163 #include "board.h"
164 #include "dialogs.h"
165 #include "engineoutput.h"
166 #include "usystem.h"
167 #include "gettext.h"
168 #include "draw.h"
169
170 #ifdef __APPLE__
171 #  include <gtkmacintegration/gtkosxapplication.h>
172    // prevent pathname of positional file argument provided by OS X being be mistaken for option name
173    // (price is that we won't recognize Windows option format anymore).
174 #  define SLASH '-'
175    // redefine some defaults
176 #  undef ICS_LOGON
177 #  undef DATADIR
178 #  undef SETTINGS_FILE
179 #  define ICS_LOGON "Library/Preferences/XboardICS.conf"
180 #  define DATADIR dataDir
181 #  define SETTINGS_FILE masterSettings
182    char dataDir[MSG_SIZ]; // for expanding ~~
183    char masterSettings[MSG_SIZ];
184 #else
185 #  define SLASH '/'
186 #endif
187
188 #ifdef __EMX__
189 #ifndef HAVE_USLEEP
190 #define HAVE_USLEEP
191 #endif
192 #define usleep(t)   _sleep2(((t)+500)/1000)
193 #endif
194
195 #ifdef ENABLE_NLS
196 # define  _(s) gettext (s)
197 # define N_(s) gettext_noop (s)
198 #else
199 # define  _(s) (s)
200 # define N_(s)  s
201 #endif
202
203 int main P((int argc, char **argv));
204 RETSIGTYPE CmailSigHandler P((int sig));
205 RETSIGTYPE IntSigHandler P((int sig));
206 RETSIGTYPE TermSizeSigHandler P((int sig));
207 #if ENABLE_NLS
208 char *InsertPxlSize P((char *pattern, int targetPxlSize));
209 XFontSet CreateFontSet P((char *base_fnt_lst));
210 #else
211 char *FindFont P((char *pattern, int targetPxlSize));
212 #endif
213 void DelayedDrag P((void));
214 void ICSInputBoxPopUp P((void));
215 void MoveTypeInProc P((GdkEventKey *eventkey));
216 gboolean KeyPressProc P((GtkWindow *window, GdkEventKey *eventkey, gpointer data));
217 Boolean TempBackwardActive = False;
218 void DisplayMove P((int moveNumber));
219 void update_ics_width P(());
220 int CopyMemoProc P(());
221 static gboolean EventProc P((GtkWidget *widget, GdkEvent *event, gpointer g));
222
223 #ifdef TODO_GTK
224 #if ENABLE_NLS
225 XFontSet fontSet, clockFontSet;
226 #else
227 Font clockFontID;
228 XFontStruct *clockFontStruct;
229 #endif
230 Font coordFontID, countFontID;
231 XFontStruct *coordFontStruct, *countFontStruct;
232 #else
233 void *shellWidget, *formWidget, *boardWidget, *titleWidget, *dropMenu, *menuBarWidget;
234 GtkWidget       *mainwindow;
235 #endif
236 Option *optList; // contains all widgets of main window
237 char *layoutName;
238
239 char installDir[] = "."; // [HGM] UCI: needed for UCI; probably needs run-time initializtion
240
241 /* pixbufs */
242 static GdkPixbuf       *mainwindowIcon=NULL;
243 static GdkPixbuf       *WhiteIcon=NULL;
244 static GdkPixbuf       *BlackIcon=NULL;
245
246 /* key board accelerators */
247 GtkAccelGroup *GtkAccelerators;
248
249 typedef unsigned int BoardSize;
250 BoardSize boardSize;
251 Boolean chessProgram;
252
253 int  minX, minY; // [HGM] placement: volatile limits on upper-left corner
254 int smallLayout = 0, tinyLayout = 0,
255   marginW, marginH, // [HGM] for run-time resizing
256   fromX = -1, fromY = -1, toX, toY, commentUp = False,
257   errorExitStatus = -1, defaultLineGap;
258 #ifdef TODO_GTK
259 Dimension textHeight;
260 #endif
261 char *chessDir, *programName, *programVersion;
262 Boolean alwaysOnTop = False;
263 char *icsTextMenuString;
264 char *icsNames;
265 char *firstChessProgramNames;
266 char *secondChessProgramNames;
267
268 WindowPlacement wpMain;
269 WindowPlacement wpConsole;
270 WindowPlacement wpComment;
271 WindowPlacement wpMoveHistory;
272 WindowPlacement wpEvalGraph;
273 WindowPlacement wpEngineOutput;
274 WindowPlacement wpGameList;
275 WindowPlacement wpTags;
276 WindowPlacement wpDualBoard;
277
278 /* This magic number is the number of intermediate frames used
279    in each half of the animation. For short moves it's reduced
280    by 1. The total number of frames will be factor * 2 + 1.  */
281 #define kFactor    4
282
283 SizeDefaults sizeDefaults[] = SIZE_DEFAULTS;
284
285 typedef struct {
286     char piece;
287     char* widget;
288 } DropMenuEnables;
289
290 DropMenuEnables dmEnables[] = {
291     { 'P', "Pawn" },
292     { 'N', "Knight" },
293     { 'B', "Bishop" },
294     { 'R', "Rook" },
295     { 'Q', "Queen" }
296 };
297
298 #ifdef TODO_GTK
299 XtResource clientResources[] = {
300     { "flashCount", "flashCount", XtRInt, sizeof(int),
301         XtOffset(AppDataPtr, flashCount), XtRImmediate,
302         (XtPointer) FLASH_COUNT  },
303 };
304 #endif
305
306 /* keyboard shortcuts not yet transistioned int menuitem @ menu.c */
307 char globalTranslations[] =
308   ":Ctrl<Key>Down: LoadSelectedProc(3) \n \
309    :Ctrl<Key>Up: LoadSelectedProc(-3) \n \
310    :<KeyDown>Return: TempBackwardProc() \n \
311    :<KeyUp>Return: TempForwardProc() \n";
312
313 char ICSInputTranslations[] =
314     "<Key>Up: UpKeyProc() \n "
315     "<Key>Down: DownKeyProc() \n "
316     "<Key>Return: EnterKeyProc() \n";
317
318 // [HGM] vari: another hideous kludge: call extend-end first so we can be sure select-start works,
319 //             as the widget is destroyed before the up-click can call extend-end
320 char commentTranslations[] = "<Btn3Down>: extend-end() select-start() CommentClick() \n";
321
322 #ifdef TODO_GTK
323 String xboardResources[] = {
324     "*Error*translations: #override\\n <Key>Return: ErrorPopDown()",
325     NULL
326   };
327 #endif
328
329 void
330 BoardToTop ()
331 {
332   gtk_window_present(GTK_WINDOW(shells[BoardWindow]));
333 }
334
335 //---------------------------------------------------------------------------------------------------------
336 // some symbol definitions to provide the proper (= XBoard) context for the code in args.h
337 #define XBOARD True
338 #define JAWS_ARGS
339 #define CW_USEDEFAULT (1<<31)
340 #define ICS_TEXT_MENU_SIZE 90
341 #define DEBUG_FILE "xboard.debug"
342 #define SetCurrentDirectory chdir
343 #define GetCurrentDirectory(SIZE, NAME) getcwd(NAME, SIZE)
344 #define OPTCHAR "-"
345 #define SEPCHAR " "
346
347 // The option definition and parsing code common to XBoard and WinBoard is collected in this file
348 #include "args.h"
349
350 // front-end part of option handling
351
352 // [HGM] This platform-dependent table provides the location for storing the color info
353 extern char *crWhite, * crBlack;
354
355 void *
356 colorVariable[] = {
357   &appData.whitePieceColor,
358   &appData.blackPieceColor,
359   &appData.lightSquareColor,
360   &appData.darkSquareColor,
361   &appData.highlightSquareColor,
362   &appData.premoveHighlightColor,
363   &appData.lowTimeWarningColor,
364   NULL,
365   NULL,
366   NULL,
367   NULL,
368   NULL,
369   &crWhite,
370   &crBlack,
371   NULL
372 };
373
374 // [HGM] font: keep a font for each square size, even non-stndard ones
375 #define NUM_SIZES 18
376 #define MAX_SIZE 130
377 Boolean fontIsSet[NUM_FONTS], fontValid[NUM_FONTS][MAX_SIZE];
378 char *fontTable[NUM_FONTS][MAX_SIZE];
379
380 void
381 ParseFont (char *name, int number)
382 { // in XBoard, only 2 of the fonts are currently implemented, and we just copy their name
383   int size;
384   if(sscanf(name, "size%d:", &size)) {
385     // [HGM] font: font is meant for specific boardSize (likely from settings file);
386     //       defer processing it until we know if it matches our board size
387     if(size >= 0 && size<MAX_SIZE) { // for now, fixed limit
388         fontTable[number][size] = strdup(strchr(name, ':')+1);
389         fontValid[number][size] = True;
390     }
391     return;
392   }
393   switch(number) {
394     case 0: // CLOCK_FONT
395         appData.clockFont = strdup(name);
396       break;
397     case 1: // MESSAGE_FONT
398         appData.font = strdup(name);
399       break;
400     case 2: // COORD_FONT
401         appData.coordFont = strdup(name);
402       break;
403     default:
404       return;
405   }
406   fontIsSet[number] = True; // [HGM] font: indicate a font was specified (not from settings file)
407 }
408
409 void
410 SetFontDefaults ()
411 { // only 2 fonts currently
412   appData.clockFont = CLOCK_FONT_NAME;
413   appData.coordFont = COORD_FONT_NAME;
414   appData.font  =   DEFAULT_FONT_NAME;
415 }
416
417 void
418 CreateFonts ()
419 { // no-op, until we identify the code for this already in XBoard and move it here
420 }
421
422 void
423 ParseColor (int n, char *name)
424 { // in XBoard, just copy the color-name string
425   if(colorVariable[n]) *(char**)colorVariable[n] = strdup(name);
426 }
427
428 void
429 ParseTextAttribs (ColorClass cc, char *s)
430 {
431     (&appData.colorShout)[cc] = strdup(s);
432 }
433
434 void
435 ParseBoardSize (void *addr, char *name)
436 {
437     appData.boardSize = strdup(name);
438 }
439
440 void
441 LoadAllSounds ()
442 { // In XBoard the sound-playing program takes care of obtaining the actual sound
443 }
444
445 void
446 SetCommPortDefaults ()
447 { // for now, this is a no-op, as the corresponding option does not exist in XBoard
448 }
449
450 // [HGM] args: these three cases taken out to stay in front-end
451 void
452 SaveFontArg (FILE *f, ArgDescriptor *ad)
453 {
454   char *name;
455   int i, n = (int)(intptr_t)ad->argLoc;
456   switch(n) {
457     case 0: // CLOCK_FONT
458         name = appData.clockFont;
459       break;
460     case 1: // MESSAGE_FONT
461         name = appData.font;
462       break;
463     case 2: // COORD_FONT
464         name = appData.coordFont;
465       break;
466     default:
467       return;
468   }
469   for(i=0; i<NUM_SIZES; i++) // [HGM] font: current font becomes standard for current size
470     if(sizeDefaults[i].squareSize == squareSize) { // only for standard sizes!
471         fontTable[n][squareSize] = strdup(name);
472         fontValid[n][squareSize] = True;
473         break;
474   }
475   for(i=0; i<MAX_SIZE; i++) if(fontValid[n][i]) // [HGM] font: store all standard fonts
476     fprintf(f, OPTCHAR "%s" SEPCHAR "\"size%d:%s\"\n", ad->argName, i, fontTable[n][i]);
477 }
478
479 void
480 ExportSounds ()
481 { // nothing to do, as the sounds are at all times represented by their text-string names already
482 }
483
484 void
485 SaveAttribsArg (FILE *f, ArgDescriptor *ad)
486 {       // here the "argLoc" defines a table index. It could have contained the 'ta' pointer itself, though
487         fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, (&appData.colorShout)[(int)(intptr_t)ad->argLoc]);
488 }
489
490 void
491 SaveColor (FILE *f, ArgDescriptor *ad)
492 {       // in WinBoard the color is an int and has to be converted to text. In X it would be a string already?
493         if(colorVariable[(int)(intptr_t)ad->argLoc])
494         fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, *(char**)colorVariable[(int)(intptr_t)ad->argLoc]);
495 }
496
497 void
498 SaveBoardSize (FILE *f, char *name, void *addr)
499 { // wrapper to shield back-end from BoardSize & sizeInfo
500   fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", name, appData.boardSize);
501 }
502
503 void
504 ParseCommPortSettings (char *s)
505 { // no such option in XBoard (yet)
506 }
507
508 int frameX, frameY;
509
510 void
511 GetActualPlacement (GtkWidget *shell, WindowPlacement *wp)
512 {
513   GtkAllocation a;
514   if(!shell) return;
515   gtk_widget_get_allocation(shell, &a);
516   gtk_window_get_position(GTK_WINDOW(shell), &a.x, &a.y);
517   wp->x = a.x;
518   wp->y = a.y;
519   wp->width = a.width;
520   wp->height = a.height;
521 //printf("placement: (%d,%d) %dx%d\n", a.x, a.y, a.width, a.height);
522   frameX = 3; frameY = 3; // remember to decide if windows touch
523 }
524
525 void
526 GetWindowCoords ()
527 { // wrapper to shield use of window handles from back-end (make addressible by number?)
528   // In XBoard this will have to wait until awareness of window parameters is implemented
529   GetActualPlacement(shellWidget, &wpMain);
530   if(shellUp[EngOutDlg]) GetActualPlacement(shells[EngOutDlg], &wpEngineOutput);
531   if(shellUp[HistoryDlg]) GetActualPlacement(shells[HistoryDlg], &wpMoveHistory);
532   if(shellUp[EvalGraphDlg]) GetActualPlacement(shells[EvalGraphDlg], &wpEvalGraph);
533   if(shellUp[GameListDlg]) GetActualPlacement(shells[GameListDlg], &wpGameList);
534   if(shellUp[CommentDlg]) GetActualPlacement(shells[CommentDlg], &wpComment);
535   if(shellUp[TagsDlg]) GetActualPlacement(shells[TagsDlg], &wpTags);
536 }
537
538 void
539 PrintCommPortSettings (FILE *f, char *name)
540 { // This option does not exist in XBoard
541 }
542
543 void
544 EnsureOnScreen (int *x, int *y, int minX, int minY)
545 {
546   return;
547 }
548
549 int
550 MainWindowUp ()
551 { // [HGM] args: allows testing if main window is realized from back-end
552   return DialogExists(BoardWindow);
553 }
554
555 void
556 PopUpStartupDialog ()
557 {  // start menu not implemented in XBoard
558 }
559
560 char *
561 ConvertToLine (int argc, char **argv)
562 {
563   static char line[128*1024], buf[1024];
564   int i;
565
566   line[0] = NULLCHAR;
567   for(i=1; i<argc; i++)
568     {
569       if( (strchr(argv[i], ' ') || strchr(argv[i], '\n') ||strchr(argv[i], '\t') || argv[i][0] == NULLCHAR)
570           && argv[i][0] != '{' )
571         snprintf(buf, sizeof(buf)/sizeof(buf[0]), "{%s} ", argv[i]);
572       else
573         snprintf(buf, sizeof(buf)/sizeof(buf[0]), "%s ", argv[i]);
574       strncat(line, buf, 128*1024 - strlen(line) - 1 );
575     }
576
577   line[strlen(line)-1] = NULLCHAR;
578   return line;
579 }
580
581 //--------------------------------------------------------------------------------------------
582
583 int clockKludge;
584
585 void
586 ResizeBoardWindow (int w, int h, int inhibit)
587 {
588     GtkAllocation a;
589     if(clockKludge) return; // ignore as long as clock does not have final height
590     gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
591     w += marginW + 1; // [HGM] not sure why the +1 is (sometimes) needed...
592     h += marginH + a.height + 1;
593     gtk_window_resize(GTK_WINDOW(shellWidget), w, h);
594 }
595
596 int
597 MakeColors ()
598 {   // dummy, as the GTK code does not make colors in advance
599     return FALSE;
600 }
601
602 void
603 InitializeFonts (int clockFontPxlSize, int coordFontPxlSize, int fontPxlSize)
604 {   // determine what fonts to use, and create them
605 #ifdef TODO_GTK
606     XrmValue vTo;
607     XrmDatabase xdb;
608
609     if(!fontIsSet[CLOCK_FONT] && fontValid[CLOCK_FONT][squareSize])
610         appData.clockFont = fontTable[CLOCK_FONT][squareSize];
611     if(!fontIsSet[MESSAGE_FONT] && fontValid[MESSAGE_FONT][squareSize])
612         appData.font = fontTable[MESSAGE_FONT][squareSize];
613     if(!fontIsSet[COORD_FONT] && fontValid[COORD_FONT][squareSize])
614         appData.coordFont = fontTable[COORD_FONT][squareSize];
615
616 #if ENABLE_NLS
617     appData.font = InsertPxlSize(appData.font, fontPxlSize);
618     appData.clockFont = InsertPxlSize(appData.clockFont, clockFontPxlSize);
619     appData.coordFont = InsertPxlSize(appData.coordFont, coordFontPxlSize);
620     fontSet = CreateFontSet(appData.font);
621     clockFontSet = CreateFontSet(appData.clockFont);
622     {
623       /* For the coordFont, use the 0th font of the fontset. */
624       XFontSet coordFontSet = CreateFontSet(appData.coordFont);
625       XFontStruct **font_struct_list;
626       XFontSetExtents *fontSize;
627       char **font_name_list;
628       XFontsOfFontSet(coordFontSet, &font_struct_list, &font_name_list);
629       coordFontID = XLoadFont(xDisplay, font_name_list[0]);
630       coordFontStruct = XQueryFont(xDisplay, coordFontID);
631       fontSize = XExtentsOfFontSet(fontSet); // [HGM] figure out how much vertical space font takes
632       textHeight = fontSize->max_logical_extent.height + 5; // add borderWidth
633     }
634 #else
635     appData.font = FindFont(appData.font, fontPxlSize);
636     appData.clockFont = FindFont(appData.clockFont, clockFontPxlSize);
637     appData.coordFont = FindFont(appData.coordFont, coordFontPxlSize);
638     clockFontID = XLoadFont(xDisplay, appData.clockFont);
639     clockFontStruct = XQueryFont(xDisplay, clockFontID);
640     coordFontID = XLoadFont(xDisplay, appData.coordFont);
641     coordFontStruct = XQueryFont(xDisplay, coordFontID);
642     // textHeight in !NLS mode!
643 #endif
644     countFontID = coordFontID;  // [HGM] holdings
645     countFontStruct = coordFontStruct;
646
647     xdb = XtDatabase(xDisplay);
648 #if ENABLE_NLS
649     XrmPutLineResource(&xdb, "*international: True");
650     vTo.size = sizeof(XFontSet);
651     vTo.addr = (XtPointer) &fontSet;
652     XrmPutResource(&xdb, "*fontSet", XtRFontSet, &vTo);
653 #else
654     XrmPutStringResource(&xdb, "*font", appData.font);
655 #endif
656 #endif
657 }
658
659 char *
660 PrintArg (ArgType t)
661 {
662   char *p="";
663   switch(t) {
664     case ArgZ:
665     case ArgInt:      p = " N"; break;
666     case ArgString:   p = " STR"; break;
667     case ArgBoolean:  p = " TF"; break;
668     case ArgSettingsFilename:
669     case ArgBackupSettingsFile:
670     case ArgFilename: p = " FILE"; break;
671     case ArgX:        p = " Nx"; break;
672     case ArgY:        p = " Ny"; break;
673     case ArgAttribs:  p = " TEXTCOL"; break;
674     case ArgColor:    p = " COL"; break;
675     case ArgFont:     p = " FONT"; break;
676     case ArgBoardSize: p = " SIZE"; break;
677     case ArgFloat: p = " FLOAT"; break;
678     case ArgTrue:
679     case ArgFalse:
680     case ArgTwo:
681     case ArgNone:
682     case ArgCommSettings:
683     default:
684       break;
685   }
686   return p;
687 }
688
689 void
690 PrintOptions ()
691 {
692   char buf[MSG_SIZ];
693   int len=0;
694   ArgDescriptor *q, *p = argDescriptors+5;
695   printf("\nXBoard accepts the following options:\n"
696          "(N = integer, TF = true or false, STR = text string, FILE = filename,\n"
697          " Nx, Ny = relative coordinates, COL = color, FONT = X-font spec,\n"
698          " SIZE = board-size spec(s)\n"
699          " Within parentheses are short forms, or options to set to true or false.\n"
700          " Persistent options (saved in the settings file) are marked with *)\n\n");
701   while(p->argName) {
702     if(p->argType == ArgCommSettings) { p++; continue; } // XBoard has no comm port
703     snprintf(buf+len, MSG_SIZ, "-%s%s", p->argName, PrintArg(p->argType));
704     if(p->save) strcat(buf+len, "*");
705     for(q=p+1; q->argLoc == p->argLoc; q++) {
706       if(q->argName[0] == '-') continue;
707       strcat(buf+len, q == p+1 ? " (" : " ");
708       sprintf(buf+strlen(buf), "-%s%s", q->argName, PrintArg(q->argType));
709     }
710     if(q != p+1) strcat(buf+len, ")");
711     len = strlen(buf);
712     if(len > 39) len = 0, printf("%s\n", buf); else while(len < 39) buf[len++] = ' ';
713     p = q;
714   }
715   if(len) buf[len] = NULLCHAR, printf("%s\n", buf);
716 }
717
718 void
719 SlaveResize (Option *opt)
720 {
721     static int slaveW, slaveH, w, h;
722     GtkAllocation a;
723     if(!slaveH) {
724         gtk_widget_get_allocation(shells[DummyDlg], &a);
725         w = a.width; h = a.height;
726         gtk_widget_get_allocation(opt->handle, &a);
727         slaveW =  w - opt->max; // [HGM] needed to set new shellWidget size when we resize board
728         slaveH =  h - a.height + 13;
729    }
730   gtk_window_resize(GTK_WINDOW(shells[DummyDlg]), slaveW + opt->max, slaveH + opt->value);
731 }
732
733 #ifdef __APPLE__
734 static char clickedFile[MSG_SIZ];
735 static int suppress;
736
737 static gboolean
738 StartNewXBoard(GtkosxApplication *app, gchar *path, gpointer user_data)
739 { // handler of OSX OpenFile signal, which sends us the filename of clicked file or first argument
740   if(suppress) { // we just started XBoard without arguments
741     strncpy(clickedFile, path, MSG_SIZ); // remember file name, but otherwise ignore
742   } else {       // we are running something presumably useful
743     char buf[MSG_SIZ];
744     snprintf(buf, MSG_SIZ, "open -n -a \"xboard\" --args \"%s\"", path);
745     system(buf); // start new instance on this file
746   }
747   return TRUE;
748 }
749 #endif
750
751 int
752 main (int argc, char **argv)
753 {
754     int i, clockFontPxlSize, coordFontPxlSize, fontPxlSize;
755     int boardWidth, boardHeight, w, h;
756     char *p;
757     int forceMono = False;
758
759     srandom(time(0)); // [HGM] book: make random truly random
760
761     setbuf(stdout, NULL);
762     setbuf(stderr, NULL);
763     debugFP = stderr;
764
765     if(argc > 1 && (!strcmp(argv[1], "-v" ) || !strcmp(argv[1], "--version" ))) {
766         printf("%s version %s\n\n  configure options: %s\n", PACKAGE_NAME, PACKAGE_VERSION, CONFIGURE_OPTIONS);
767         exit(0);
768     }
769
770     if(argc > 1 && !strcmp(argv[1], "--help" )) {
771         PrintOptions();
772         exit(0);
773     }
774
775     /* set up GTK */
776     gtk_init (&argc, &argv);
777 #ifdef __APPLE__
778     {   // prepare to catch OX OpenFile signal, which will tell us the clicked file
779         GtkosxApplication *theApp = g_object_new(GTKOSX_TYPE_APPLICATION, NULL);
780         char *path = gtkosx_application_get_bundle_path();
781         strncpy(dataDir, path, MSG_SIZ);
782         snprintf(masterSettings, MSG_SIZ, "%s/Contents/Resources/etc/xboard.conf", path);
783         g_signal_connect(theApp, "NSApplicationOpenFile", G_CALLBACK(StartNewXBoard), NULL);
784         // we must call application ready before we can get the signal,
785         // and supply a (dummy) menu bar before that, to avoid problems with dual apples in it
786         gtkosx_application_set_menu_bar(theApp, GTK_MENU_SHELL(gtk_menu_bar_new()));
787         gtkosx_application_ready(theApp);
788         suppress = (argc == 1 || argc > 1 && argv[1][00] != '-'); // OSX sends signal even if name was already argv[1]!
789         if(argc == 1) {                  // called without args: OSX open-file signal might follow
790             static char *fakeArgv[3] = {NULL, clickedFile, NULL};
791             usleep(10000);               // wait 10 msec (and hope this is long enough).
792             while(gtk_events_pending())
793                 gtk_main_iteration();    // process all events that came in upto now
794             suppress = 0;                // future open-file signals should start new instance
795             if(clickedFile[0]) {         // we were sent an open-file signal with filename!
796               fakeArgv[0] = argv[0];
797               argc = 2; argv = fakeArgv; // fake that we were called as "xboard filename"
798             }
799         }
800     }
801 #endif
802
803     if(argc > 1 && !strcmp(argv[1], "--show-config")) { // [HGM] install: called to print config info
804         typedef struct {char *name, *value; } Config;
805         static Config configList[] = {
806           { "Datadir", DATADIR },
807           { "Sysconfdir", SYSCONFDIR },
808           { NULL }
809         };
810         int i;
811
812         for(i=0; configList[i].name; i++) {
813             if(argc > 2 && strcmp(argv[2], configList[i].name)) continue;
814             if(argc > 2) printf("%s", configList[i].value);
815             else printf("%-12s: %s\n", configList[i].name, configList[i].value);
816         }
817         exit(0);
818     }
819
820     /* set up keyboard accelerators group */
821     GtkAccelerators = gtk_accel_group_new();
822
823     programName = strrchr(argv[0], '/');
824     if (programName == NULL)
825       programName = argv[0];
826     else
827       programName++;
828
829 #ifdef ENABLE_NLS
830 //    if (appData.debugMode) {
831 //      fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
832 //    }
833
834     bindtextdomain(PACKAGE, LOCALEDIR);
835     bind_textdomain_codeset(PACKAGE, "UTF-8"); // needed when creating markup for the clocks
836     textdomain(PACKAGE);
837 #endif
838
839     appData.boardSize = "";
840     InitAppData(ConvertToLine(argc, argv));
841     p = getenv("HOME");
842     if (p == NULL) p = "/tmp";
843     i = strlen(p) + strlen("/.xboardXXXXXx.pgn") + 1;
844     gameCopyFilename = (char*) malloc(i);
845     gamePasteFilename = (char*) malloc(i);
846     snprintf(gameCopyFilename,i, "%s/.xboard%05uc.pgn", p, getpid());
847     snprintf(gamePasteFilename,i, "%s/.xboard%05up.pgn", p, getpid());
848
849     { // [HGM] initstring: kludge to fix bad bug. expand '\n' characters in init string and computer string.
850         static char buf[MSG_SIZ];
851         snprintf(buf, MSG_SIZ, appData.sysOpen, DATADIR);
852         ASSIGN(appData.sysOpen, buf); // expand %s in -openCommand to DATADIR (usefull for OS X configuring)
853         EscapeExpand(buf, appData.firstInitString);
854         appData.firstInitString = strdup(buf);
855         EscapeExpand(buf, appData.secondInitString);
856         appData.secondInitString = strdup(buf);
857         EscapeExpand(buf, appData.firstComputerString);
858         appData.firstComputerString = strdup(buf);
859         EscapeExpand(buf, appData.secondComputerString);
860         appData.secondComputerString = strdup(buf);
861     }
862
863     if ((chessDir = (char *) getenv("CHESSDIR")) == NULL) {
864         chessDir = ".";
865     } else {
866         if (chdir(chessDir) != 0) {
867             fprintf(stderr, _("%s: can't cd to CHESSDIR: "), programName);
868             perror(chessDir);
869             exit(1);
870         }
871     }
872
873     if (appData.debugMode && appData.nameOfDebugFile && strcmp(appData.nameOfDebugFile, "stderr")) {
874         /* [DM] debug info to file [HGM] make the filename a command-line option, and allow it to remain stderr */
875         if ((debugFP = fopen(appData.nameOfDebugFile, "w")) == NULL)  {
876            printf(_("Failed to open file '%s'\n"), appData.nameOfDebugFile);
877            exit(errno);
878         }
879         setbuf(debugFP, NULL);
880     }
881
882 #if ENABLE_NLS
883     if (appData.debugMode) {
884       fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
885     }
886 #endif
887
888     /* [HGM,HR] make sure board size is acceptable */
889     if(appData.NrFiles > BOARD_FILES ||
890        appData.NrRanks > BOARD_RANKS   )
891          DisplayFatalError(_("Recompile with larger BOARD_RANKS or BOARD_FILES to support this size"), 0, 2);
892
893 #if !HIGHDRAG
894     /* This feature does not work; animation needs a rewrite */
895     appData.highlightDragging = FALSE;
896 #endif
897     InitBackEnd1();
898
899         gameInfo.variant = StringToVariant(appData.variant);
900         InitPosition(FALSE);
901
902     /*
903      * determine size, based on supplied or remembered -size, or screen size
904      */
905     if (isdigit(appData.boardSize[0])) {
906         i = sscanf(appData.boardSize, "%d,%d,%d,%d,%d,%d,%d", &squareSize,
907                    &lineGap, &clockFontPxlSize, &coordFontPxlSize,
908                    &fontPxlSize, &smallLayout, &tinyLayout);
909         if (i == 0) {
910             fprintf(stderr, _("%s: bad boardSize syntax %s\n"),
911                     programName, appData.boardSize);
912             exit(2);
913         }
914         if (i < 7) {
915             /* Find some defaults; use the nearest known size */
916             SizeDefaults *szd, *nearest;
917             int distance = 99999;
918             nearest = szd = sizeDefaults;
919             while (szd->name != NULL) {
920                 if (abs(szd->squareSize - squareSize) < distance) {
921                     nearest = szd;
922                     distance = abs(szd->squareSize - squareSize);
923                     if (distance == 0) break;
924                 }
925                 szd++;
926             }
927             if (i < 2) lineGap = nearest->lineGap;
928             if (i < 3) clockFontPxlSize = nearest->clockFontPxlSize;
929             if (i < 4) coordFontPxlSize = nearest->coordFontPxlSize;
930             if (i < 5) fontPxlSize = nearest->fontPxlSize;
931             if (i < 6) smallLayout = nearest->smallLayout;
932             if (i < 7) tinyLayout = nearest->tinyLayout;
933         }
934     } else {
935         SizeDefaults *szd = sizeDefaults;
936         if (*appData.boardSize == NULLCHAR) {
937             GdkScreen *screen = gtk_window_get_screen(GTK_WINDOW(mainwindow));
938             guint screenwidth = gdk_screen_get_width(screen);
939             guint screenheight = gdk_screen_get_height(screen);
940             while (screenwidth < szd->minScreenSize ||
941                    screenheight < szd->minScreenSize) {
942               szd++;
943             }
944             if (szd->name == NULL) szd--;
945             appData.boardSize = strdup(szd->name); // [HGM] settings: remember name for saving settings
946         } else {
947             while (szd->name != NULL &&
948                    StrCaseCmp(szd->name, appData.boardSize) != 0) szd++;
949             if (szd->name == NULL) {
950                 fprintf(stderr, _("%s: unrecognized boardSize name %s\n"),
951                         programName, appData.boardSize);
952                 exit(2);
953             }
954         }
955         squareSize = szd->squareSize;
956         lineGap = szd->lineGap;
957         clockFontPxlSize = szd->clockFontPxlSize;
958         coordFontPxlSize = szd->coordFontPxlSize;
959         fontPxlSize = szd->fontPxlSize;
960         smallLayout = szd->smallLayout;
961         tinyLayout = szd->tinyLayout;
962         // [HGM] font: use defaults from settings file if available and not overruled
963     }
964
965     defaultLineGap = lineGap;
966     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
967
968     /* [HR] height treated separately (hacked) */
969     boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
970     boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
971
972     /*
973      * Determine what fonts to use.
974      */
975 #ifdef TODO_GTK
976     InitializeFonts(clockFontPxlSize, coordFontPxlSize, fontPxlSize);
977 #endif
978
979     /*
980      * Detect if there are not enough colors available and adapt.
981      */
982 #ifdef TODO_GTK
983     if (DefaultDepth(xDisplay, xScreen) <= 2) {
984       appData.monoMode = True;
985     }
986 #endif
987
988     forceMono = MakeColors();
989
990     if (forceMono) {
991       fprintf(stderr, _("%s: too few colors available; trying monochrome mode\n"),
992               programName);
993         appData.monoMode = True;
994     }
995
996     ParseIcsTextColors();
997
998     /*
999      * widget hierarchy
1000      */
1001     if (tinyLayout) {
1002         layoutName = "tinyLayout";
1003     } else if (smallLayout) {
1004         layoutName = "smallLayout";
1005     } else {
1006         layoutName = "normalLayout";
1007     }
1008
1009     wpMain.width = -1; // prevent popup sizes window
1010     optList = BoardPopUp(squareSize, lineGap, (void*)
1011 #ifdef TODO_GTK
1012 #if ENABLE_NLS
1013                                                 &clockFontSet);
1014 #else
1015                                                 clockFontStruct);
1016 #endif
1017 #else
1018 0);
1019 #endif
1020     InitDrawingHandle(optList + W_BOARD);
1021     shellWidget      = shells[BoardWindow];
1022     currBoard        = &optList[W_BOARD];
1023     boardWidget      = optList[W_BOARD].handle;
1024     menuBarWidget    = optList[W_MENU].handle;
1025     dropMenu         = optList[W_DROP].handle;
1026     titleWidget = optList[optList[W_TITLE].type != -1 ? W_TITLE : W_SMALL].handle;
1027 #ifdef TODO_GTK
1028     formWidget  = XtParent(boardWidget);
1029     XtSetArg(args[0], XtNbackground, &timerBackgroundPixel);
1030     XtSetArg(args[1], XtNforeground, &timerForegroundPixel);
1031     XtGetValues(optList[W_WHITE].handle, args, 2);
1032     if (appData.showButtonBar) { // can't we use timer pixels for this? (Or better yet, just black & white?)
1033       XtSetArg(args[0], XtNbackground, &buttonBackgroundPixel);
1034       XtSetArg(args[1], XtNforeground, &buttonForegroundPixel);
1035       XtGetValues(optList[W_PAUSE].handle, args, 2);
1036     }
1037 #endif
1038
1039     // [HGM] it seems the layout code ends here, but perhaps the color stuff is size independent and would
1040     //       not need to go into InitDrawingSizes().
1041
1042     InitMenuMarkers();
1043
1044     // add accelerators to main shell
1045     gtk_window_add_accel_group(GTK_WINDOW(shellWidget), GtkAccelerators);
1046
1047     /*
1048      * Create an icon. (Use two icons, to indicate whther it is white's or black's turn.)
1049      */
1050     WhiteIcon  = gdk_pixbuf_new_from_file(SVGDIR "/icon_white.svg", NULL);
1051     BlackIcon  = gdk_pixbuf_new_from_file(SVGDIR "/icon_black.svg", NULL);
1052     mainwindowIcon = WhiteIcon;
1053     gtk_window_set_icon(GTK_WINDOW(shellWidget), mainwindowIcon);
1054
1055
1056     /*
1057      * Create a cursor for the board widget.
1058      */
1059 #ifdef TODO_GTK
1060     window_attributes.cursor = XCreateFontCursor(xDisplay, XC_hand2);
1061     XChangeWindowAttributes(xDisplay, xBoardWindow,
1062                             CWCursor, &window_attributes);
1063 #endif
1064
1065     /*
1066      * Inhibit shell resizing.
1067      */
1068 #ifdef TODO_GTK
1069     shellArgs[0].value = (XtArgVal) &w;
1070     shellArgs[1].value = (XtArgVal) &h;
1071     XtGetValues(shellWidget, shellArgs, 2);
1072     shellArgs[4].value = shellArgs[2].value = w;
1073     shellArgs[5].value = shellArgs[3].value = h;
1074 //    XtSetValues(shellWidget, &shellArgs[2], 4);
1075 #endif
1076     {
1077         // Note: We cannot do sensible sizing here, because the height of the clock widget is not yet known
1078         // It wil only become known asynchronously, when we first write a string into it.
1079         // This will then change the clock widget height, which triggers resizing the top-level window
1080         // and a configure event. Only then can we know the total height of the top-level window,
1081         // and calculate the height we need. The clockKludge flag suppresses all resizing until
1082         // that moment comes, after which the configure event-handler handles it through a (delayed) DragProg.
1083         int hc;
1084         GtkAllocation a;
1085         gtk_widget_get_allocation(shells[BoardWindow], &a);
1086         w = a.width; h = a.height;
1087         gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1088         clockKludge = hc = a.height;
1089         gtk_widget_get_allocation(boardWidget, &a);
1090         marginW =  w - boardWidth; // [HGM] needed to set new shellWidget size when we resize board
1091         marginH =  h - a.height - hc; // subtract current clock height, so it can be added back dynamically
1092     }
1093
1094     CreateAnyPieces();
1095     CreateGrid();
1096
1097     if(appData.logoSize)
1098     {   // locate and read user logo
1099         char buf[MSG_SIZ];
1100         snprintf(buf, MSG_SIZ, "%s/%s.png", appData.logoDir, UserName());
1101         ASSIGN(userLogo, buf);
1102     }
1103
1104     if (appData.animate || appData.animateDragging)
1105       CreateAnimVars();
1106
1107     g_signal_connect(shells[BoardWindow], "key-press-event", G_CALLBACK(KeyPressProc), NULL);
1108     g_signal_connect(shells[BoardWindow], "configure-event", G_CALLBACK(EventProc), NULL);
1109
1110     /* [AS] Restore layout */
1111     if( wpMoveHistory.visible ) {
1112       HistoryPopUp();
1113     }
1114
1115     if( wpEvalGraph.visible )
1116       {
1117         EvalGraphPopUp();
1118       };
1119
1120     if( wpEngineOutput.visible ) {
1121       EngineOutputPopUp();
1122     }
1123
1124     InitBackEnd2();
1125
1126     if (errorExitStatus == -1) {
1127         if (appData.icsActive) {
1128             /* We now wait until we see "login:" from the ICS before
1129                sending the logon script (problems with timestamp otherwise) */
1130             /*ICSInitScript();*/
1131             if (appData.icsInputBox) ICSInputBoxPopUp();
1132         }
1133
1134     #ifdef SIGWINCH
1135     signal(SIGWINCH, TermSizeSigHandler);
1136     #endif
1137         signal(SIGINT, IntSigHandler);
1138         signal(SIGTERM, IntSigHandler);
1139         if (*appData.cmailGameName != NULLCHAR) {
1140             signal(SIGUSR1, CmailSigHandler);
1141         }
1142     }
1143
1144     gameInfo.boardWidth = 0; // [HGM] pieces: kludge to ensure InitPosition() calls InitDrawingSizes()
1145     InitPosition(TRUE);
1146     UpdateLogos(TRUE);
1147 //    XtSetKeyboardFocus(shellWidget, formWidget);
1148 #ifdef TODO_GTK
1149     XSetInputFocus(xDisplay, XtWindow(formWidget), RevertToPointerRoot, CurrentTime);
1150 #endif
1151
1152     /* check for GTK events and process them */
1153 //    gtk_main();
1154 while(1) {
1155 gtk_main_iteration();
1156 }
1157
1158     if (appData.debugMode) fclose(debugFP); // [DM] debug
1159     return 0;
1160 }
1161
1162 RETSIGTYPE
1163 TermSizeSigHandler (int sig)
1164 {
1165     update_ics_width();
1166 }
1167
1168 RETSIGTYPE
1169 IntSigHandler (int sig)
1170 {
1171     ExitEvent(sig);
1172 }
1173
1174 RETSIGTYPE
1175 CmailSigHandler (int sig)
1176 {
1177     int dummy = 0;
1178     int error;
1179
1180     signal(SIGUSR1, SIG_IGN);   /* suspend handler     */
1181
1182     /* Activate call-back function CmailSigHandlerCallBack()             */
1183     OutputToProcess(cmailPR, (char *)(&dummy), sizeof(int), &error);
1184
1185     signal(SIGUSR1, CmailSigHandler); /* re-activate handler */
1186 }
1187
1188 void
1189 CmailSigHandlerCallBack (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1190 {
1191     BoardToTop();
1192     ReloadCmailMsgEvent(TRUE);  /* Reload cmail msg  */
1193 }
1194 /**** end signal code ****/
1195
1196
1197 #define Abs(n) ((n)<0 ? -(n) : (n))
1198
1199 #ifdef ENABLE_NLS
1200 char *
1201 InsertPxlSize (char *pattern, int targetPxlSize)
1202 {
1203     char *base_fnt_lst, strInt[12], *p, *q;
1204     int alternatives, i, len, strIntLen;
1205
1206     /*
1207      * Replace the "*" (if present) in the pixel-size slot of each
1208      * alternative with the targetPxlSize.
1209      */
1210     p = pattern;
1211     alternatives = 1;
1212     while ((p = strchr(p, ',')) != NULL) {
1213       alternatives++;
1214       p++;
1215     }
1216     snprintf(strInt, sizeof(strInt), "%d", targetPxlSize);
1217     strIntLen = strlen(strInt);
1218     base_fnt_lst = calloc(1, strlen(pattern) + strIntLen * alternatives + 1);
1219
1220     p = pattern;
1221     q = base_fnt_lst;
1222     while (alternatives--) {
1223       char *comma = strchr(p, ',');
1224       for (i=0; i<14; i++) {
1225         char *hyphen = strchr(p, '-');
1226         if (!hyphen) break;
1227         if (comma && hyphen > comma) break;
1228         len = hyphen + 1 - p;
1229         if (i == 7 && *p == '*' && len == 2) {
1230           p += len;
1231           memcpy(q, strInt, strIntLen);
1232           q += strIntLen;
1233           *q++ = '-';
1234         } else {
1235           memcpy(q, p, len);
1236           p += len;
1237           q += len;
1238         }
1239       }
1240       if (!comma) break;
1241       len = comma + 1 - p;
1242       memcpy(q, p, len);
1243       p += len;
1244       q += len;
1245     }
1246     strcpy(q, p);
1247
1248     return base_fnt_lst;
1249 }
1250
1251 #ifdef TODO_GTK
1252 XFontSet
1253 CreateFontSet (char *base_fnt_lst)
1254 {
1255     XFontSet fntSet;
1256     char **missing_list;
1257     int missing_count;
1258     char *def_string;
1259
1260     fntSet = XCreateFontSet(xDisplay, base_fnt_lst,
1261                             &missing_list, &missing_count, &def_string);
1262     if (appData.debugMode) {
1263       int i, count;
1264       XFontStruct **font_struct_list;
1265       char **font_name_list;
1266       fprintf(debugFP, "Requested font set for list %s\n", base_fnt_lst);
1267       if (fntSet) {
1268         fprintf(debugFP, " got list %s, locale %s\n",
1269                 XBaseFontNameListOfFontSet(fntSet),
1270                 XLocaleOfFontSet(fntSet));
1271         count = XFontsOfFontSet(fntSet, &font_struct_list, &font_name_list);
1272         for (i = 0; i < count; i++) {
1273           fprintf(debugFP, " got charset %s\n", font_name_list[i]);
1274         }
1275       }
1276       for (i = 0; i < missing_count; i++) {
1277         fprintf(debugFP, " missing charset %s\n", missing_list[i]);
1278       }
1279     }
1280     if (fntSet == NULL) {
1281       fprintf(stderr, _("Unable to create font set for %s.\n"), base_fnt_lst);
1282       exit(2);
1283     }
1284     return fntSet;
1285 }
1286 #endif
1287 #else // not ENABLE_NLS
1288 /*
1289  * Find a font that matches "pattern" that is as close as
1290  * possible to the targetPxlSize.  Prefer fonts that are k
1291  * pixels smaller to fonts that are k pixels larger.  The
1292  * pattern must be in the X Consortium standard format,
1293  * e.g. "-*-helvetica-bold-r-normal--*-*-*-*-*-*-*-*".
1294  * The return value should be freed with XtFree when no
1295  * longer needed.
1296  */
1297 char *
1298 FindFont (char *pattern, int targetPxlSize)
1299 {
1300     char **fonts, *p, *best, *scalable, *scalableTail;
1301     int i, j, nfonts, minerr, err, pxlSize;
1302
1303 #ifdef TODO_GTK
1304     fonts = XListFonts(xDisplay, pattern, 999999, &nfonts);
1305     if (nfonts < 1) {
1306         fprintf(stderr, _("%s: no fonts match pattern %s\n"),
1307                 programName, pattern);
1308         exit(2);
1309     }
1310
1311     best = fonts[0];
1312     scalable = NULL;
1313     minerr = 999999;
1314     for (i=0; i<nfonts; i++) {
1315         j = 0;
1316         p = fonts[i];
1317         if (*p != '-') continue;
1318         while (j < 7) {
1319             if (*p == NULLCHAR) break;
1320             if (*p++ == '-') j++;
1321         }
1322         if (j < 7) continue;
1323         pxlSize = atoi(p);
1324         if (pxlSize == 0) {
1325             scalable = fonts[i];
1326             scalableTail = p;
1327         } else {
1328             err = pxlSize - targetPxlSize;
1329             if (Abs(err) < Abs(minerr) ||
1330                 (minerr > 0 && err < 0 && -err == minerr)) {
1331                 best = fonts[i];
1332                 minerr = err;
1333             }
1334         }
1335     }
1336     if (scalable && Abs(minerr) > appData.fontSizeTolerance) {
1337         /* If the error is too big and there is a scalable font,
1338            use the scalable font. */
1339         int headlen = scalableTail - scalable;
1340         p = (char *) XtMalloc(strlen(scalable) + 10);
1341         while (isdigit(*scalableTail)) scalableTail++;
1342         sprintf(p, "%.*s%d%s", headlen, scalable, targetPxlSize, scalableTail);
1343     } else {
1344         p = (char *) XtMalloc(strlen(best) + 2);
1345         safeStrCpy(p, best, strlen(best)+1 );
1346     }
1347     if (appData.debugMode) {
1348         fprintf(debugFP, "resolved %s at pixel size %d\n  to %s\n",
1349                 pattern, targetPxlSize, p);
1350     }
1351     XFreeFontNames(fonts);
1352 #endif
1353     return p;
1354 }
1355 #endif
1356
1357 void
1358 EnableNamedMenuItem (char *menuRef, int state)
1359 {
1360     MenuItem *item = MenuNameToItem(menuRef);
1361
1362     if(item) gtk_widget_set_sensitive(item->handle, state);
1363 }
1364
1365 void
1366 EnableButtonBar (int state)
1367 {
1368 #ifdef TODO_GTK
1369     XtSetSensitive(optList[W_BUTTON].handle, state);
1370 #endif
1371 }
1372
1373
1374 void
1375 SetMenuEnables (Enables *enab)
1376 {
1377   while (enab->name != NULL) {
1378     EnableNamedMenuItem(enab->name, enab->value);
1379     enab++;
1380   }
1381 }
1382
1383 gboolean KeyPressProc(window, eventkey, data)
1384      GtkWindow *window;
1385      GdkEventKey  *eventkey;
1386      gpointer data;
1387 {
1388
1389     MoveTypeInProc(eventkey); // pop up for typed in moves
1390
1391 #ifdef TODO_GTK
1392     /* check for other key values */
1393     switch(eventkey->keyval) {
1394         case GDK_question:
1395           AboutGameEvent();
1396           break;
1397         default:
1398           break;
1399     }
1400 #endif
1401     return False;
1402 }
1403 #ifdef TODO_GTK
1404 void
1405 KeyBindingProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1406 {   // [HGM] new method of key binding: specify MenuItem(FlipView) in stead of FlipViewProc in translation string
1407     MenuItem *item;
1408     if(*nprms == 0) return;
1409     item = MenuNameToItem(prms[0]);
1410     if(item) ((MenuProc *) item->proc) ();
1411 }
1412 #endif
1413
1414 void
1415 SetupDropMenu ()
1416 {
1417 #ifdef TODO_GTK
1418     int i, j, count;
1419     char label[32];
1420     Arg args[16];
1421     Widget entry;
1422     char* p;
1423
1424     for (i=0; i<sizeof(dmEnables)/sizeof(DropMenuEnables); i++) {
1425         entry = XtNameToWidget(dropMenu, dmEnables[i].widget);
1426         p = strchr(gameMode == IcsPlayingWhite ? white_holding : black_holding,
1427                    dmEnables[i].piece);
1428         XtSetSensitive(entry, p != NULL || !appData.testLegality
1429                        /*!!temp:*/ || (gameInfo.variant == VariantCrazyhouse
1430                                        && !appData.icsActive));
1431         count = 0;
1432         while (p && *p++ == dmEnables[i].piece) count++;
1433         snprintf(label, sizeof(label), "%s  %d", dmEnables[i].widget, count);
1434         j = 0;
1435         XtSetArg(args[j], XtNlabel, label); j++;
1436         XtSetValues(entry, args, j);
1437     }
1438 #endif
1439 }
1440
1441 static void
1442 do_flash_delay (unsigned long msec)
1443 {
1444     TimeDelay(msec);
1445 }
1446
1447 void
1448 FlashDelay (int flash_delay)
1449 {
1450         if(flash_delay) do_flash_delay(flash_delay);
1451 }
1452
1453 double
1454 Fraction (int x, int start, int stop)
1455 {
1456    double f = ((double) x - start)/(stop - start);
1457    if(f > 1.) f = 1.; else if(f < 0.) f = 0.;
1458    return f;
1459 }
1460
1461 static WindowPlacement wpNew;
1462
1463 void
1464 CoDrag (GtkWidget *sh, WindowPlacement *wp)
1465 {
1466     int touch=0, fudge = 2, f = 2;
1467     GetActualPlacement(sh, wp);
1468     if(abs(wpMain.x + wpMain.width + 2*frameX - f - wp->x)         < fudge) touch = 1; else // right touch
1469     if(abs(wp->x + wp->width + 2*frameX + f - wpMain.x)            < fudge) touch = 2; else // left touch
1470     if(abs(wpMain.y + wpMain.height + frameX - f + frameY - wp->y) < fudge) touch = 3; else // bottom touch
1471     if(abs(wp->y + wp->height + frameX + frameY + f - wpMain.y)    < fudge) touch = 4;      // top touch
1472 //printf("CoDrag: touch = %d x=%d w=%d x2=%d w2=%d fx=%d\n", touch, wpMain.x, wpMain.width, wp->x, wp->width, frameX);
1473     if(!touch ) return; // only windows that touch co-move
1474     if(touch < 3 && wpNew.height != wpMain.height) { // left or right and height changed
1475         int heightInc = wpNew.height - wpMain.height;
1476         double fracTop = Fraction(wp->y, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1477         double fracBot = Fraction(wp->y + wp->height + frameX + frameY + 1, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1478         wp->y += fracTop * heightInc;
1479         heightInc = (int) (fracBot * heightInc) - (int) (fracTop * heightInc);
1480 #ifdef TODO_GTK
1481         if(heightInc) XtSetArg(args[j], XtNheight, wp->height + heightInc), j++;
1482 #endif
1483         wp->height += heightInc;
1484     } else if(touch > 2 && wpNew.width != wpMain.width) { // top or bottom and width changed
1485         int widthInc = wpNew.width - wpMain.width;
1486         double fracLeft = Fraction(wp->x, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1487         double fracRght = Fraction(wp->x + wp->width + 2*frameX + 1, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1488         wp->y += fracLeft * widthInc;
1489         widthInc = (int) (fracRght * widthInc) - (int) (fracLeft * widthInc);
1490 #ifdef TODO_GTK
1491         if(widthInc) XtSetArg(args[j], XtNwidth, wp->width + widthInc), j++;
1492 #endif
1493         wp->width += widthInc;
1494     }
1495     wp->x += wpNew.x - wpMain.x;
1496     wp->y += wpNew.y - wpMain.y;
1497     if(touch == 1) wp->x += wpNew.width - wpMain.width; else
1498     if(touch == 3) wp->y += wpNew.height - wpMain.height;
1499 #ifdef TODO_GTK
1500     XtSetArg(args[j], XtNx, wp->x); j++;
1501     XtSetArg(args[j], XtNy, wp->y); j++;
1502     XtSetValues(sh, args, j);
1503 #endif
1504         gtk_window_move(GTK_WINDOW(sh), wp->x, wp->y);
1505 //printf("moved to (%d,%d)\n", wp->x, wp->y);
1506         gtk_window_resize(GTK_WINDOW(sh), wp->width, wp->height);
1507 }
1508
1509 void
1510 ReSize (WindowPlacement *wp)
1511 {
1512         GtkAllocation a;
1513         int sqx, sqy, w, h, hc, lg = lineGap;
1514         gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1515         hc = a.height; // clock height can depend on single / double line clock text!
1516         if(clockKludge == a.height) return; // wait for clock to get final size at startup
1517         if(clockKludge) { // clock height OK now; calculate desired initial board height
1518             clockKludge = 0;
1519             wp->height = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + marginH + hc;
1520         }
1521         if(wp->width == wpMain.width && wp->height == wpMain.height) return; // not sized
1522         sqx = (wp->width  - lg - marginW) / BOARD_WIDTH - lg;
1523         sqy = (wp->height - lg - marginH - hc) / BOARD_HEIGHT - lg;
1524         if(sqy < sqx) sqx = sqy;
1525         if(appData.overrideLineGap < 0) { // do second iteration with adjusted lineGap
1526             lg = lineGap = sqx < 37 ? 1 : sqx < 59 ? 2 : sqx < 116 ? 3 : 4;
1527             sqx = (wp->width  - lg - marginW) / BOARD_WIDTH - lg;
1528             sqy = (wp->height - lg - marginH - hc) / BOARD_HEIGHT - lg;
1529             if(sqy < sqx) sqx = sqy;
1530         }
1531         if(sqx != squareSize) {
1532 //printf("new sq size %d (%dx%d)\n", sqx, wp->width, wp->height);
1533             squareSize = sqx; // adopt new square size
1534             CreatePNGPieces(); // make newly scaled pieces
1535             InitDrawingSizes(0, 0); // creates grid etc.
1536         } else ResizeBoardWindow(BOARD_WIDTH * (squareSize + lineGap) + lineGap, BOARD_HEIGHT * (squareSize + lineGap) + lineGap, 0);
1537         w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
1538         h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
1539         if(optList[W_BOARD].max   > w) optList[W_BOARD].max = w;
1540         if(optList[W_BOARD].value > h) optList[W_BOARD].value = h;
1541 }
1542
1543 static guint delayedDragTag = 0;
1544
1545 void
1546 DragProc ()
1547 {
1548         static int busy;
1549         if(busy) return;
1550
1551         busy = 1;
1552 //      GetActualPlacement(shellWidget, &wpNew);
1553         if(wpNew.x == wpMain.x && wpNew.y == wpMain.y && // not moved
1554            wpNew.width == wpMain.width && wpNew.height == wpMain.height) { // not sized
1555             busy = 0; return; // false alarm
1556         }
1557         ReSize(&wpNew);
1558         if(appData.useStickyWindows) {
1559             if(shellUp[EngOutDlg]) CoDrag(shells[EngOutDlg], &wpEngineOutput);
1560             if(shellUp[HistoryDlg]) CoDrag(shells[HistoryDlg], &wpMoveHistory);
1561             if(shellUp[EvalGraphDlg]) CoDrag(shells[EvalGraphDlg], &wpEvalGraph);
1562             if(shellUp[GameListDlg]) CoDrag(shells[GameListDlg], &wpGameList);
1563         }
1564         wpMain = wpNew;
1565         DrawPosition(True, NULL);
1566         if(delayedDragTag) g_source_remove(delayedDragTag);
1567         delayedDragTag = 0; // now drag executed, make sure next DelayedDrag will not cancel timer event (which could now be used by other)
1568         busy = 0;
1569 }
1570
1571 void
1572 DelayedDrag ()
1573 {
1574 //printf("old timr = %d\n", delayedDragTag);
1575     if(delayedDragTag) g_source_remove(delayedDragTag);
1576     delayedDragTag = g_timeout_add( 200, (GSourceFunc) DragProc, NULL);
1577 //printf("new timr = %d\n", delayedDragTag);
1578 }
1579
1580 static gboolean
1581 EventProc (GtkWidget *widget, GdkEvent *event, gpointer g)
1582 {
1583 //printf("event proc (%d,%d) %dx%d\n", event->configure.x, event->configure.y, event->configure.width, event->configure.height);
1584     // immediately
1585     wpNew.x = event->configure.x;
1586     wpNew.y = event->configure.y;
1587     wpNew.width  = event->configure.width;
1588     wpNew.height = event->configure.height;
1589     DelayedDrag(); // as long as events keep coming in faster than 50 msec, they destroy each other
1590     return FALSE;
1591 }
1592
1593
1594
1595 /* Disable all user input other than deleting the window */
1596 static int frozen = 0;
1597
1598 void
1599 FreezeUI ()
1600 {
1601   if (frozen) return;
1602   /* Grab by a widget that doesn't accept input */
1603   gtk_grab_add(optList[W_MESSG].handle);
1604   frozen = 1;
1605 }
1606
1607 /* Undo a FreezeUI */
1608 void
1609 ThawUI ()
1610 {
1611   if (!frozen) return;
1612   gtk_grab_remove(optList[W_MESSG].handle);
1613   frozen = 0;
1614 }
1615
1616 void
1617 ModeHighlight ()
1618 {
1619     static int oldPausing = FALSE;
1620     static GameMode oldmode = (GameMode) -1;
1621     char *wname;
1622     if (!boardWidget) return;
1623
1624     if (pausing != oldPausing) {
1625         oldPausing = pausing;
1626         MarkMenuItem("Mode.Pause", pausing);
1627
1628         if (appData.showButtonBar) {
1629           /* Always toggle, don't set.  Previous code messes up when
1630              invoked while the button is pressed, as releasing it
1631              toggles the state again. */
1632             GdkColor color;
1633             gdk_color_parse( pausing ? "#808080" : "#F0F0F0", &color );
1634             gtk_widget_modify_bg ( GTK_WIDGET(optList[W_PAUSE].handle), GTK_STATE_NORMAL, &color );
1635         }
1636     }
1637
1638     wname = ModeToWidgetName(oldmode);
1639     if (wname != NULL) {
1640         MarkMenuItem(wname, False);
1641     }
1642     wname = ModeToWidgetName(gameMode);
1643     if (wname != NULL) {
1644         MarkMenuItem(wname, True);
1645     }
1646     oldmode = gameMode;
1647     MarkMenuItem("Mode.MachineMatch", matchMode && matchGame < appData.matchGames);
1648
1649     /* Maybe all the enables should be handled here, not just this one */
1650     EnableNamedMenuItem("Mode.Training", gameMode == Training || gameMode == PlayFromGameFile);
1651
1652     DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
1653 }
1654
1655
1656 /*
1657  * Button/menu procedures
1658  */
1659
1660 void CopyFileToClipboard(gchar *filename)
1661 {
1662     gchar *selection_tmp;
1663     GtkClipboard *cb;
1664
1665     // read the file
1666     FILE* f = fopen(filename, "r");
1667     long len;
1668     size_t count;
1669     if (f == NULL) return;
1670     fseek(f, 0, 2);
1671     len = ftell(f);
1672     rewind(f);
1673     selection_tmp = g_try_malloc(len + 1);
1674     if (selection_tmp == NULL) {
1675         printf("Malloc failed in CopyFileToClipboard\n");
1676         return;
1677     }
1678     count = fread(selection_tmp, 1, len, f);
1679     fclose(f);
1680     if (len != count) {
1681       g_free(selection_tmp);
1682       return;
1683     }
1684     selection_tmp[len] = NULLCHAR; // file is now in selection_tmp
1685
1686     // copy selection_tmp to clipboard
1687     GdkDisplay *gdisp = gdk_display_get_default();
1688     if (!gdisp) {
1689         g_free(selection_tmp);
1690         return;
1691     }
1692     cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1693     gtk_clipboard_set_text(cb, selection_tmp, -1);
1694     g_free(selection_tmp);
1695 }
1696
1697 void
1698 CopySomething (char *src)
1699 {
1700     GdkDisplay *gdisp = gdk_display_get_default();
1701     GtkClipboard *cb;
1702     if(!src) { CopyFileToClipboard(gameCopyFilename); return; }
1703     if (gdisp == NULL) return;
1704     cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1705     gtk_clipboard_set_text(cb, src, -1);
1706 }
1707
1708 void
1709 PastePositionProc ()
1710 {
1711     GdkDisplay *gdisp = gdk_display_get_default();
1712     GtkClipboard *cb;
1713     gchar *fenstr;
1714
1715     if (gdisp == NULL) return;
1716     cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1717     fenstr = gtk_clipboard_wait_for_text(cb);
1718     if (fenstr==NULL) return; // nothing had been selected to copy
1719     EditPositionPasteFEN(fenstr);
1720     return;
1721 }
1722
1723 void
1724 PasteGameProc ()
1725 {
1726     gchar *text=NULL;
1727     GtkClipboard *cb;
1728     guint len=0;
1729     FILE* f;
1730
1731     // get game from clipboard
1732     GdkDisplay *gdisp = gdk_display_get_default();
1733     if (gdisp == NULL) return;
1734     cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1735     text = gtk_clipboard_wait_for_text(cb);
1736     if (text == NULL) return; // nothing to paste
1737     len = strlen(text);
1738
1739     // write to temp file
1740     if (text == NULL || len == 0) {
1741       return; //nothing to paste
1742     }
1743     f = fopen(gamePasteFilename, "w");
1744     if (f == NULL) {
1745       DisplayError(_("Can't open temp file"), errno);
1746       return;
1747     }
1748     fwrite(text, 1, len, f);
1749     fclose(f);
1750
1751     // load from file
1752     LoadGameFromFile(gamePasteFilename, 0, gamePasteFilename, TRUE);
1753     return;
1754 }
1755
1756
1757 #ifdef TODO_GTK
1758 void
1759 QuitWrapper (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1760 {
1761     QuitProc();
1762 }
1763 #endif
1764
1765 void MoveTypeInProc(eventkey)
1766     GdkEventKey  *eventkey;
1767 {
1768     char buf[10];
1769
1770     // ingnore if ctrl, alt, or meta is pressed
1771     if (eventkey->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_META_MASK)) {
1772         return;
1773     }
1774
1775     buf[0]=eventkey->keyval;
1776     buf[1]='\0';
1777     if (eventkey->keyval > 32 && eventkey->keyval < 256)
1778         BoxAutoPopUp (buf);
1779 }
1780
1781 #ifdef TODO_GTK
1782 void
1783 TempBackwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1784 {
1785         if (!TempBackwardActive) {
1786                 TempBackwardActive = True;
1787                 BackwardEvent();
1788         }
1789 }
1790
1791 void
1792 TempForwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1793 {
1794         /* Check to see if triggered by a key release event for a repeating key.
1795          * If so the next queued event will be a key press of the same key at the same time */
1796         if (XEventsQueued(xDisplay, QueuedAfterReading)) {
1797                 XEvent next;
1798                 XPeekEvent(xDisplay, &next);
1799                 if (next.type == KeyPress && next.xkey.time == event->xkey.time &&
1800                         next.xkey.keycode == event->xkey.keycode)
1801                                 return;
1802         }
1803     ForwardEvent();
1804         TempBackwardActive = False;
1805 }
1806 #endif
1807
1808 void
1809 ManProc ()
1810 {   // called from menu
1811 #ifdef __APPLE__
1812     char buf[MSG_SIZ];
1813     snprintf(buf, MSG_SIZ, "%s ./man.command", appData.sysOpen);
1814     system(buf);
1815 #else
1816     system("xterm -e man xboard &");
1817 #endif
1818 }
1819
1820 void
1821 SetWindowTitle (char *text, char *title, char *icon)
1822 {
1823 #ifdef TODO_GTK
1824     Arg args[16];
1825     int i;
1826     if (appData.titleInWindow) {
1827         i = 0;
1828         XtSetArg(args[i], XtNlabel, text);   i++;
1829         XtSetValues(titleWidget, args, i);
1830     }
1831     i = 0;
1832     XtSetArg(args[i], XtNiconName, (XtArgVal) icon);    i++;
1833     XtSetArg(args[i], XtNtitle, (XtArgVal) title);      i++;
1834     XtSetValues(shellWidget, args, i);
1835     XSync(xDisplay, False);
1836 #endif
1837     if (appData.titleInWindow) {
1838         SetWidgetLabel(titleWidget, text);
1839     }
1840     gtk_window_set_title (GTK_WINDOW(shells[BoardWindow]), title);
1841 }
1842
1843
1844 void
1845 DisplayIcsInteractionTitle (String message)
1846 {
1847 #ifdef TODO_GTK
1848   if (oldICSInteractionTitle == NULL) {
1849     /* Magic to find the old window title, adapted from vim */
1850     char *wina = getenv("WINDOWID");
1851     if (wina != NULL) {
1852       Window win = (Window) atoi(wina);
1853       Window root, parent, *children;
1854       unsigned int nchildren;
1855       int (*oldHandler)() = XSetErrorHandler(NullXErrorCheck);
1856       for (;;) {
1857         if (XFetchName(xDisplay, win, &oldICSInteractionTitle)) break;
1858         if (!XQueryTree(xDisplay, win, &root, &parent,
1859                         &children, &nchildren)) break;
1860         if (children) XFree((void *)children);
1861         if (parent == root || parent == 0) break;
1862         win = parent;
1863       }
1864       XSetErrorHandler(oldHandler);
1865     }
1866     if (oldICSInteractionTitle == NULL) {
1867       oldICSInteractionTitle = "xterm";
1868     }
1869   }
1870   printf("\033]0;%s\007", message);
1871   fflush(stdout);
1872 #endif
1873 }
1874
1875
1876 void
1877 DisplayTimerLabel (Option *opt, char *color, long timer, int highlight)
1878 {
1879     GtkWidget *w = (GtkWidget *) opt->handle;
1880     GdkColor col;
1881     char *markup;
1882     char bgcolor[10];
1883     char fgcolor[10];
1884
1885     if (highlight) {
1886         strcpy(bgcolor, "black");
1887         strcpy(fgcolor, "white");
1888     } else {
1889         strcpy(bgcolor, "white");
1890         strcpy(fgcolor, "black");
1891     }
1892     if (timer > 0 &&
1893         appData.lowTimeWarning &&
1894         (timer / 1000) < appData.icsAlarmTime) {
1895         strcpy(fgcolor, appData.lowTimeWarningColor);
1896     }
1897
1898     gdk_color_parse( bgcolor, &col );
1899     gtk_widget_modify_bg(gtk_widget_get_parent(opt->handle), GTK_STATE_NORMAL, &col);
1900
1901     if (appData.clockMode) {
1902         markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s:%s%s</span>",
1903                                          bgcolor, fgcolor, color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
1904     } else {
1905         markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s  </span>",
1906                                          bgcolor, fgcolor, color);
1907     }
1908     gtk_label_set_markup(GTK_LABEL(w), markup);
1909     g_free(markup);
1910 }
1911
1912 static GdkPixbuf **clockIcons[] = { &WhiteIcon, &BlackIcon };
1913
1914 void
1915 SetClockIcon (int color)
1916 {
1917     GdkPixbuf *pm = *clockIcons[color];
1918     if (mainwindowIcon != pm) {
1919         mainwindowIcon = pm;
1920         gtk_window_set_icon(GTK_WINDOW(shellWidget), mainwindowIcon);
1921     }
1922 }
1923
1924 #define INPUT_SOURCE_BUF_SIZE 8192
1925
1926 typedef struct {
1927     CPKind kind;
1928     int fd;
1929     int lineByLine;
1930     char *unused;
1931     InputCallback func;
1932     guint sid;
1933     char buf[INPUT_SOURCE_BUF_SIZE];
1934     VOIDSTAR closure;
1935 } InputSource;
1936
1937 gboolean
1938 DoInputCallback(io, cond, data)
1939      GIOChannel  *io;
1940      GIOCondition cond;
1941      gpointer    *data;
1942 {
1943   /* read input from one of the input source (for example a chess program, ICS, etc).
1944    * and call a function that will handle the input
1945    */
1946
1947     int count;
1948     int error;
1949     char *p, *q;
1950
1951     /* All information (callback function, file descriptor, etc) is
1952      * saved in an InputSource structure
1953      */
1954     InputSource *is = (InputSource *) data;
1955
1956     if (is->lineByLine) {
1957         count = read(is->fd, is->unused,
1958                      INPUT_SOURCE_BUF_SIZE - (is->unused - is->buf));
1959         if (count <= 0) {
1960             (is->func)(is, is->closure, is->buf, count, count ? errno : 0);
1961             return True;
1962         }
1963         is->unused += count;
1964         p = is->buf;
1965         /* break input into lines and call the callback function on each
1966          * line
1967          */
1968         while (p < is->unused) {
1969             q = memchr(p, '\n', is->unused - p);
1970             if (q == NULL) break;
1971             q++;
1972             (is->func)(is, is->closure, p, q - p, 0);
1973             p = q;
1974         }
1975         /* remember not yet used part of the buffer */
1976         q = is->buf;
1977         while (p < is->unused) {
1978             *q++ = *p++;
1979         }
1980         is->unused = q;
1981     } else {
1982       /* read maximum length of input buffer and send the whole buffer
1983        * to the callback function
1984        */
1985         count = read(is->fd, is->buf, INPUT_SOURCE_BUF_SIZE);
1986         if (count == -1)
1987           error = errno;
1988         else
1989           error = 0;
1990         (is->func)(is, is->closure, is->buf, count, error);
1991     }
1992     return True; // Must return true or the watch will be removed
1993 }
1994
1995 InputSourceRef AddInputSource(pr, lineByLine, func, closure)
1996      ProcRef pr;
1997      int lineByLine;
1998      InputCallback func;
1999      VOIDSTAR closure;
2000 {
2001     InputSource *is;
2002     GIOChannel *channel;
2003     ChildProc *cp = (ChildProc *) pr;
2004
2005     is = (InputSource *) calloc(1, sizeof(InputSource));
2006     is->lineByLine = lineByLine;
2007     is->func = func;
2008     if (pr == NoProc) {
2009         is->kind = CPReal;
2010         is->fd = fileno(stdin);
2011     } else {
2012         is->kind = cp->kind;
2013         is->fd = cp->fdFrom;
2014     }
2015     if (lineByLine)
2016       is->unused = is->buf;
2017     else
2018       is->unused = NULL;
2019
2020    /* GTK-TODO: will this work on windows?*/
2021
2022     channel = g_io_channel_unix_new(is->fd);
2023     g_io_channel_set_close_on_unref (channel, TRUE);
2024     is->sid = g_io_add_watch(channel, G_IO_IN,(GIOFunc) DoInputCallback, is);
2025
2026     is->closure = closure;
2027     return (InputSourceRef) is;
2028 }
2029
2030
2031 void
2032 RemoveInputSource(isr)
2033      InputSourceRef isr;
2034 {
2035     InputSource *is = (InputSource *) isr;
2036
2037     if (is->sid == 0) return;
2038     g_source_remove(is->sid);
2039     is->sid = 0;
2040     return;
2041 }
2042
2043 #ifndef HAVE_USLEEP
2044
2045 static Boolean frameWaiting;
2046
2047 static RETSIGTYPE
2048 FrameAlarm (int sig)
2049 {
2050   frameWaiting = False;
2051   /* In case System-V style signals.  Needed?? */
2052   signal(SIGALRM, FrameAlarm);
2053 }
2054
2055 void
2056 FrameDelay (int time)
2057 {
2058   struct itimerval delay;
2059
2060   if (time > 0) {
2061     frameWaiting = True;
2062     signal(SIGALRM, FrameAlarm);
2063     delay.it_interval.tv_sec =
2064       delay.it_value.tv_sec = time / 1000;
2065     delay.it_interval.tv_usec =
2066       delay.it_value.tv_usec = (time % 1000) * 1000;
2067     setitimer(ITIMER_REAL, &delay, NULL);
2068     while (frameWaiting) pause();
2069     delay.it_interval.tv_sec = delay.it_value.tv_sec = 0;
2070     delay.it_interval.tv_usec = delay.it_value.tv_usec = 0;
2071     setitimer(ITIMER_REAL, &delay, NULL);
2072   }
2073 }
2074
2075 #else
2076
2077 void
2078 FrameDelay (int time)
2079 {
2080 #ifdef TODO_GTK
2081   XSync(xDisplay, False);
2082 #endif
2083 //  gtk_main_iteration_do(False);
2084
2085   if (time > 0)
2086     usleep(time * 1000);
2087 }
2088
2089 #endif
2090
2091 static void
2092 LoadLogo (ChessProgramState *cps, int n, Boolean ics)
2093 {
2094     char buf[MSG_SIZ], *logoName = buf;
2095     if(appData.logo[n][0]) {
2096         logoName = appData.logo[n];
2097     } else if(appData.autoLogo) {
2098         if(ics) { // [HGM] logo: in ICS mode second can be used for ICS
2099             sprintf(buf, "%s/%s.png", appData.logoDir, appData.icsHost);
2100         } else if(appData.directory[n] && appData.directory[n][0]) {
2101             sprintf(buf, "%s/%s.png", appData.logoDir, cps->tidy);
2102         }
2103     }
2104     if(logoName[0])
2105         { ASSIGN(cps->programLogo, logoName); }
2106 }
2107
2108 void
2109 UpdateLogos (int displ)
2110 {
2111     if(optList[W_WHITE-1].handle == NULL) return;
2112     LoadLogo(&first, 0, 0);
2113     LoadLogo(&second, 1, appData.icsActive);
2114     if(displ) DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
2115     return;
2116 }
2117
2118 void FileNamePopUpWrapper(label, def, filter, proc, pathFlag, openMode, name, fp)
2119      char *label;
2120      char *def;
2121      char *filter;
2122      FileProc proc;
2123      char *openMode;
2124      Boolean pathFlag;
2125      char **name;
2126      FILE **fp;
2127 {
2128   GtkWidget     *dialog;
2129   GtkFileFilter *gtkfilter;
2130   GtkFileFilter *gtkfilter_all;
2131   char space[]     = " ";
2132   char fileext[10] = "";
2133   char *result     = NULL;
2134   char *cp;
2135
2136   /* make a copy of the filter string, so that strtok can work with it*/
2137   cp = strdup(filter);
2138
2139   /* add filters for file extensions */
2140   gtkfilter     = gtk_file_filter_new();
2141   gtkfilter_all = gtk_file_filter_new();
2142
2143   /* one filter to show everything */
2144   gtk_file_filter_add_pattern(gtkfilter_all, "*.*");
2145   gtk_file_filter_set_name   (gtkfilter_all, "All Files");
2146
2147   /* add filter if present */
2148   result = strtok(cp, space);
2149   while( result != NULL  ) {
2150     snprintf(fileext,10,"*%s",result);
2151     result = strtok( NULL, space );
2152     gtk_file_filter_add_pattern(gtkfilter, fileext);
2153   };
2154
2155   /* second filter to only show what's useful */
2156   gtk_file_filter_set_name (gtkfilter,filter);
2157
2158   if (openMode[0] == 'r')
2159     {
2160       dialog = gtk_file_chooser_dialog_new (label,
2161                                             NULL,
2162                                             GTK_FILE_CHOOSER_ACTION_OPEN,
2163                                             GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2164                                             GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
2165                                             NULL);
2166     }
2167   else
2168     {
2169       dialog = gtk_file_chooser_dialog_new (label,
2170                                             NULL,
2171                                             GTK_FILE_CHOOSER_ACTION_SAVE,
2172                                             GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2173                                             GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
2174                                             NULL);
2175       /* add filename suggestions */
2176       if (strlen(def) > 0 )
2177         gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), def);
2178
2179       //gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER (dialog),TRUE);
2180     }
2181
2182   /* add filters */
2183   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter_all);
2184   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2185   /* activate filter */
2186   gtk_file_chooser_set_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2187
2188   if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
2189     {
2190       char *filename;
2191       FILE *f;
2192
2193       filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
2194
2195       //see loadgamepopup
2196       f = fopen(filename, openMode);
2197       if (f == NULL)
2198         {
2199           DisplayError(_("Failed to open file"), errno);
2200         }
2201       else
2202         {
2203           /* TODO add indec */
2204             *fp = f;
2205             ASSIGN(*name, filename);
2206             ScheduleDelayedEvent(DelayedLoad, 50);
2207         }
2208       g_free (filename);
2209     };
2210
2211   gtk_widget_destroy (dialog);
2212   ModeHighlight();
2213
2214   free(cp);
2215   return;
2216
2217 }