2 * xboard.c -- X front end for XBoard
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
10 * The following terms apply to Digital Equipment Corporation's copyright
12 * ------------------------------------------------------------------------
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.
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
30 * ------------------------------------------------------------------------
32 * The following terms apply to the enhanced version of XBoard
33 * distributed by the Free Software Foundation:
34 * ------------------------------------------------------------------------
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.
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.
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/. *
49 *------------------------------------------------------------------------
50 ** See the file ChangeLog for a revision history. */
60 #include <sys/types.h>
64 #include <cairo/cairo.h>
65 #include <cairo/cairo-xlib.h>
69 # if HAVE_SYS_SOCKET_H
70 # include <sys/socket.h>
71 # include <netinet/in.h>
73 # else /* not HAVE_SYS_SOCKET_H */
74 # if HAVE_LAN_SOCKET_H
75 # include <lan/socket.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 */
87 #else /* not STDC_HEADERS */
88 extern char *getenv();
91 # else /* not HAVE_STRING_H */
93 # endif /* not HAVE_STRING_H */
94 #endif /* not STDC_HEADERS */
97 # include <sys/fcntl.h>
98 #else /* not HAVE_SYS_FCNTL_H */
101 # endif /* HAVE_FCNTL_H */
102 #endif /* not HAVE_SYS_FCNTL_H */
104 #if HAVE_SYS_SYSTEMINFO_H
105 # include <sys/systeminfo.h>
106 #endif /* HAVE_SYS_SYSTEMINFO_H */
108 #if TIME_WITH_SYS_TIME
109 # include <sys/time.h>
113 # include <sys/time.h>
124 # include <sys/wait.h>
129 # define NAMLEN(dirent) strlen((dirent)->d_name)
130 # define HAVE_DIR_STRUCT
132 # define dirent direct
133 # define NAMLEN(dirent) (dirent)->d_namlen
135 # include <sys/ndir.h>
136 # define HAVE_DIR_STRUCT
139 # include <sys/dir.h>
140 # define HAVE_DIR_STRUCT
144 # define HAVE_DIR_STRUCT
152 // [HGM] bitmaps: put before incuding the bitmaps / pixmaps, to know how many piece types there are.
155 #include "frontend.h"
157 #include "backendz.h"
165 #include "engineoutput.h"
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).
176 // redefine some defaults
180 # undef SETTINGS_FILE
181 # define ICS_LOGON "Library/Preferences/XboardICS.conf"
182 # define DATADIR dataDir
183 # define LOCALEDIR localeDir
184 # define SETTINGS_FILE masterSettings
185 # define SYNC_MENUBAR gtkosx_application_sync_menubar(theApp)
186 char dataDir[MSG_SIZ]; // for expanding ~~
187 char localeDir[MSG_SIZ];
188 char masterSettings[MSG_SIZ];
192 # define SYNC_MENUBAR
199 #define usleep(t) _sleep2(((t)+500)/1000)
203 # define _(s) gettext (s)
204 # define N_(s) gettext_noop (s)
210 int main P((int argc, char **argv));
211 RETSIGTYPE CmailSigHandler P((int sig));
212 RETSIGTYPE IntSigHandler P((int sig));
213 RETSIGTYPE TermSizeSigHandler P((int sig));
214 char *InsertPxlSize P((char *pattern, int targetPxlSize));
216 XFontSet CreateFontSet P((char *base_fnt_lst));
218 char *FindFont P((char *pattern, int targetPxlSize));
220 void DelayedDrag P((void));
221 void ICSInputBoxPopUp P((void));
222 void MoveTypeInProc P((GdkEventKey *eventkey));
223 gboolean KeyPressProc P((GtkWindow *window, GdkEventKey *eventkey, gpointer data));
224 Boolean TempBackwardActive = False;
225 void DisplayMove P((int moveNumber));
226 void update_ics_width P(());
227 int CopyMemoProc P(());
228 static gboolean EventProc P((GtkWidget *widget, GdkEvent *event, gpointer g));
229 static int FindLogo P((char *place, char *name, char *buf));
233 XFontSet fontSet, clockFontSet;
236 XFontStruct *clockFontStruct;
238 Font coordFontID, countFontID;
239 XFontStruct *coordFontStruct, *countFontStruct;
241 void *shellWidget, *formWidget, *boardWidget, *titleWidget, *dropMenu, *menuBarWidget;
242 GtkWidget *mainwindow;
244 Option *optList; // contains all widgets of main window
247 char installDir[] = "."; // [HGM] UCI: needed for UCI; probably needs run-time initializtion
250 static GdkPixbuf *mainwindowIcon=NULL;
251 static GdkPixbuf *WhiteIcon=NULL;
252 static GdkPixbuf *BlackIcon=NULL;
254 /* key board accelerators */
255 GtkAccelGroup *GtkAccelerators;
257 typedef unsigned int BoardSize;
259 Boolean chessProgram;
261 int minX, minY; // [HGM] placement: volatile limits on upper-left corner
262 int smallLayout = 0, tinyLayout = 0,
263 marginW, marginH, // [HGM] for run-time resizing
264 fromX = -1, fromY = -1, toX, toY, commentUp = False,
265 errorExitStatus = -1, defaultLineGap;
267 Dimension textHeight;
269 char *chessDir, *programName, *programVersion;
270 Boolean alwaysOnTop = False;
271 char *icsTextMenuString;
273 char *firstChessProgramNames;
274 char *secondChessProgramNames;
276 WindowPlacement wpMain;
277 WindowPlacement wpConsole;
278 WindowPlacement wpComment;
279 WindowPlacement wpMoveHistory;
280 WindowPlacement wpEvalGraph;
281 WindowPlacement wpEngineOutput;
282 WindowPlacement wpGameList;
283 WindowPlacement wpTags;
284 WindowPlacement wpDualBoard;
286 /* This magic number is the number of intermediate frames used
287 in each half of the animation. For short moves it's reduced
288 by 1. The total number of frames will be factor * 2 + 1. */
291 SizeDefaults sizeDefaults[] = SIZE_DEFAULTS;
298 DropMenuEnables dmEnables[] = {
307 XtResource clientResources[] = {
308 { "flashCount", "flashCount", XtRInt, sizeof(int),
309 XtOffset(AppDataPtr, flashCount), XtRImmediate,
310 (XtPointer) FLASH_COUNT },
314 /* keyboard shortcuts not yet transistioned int menuitem @ menu.c */
315 char globalTranslations[] =
316 ":Ctrl<Key>Down: LoadSelectedProc(3) \n \
317 :Ctrl<Key>Up: LoadSelectedProc(-3) \n \
318 :<KeyDown>Return: TempBackwardProc() \n \
319 :<KeyUp>Return: TempForwardProc() \n";
321 char ICSInputTranslations[] =
322 "<Key>Up: UpKeyProc() \n "
323 "<Key>Down: DownKeyProc() \n "
324 "<Key>Return: EnterKeyProc() \n";
326 // [HGM] vari: another hideous kludge: call extend-end first so we can be sure select-start works,
327 // as the widget is destroyed before the up-click can call extend-end
328 char commentTranslations[] = "<Btn3Down>: extend-end() select-start() CommentClick() \n";
331 String xboardResources[] = {
332 "*Error*translations: #override\\n <Key>Return: ErrorPopDown()",
340 gtk_window_present(GTK_WINDOW(shells[BoardWindow]));
343 //---------------------------------------------------------------------------------------------------------
344 // some symbol definitions to provide the proper (= XBoard) context for the code in args.h
347 #define CW_USEDEFAULT (1<<31)
348 #define ICS_TEXT_MENU_SIZE 90
349 #define DEBUG_FILE "xboard.debug"
350 #define SetCurrentDirectory chdir
351 #define GetCurrentDirectory(SIZE, NAME) getcwd(NAME, SIZE)
355 // The option definition and parsing code common to XBoard and WinBoard is collected in this file
358 // front-end part of option handling
360 // [HGM] This platform-dependent table provides the location for storing the color info
361 extern char *crWhite, * crBlack;
365 &appData.whitePieceColor,
366 &appData.blackPieceColor,
367 &appData.lightSquareColor,
368 &appData.darkSquareColor,
369 &appData.highlightSquareColor,
370 &appData.premoveHighlightColor,
371 &appData.lowTimeWarningColor,
382 // [HGM] font: keep a font for each square size, even non-stndard ones
385 Boolean fontIsSet[NUM_FONTS], fontValid[NUM_FONTS][MAX_SIZE];
386 char *fontTable[NUM_FONTS][MAX_SIZE];
389 ParseFont (char *name, int number)
390 { // in XBoard, only 2 of the fonts are currently implemented, and we just copy their name
392 if(sscanf(name, "size%d:", &size)) {
393 // [HGM] font: font is meant for specific boardSize (likely from settings file);
394 // defer processing it until we know if it matches our board size
395 if(!strstr(name, "-*-") && // ignore X-fonts
396 size >= 0 && size<MAX_SIZE) { // for now, fixed limit
397 fontTable[number][size] = strdup(strchr(name, ':')+1);
398 fontValid[number][size] = True;
403 case 0: // CLOCK_FONT
404 appData.clockFont = strdup(name);
406 case 1: // MESSAGE_FONT
407 appData.font = strdup(name);
409 case 2: // COORD_FONT
410 appData.coordFont = strdup(name);
413 appData.icsFont = strdup(name);
416 appData.tagsFont = strdup(name);
419 appData.commentFont = strdup(name);
421 case MOVEHISTORY_FONT:
422 appData.historyFont = strdup(name);
425 appData.gameListFont = strdup(name);
430 fontIsSet[number] = True; // [HGM] font: indicate a font was specified (not from settings file)
435 { // only 2 fonts currently
436 appData.clockFont = strdup(CLOCK_FONT_NAME);
437 appData.coordFont = strdup(COORD_FONT_NAME);
438 appData.font = strdup(DEFAULT_FONT_NAME);
439 appData.icsFont = strdup(CONSOLE_FONT_NAME);
440 appData.tagsFont = strdup(TAGS_FONT_NAME);
441 appData.commentFont = strdup(COMMENT_FONT_NAME);
442 appData.historyFont = strdup(HISTORY_FONT_NAME);
443 appData.gameListFont = strdup(GAMELIST_FONT_NAME);
448 { // no-op, until we identify the code for this already in XBoard and move it here
452 ParseColor (int n, char *name)
453 { // in XBoard, just copy the color-name string
454 if(colorVariable[n] && *name == '#') *(char**)colorVariable[n] = strdup(name);
460 return *(char**)colorVariable[n];
464 ParseTextAttribs (ColorClass cc, char *s)
466 (&appData.colorShout)[cc] = strdup(s);
470 ParseBoardSize (void *addr, char *name)
472 appData.boardSize = strdup(name);
477 { // In XBoard the sound-playing program takes care of obtaining the actual sound
481 SetCommPortDefaults ()
482 { // for now, this is a no-op, as the corresponding option does not exist in XBoard
485 // [HGM] args: these three cases taken out to stay in front-end
487 SaveFontArg (FILE *f, ArgDescriptor *ad)
490 int i, n = (int)(intptr_t)ad->argLoc;
492 case 0: // CLOCK_FONT
493 name = appData.clockFont;
495 case 1: // MESSAGE_FONT
498 case 2: // COORD_FONT
499 name = appData.coordFont;
502 name = appData.icsFont;
505 name = appData.tagsFont;
508 name = appData.commentFont;
510 case MOVEHISTORY_FONT:
511 name = appData.historyFont;
514 name = appData.gameListFont;
519 for(i=0; i<NUM_SIZES; i++) // [HGM] font: current font becomes standard for current size
520 if(sizeDefaults[i].squareSize == squareSize) { // only for standard sizes!
521 fontTable[n][squareSize] = strdup(name);
522 fontValid[n][squareSize] = True;
525 for(i=0; i<MAX_SIZE; i++) if(fontValid[n][i]) // [HGM] font: store all standard fonts
526 fprintf(f, OPTCHAR "%s" SEPCHAR "\"size%d:%s\"\n", ad->argName, i, fontTable[n][i]);
531 { // nothing to do, as the sounds are at all times represented by their text-string names already
535 SaveAttribsArg (FILE *f, ArgDescriptor *ad)
536 { // here the "argLoc" defines a table index. It could have contained the 'ta' pointer itself, though
537 fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, (&appData.colorShout)[(int)(intptr_t)ad->argLoc]);
541 SaveColor (FILE *f, ArgDescriptor *ad)
542 { // in WinBoard the color is an int and has to be converted to text. In X it would be a string already?
543 if(colorVariable[(int)(intptr_t)ad->argLoc])
544 fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, *(char**)colorVariable[(int)(intptr_t)ad->argLoc]);
548 SaveBoardSize (FILE *f, char *name, void *addr)
549 { // wrapper to shield back-end from BoardSize & sizeInfo
550 fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", name, appData.boardSize);
554 ParseCommPortSettings (char *s)
555 { // no such option in XBoard (yet)
561 GetActualPlacement (GtkWidget *shell, WindowPlacement *wp)
565 gtk_widget_get_allocation(shell, &a);
566 gtk_window_get_position(GTK_WINDOW(shell), &a.x, &a.y);
570 wp->height = a.height;
571 //printf("placement: (%d,%d) %dx%d\n", a.x, a.y, a.width, a.height);
572 frameX = 3; frameY = 3; // remember to decide if windows touch
576 GetPlacement (DialogClass dlg, WindowPlacement *wp)
577 { // wrapper to shield back-end from widget type
578 if(shellUp[dlg]) GetActualPlacement(shells[dlg], wp);
583 { // wrapper to shield use of window handles from back-end (make addressible by number?)
584 // In XBoard this will have to wait until awareness of window parameters is implemented
585 GetActualPlacement(shellWidget, &wpMain);
586 if(shellUp[EngOutDlg]) GetActualPlacement(shells[EngOutDlg], &wpEngineOutput);
587 if(shellUp[HistoryDlg]) GetActualPlacement(shells[HistoryDlg], &wpMoveHistory);
588 if(shellUp[EvalGraphDlg]) GetActualPlacement(shells[EvalGraphDlg], &wpEvalGraph);
589 if(shellUp[GameListDlg]) GetActualPlacement(shells[GameListDlg], &wpGameList);
590 if(shellUp[CommentDlg]) GetActualPlacement(shells[CommentDlg], &wpComment);
591 if(shellUp[TagsDlg]) GetActualPlacement(shells[TagsDlg], &wpTags);
592 GetPlacement(ChatDlg, &wpConsole); if(appData.icsActive) wpConsole.visible = shellUp[ChatDlg];
596 PrintCommPortSettings (FILE *f, char *name)
597 { // This option does not exist in XBoard
601 EnsureOnScreen (int *x, int *y, int minX, int minY)
608 { // [HGM] args: allows testing if main window is realized from back-end
609 return DialogExists(BoardWindow);
613 PopUpStartupDialog ()
614 { // start menu not implemented in XBoard
618 ConvertToLine (int argc, char **argv)
620 static char line[128*1024], buf[1024];
624 for(i=1; i<argc; i++)
626 if( (strchr(argv[i], ' ') || strchr(argv[i], '\n') ||strchr(argv[i], '\t') || argv[i][0] == NULLCHAR)
627 && argv[i][0] != '{' )
628 snprintf(buf, sizeof(buf)/sizeof(buf[0]), "{%s} ", argv[i]);
630 snprintf(buf, sizeof(buf)/sizeof(buf[0]), "%s ", argv[i]);
631 strncat(line, buf, 128*1024 - strlen(line) - 1 );
634 line[strlen(line)-1] = NULLCHAR;
638 //--------------------------------------------------------------------------------------------
643 ResizeBoardWindow (int w, int h, int inhibit)
647 // if(clockKludge) return; // ignore as long as clock does not have final height
648 gtk_widget_get_allocation(optList[W_BOARD].handle, &a);
650 gtk_widget_get_allocation(shellWidget, &a);
651 marginW = a.width - bw;
652 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
653 w += marginW + 1; // [HGM] not sure why the +1 is (sometimes) needed...
654 h += marginH + a.height + 1;
655 gtk_window_resize(GTK_WINDOW(shellWidget), w, h);
660 { // dummy, as the GTK code does not make colors in advance
665 InitializeFonts (int clockFontPxlSize, int coordFontPxlSize, int fontPxlSize)
666 { // determine what fonts to use, and create them
668 if(!fontIsSet[CLOCK_FONT] && fontValid[CLOCK_FONT][squareSize])
669 appData.clockFont = fontTable[CLOCK_FONT][squareSize];
670 if(!fontIsSet[MESSAGE_FONT] && fontValid[MESSAGE_FONT][squareSize])
671 appData.font = fontTable[MESSAGE_FONT][squareSize];
672 if(!fontIsSet[COORD_FONT] && fontValid[COORD_FONT][squareSize])
673 appData.coordFont = fontTable[COORD_FONT][squareSize];
674 if(!fontIsSet[CONSOLE_FONT] && fontValid[CONSOLE_FONT][squareSize])
675 appData.icsFont = fontTable[CONSOLE_FONT][squareSize];
676 if(!fontIsSet[EDITTAGS_FONT] && fontValid[EDITTAGS_FONT][squareSize])
677 appData.tagsFont = fontTable[EDITTAGS_FONT][squareSize];
678 if(!fontIsSet[COMMENT_FONT] && fontValid[COMMENT_FONT][squareSize])
679 appData.commentFont = fontTable[COMMENT_FONT][squareSize];
680 if(!fontIsSet[MOVEHISTORY_FONT] && fontValid[MOVEHISTORY_FONT][squareSize])
681 appData.historyFont = fontTable[MOVEHISTORY_FONT][squareSize];
682 if(!fontIsSet[GAMELIST_FONT] && fontValid[GAMELIST_FONT][squareSize])
683 appData.gameListFont = fontTable[GAMELIST_FONT][squareSize];
685 appData.font = InsertPxlSize(appData.font, coordFontPxlSize);
686 appData.clockFont = InsertPxlSize(appData.clockFont, clockFontPxlSize);
687 appData.coordFont = InsertPxlSize(appData.coordFont, coordFontPxlSize);
688 appData.icsFont = InsertPxlSize(appData.icsFont, coordFontPxlSize);
689 appData.tagsFont = InsertPxlSize(appData.tagsFont, coordFontPxlSize);
690 appData.commentFont = InsertPxlSize(appData.commentFont, coordFontPxlSize);
691 appData.historyFont = InsertPxlSize(appData.historyFont, coordFontPxlSize);
692 appData.gameListFont = InsertPxlSize(appData.gameListFont, coordFontPxlSize);
698 if(!fontIsSet[CLOCK_FONT] && fontValid[CLOCK_FONT][squareSize])
699 appData.clockFont = fontTable[CLOCK_FONT][squareSize];
700 if(!fontIsSet[MESSAGE_FONT] && fontValid[MESSAGE_FONT][squareSize])
701 appData.font = fontTable[MESSAGE_FONT][squareSize];
702 if(!fontIsSet[COORD_FONT] && fontValid[COORD_FONT][squareSize])
703 appData.coordFont = fontTable[COORD_FONT][squareSize];
706 appData.font = InsertPxlSize(appData.font, fontPxlSize);
707 appData.clockFont = InsertPxlSize(appData.clockFont, clockFontPxlSize);
708 appData.coordFont = InsertPxlSize(appData.coordFont, coordFontPxlSize);
709 fontSet = CreateFontSet(appData.font);
710 clockFontSet = CreateFontSet(appData.clockFont);
712 /* For the coordFont, use the 0th font of the fontset. */
713 XFontSet coordFontSet = CreateFontSet(appData.coordFont);
714 XFontStruct **font_struct_list;
715 XFontSetExtents *fontSize;
716 char **font_name_list;
717 XFontsOfFontSet(coordFontSet, &font_struct_list, &font_name_list);
718 coordFontID = XLoadFont(xDisplay, font_name_list[0]);
719 coordFontStruct = XQueryFont(xDisplay, coordFontID);
720 fontSize = XExtentsOfFontSet(fontSet); // [HGM] figure out how much vertical space font takes
721 textHeight = fontSize->max_logical_extent.height + 5; // add borderWidth
724 appData.font = FindFont(appData.font, fontPxlSize);
725 appData.clockFont = FindFont(appData.clockFont, clockFontPxlSize);
726 appData.coordFont = FindFont(appData.coordFont, coordFontPxlSize);
727 clockFontID = XLoadFont(xDisplay, appData.clockFont);
728 clockFontStruct = XQueryFont(xDisplay, clockFontID);
729 coordFontID = XLoadFont(xDisplay, appData.coordFont);
730 coordFontStruct = XQueryFont(xDisplay, coordFontID);
731 // textHeight in !NLS mode!
733 countFontID = coordFontID; // [HGM] holdings
734 countFontStruct = coordFontStruct;
736 xdb = XtDatabase(xDisplay);
738 XrmPutLineResource(&xdb, "*international: True");
739 vTo.size = sizeof(XFontSet);
740 vTo.addr = (XtPointer) &fontSet;
741 XrmPutResource(&xdb, "*fontSet", XtRFontSet, &vTo);
743 XrmPutStringResource(&xdb, "*font", appData.font);
754 case ArgInt: p = " N"; break;
755 case ArgString: p = " STR"; break;
756 case ArgBoolean: p = " TF"; break;
757 case ArgSettingsFilename:
758 case ArgBackupSettingsFile:
759 case ArgFilename: p = " FILE"; break;
760 case ArgX: p = " Nx"; break;
761 case ArgY: p = " Ny"; break;
762 case ArgAttribs: p = " TEXTCOL"; break;
763 case ArgColor: p = " COL"; break;
764 case ArgFont: p = " FONT"; break;
765 case ArgBoardSize: p = " SIZE"; break;
766 case ArgFloat: p = " FLOAT"; break;
771 case ArgCommSettings:
783 ArgDescriptor *q, *p = argDescriptors+5;
784 printf("\nXBoard accepts the following options:\n"
785 "(N = integer, TF = true or false, STR = text string, FILE = filename,\n"
786 " Nx, Ny = relative coordinates, COL = color, FONT = X-font spec,\n"
787 " SIZE = board-size spec(s)\n"
788 " Within parentheses are short forms, or options to set to true or false.\n"
789 " Persistent options (saved in the settings file) are marked with *)\n\n");
791 if(p->argType == ArgCommSettings) { p++; continue; } // XBoard has no comm port
792 snprintf(buf+len, MSG_SIZ, "-%s%s", p->argName, PrintArg(p->argType));
793 if(p->save) strcat(buf+len, "*");
794 for(q=p+1; q->argLoc == p->argLoc; q++) {
795 if(q->argName[0] == '-') continue;
796 strcat(buf+len, q == p+1 ? " (" : " ");
797 sprintf(buf+strlen(buf), "-%s%s", q->argName, PrintArg(q->argType));
799 if(q != p+1) strcat(buf+len, ")");
801 if(len > 39) len = 0, printf("%s\n", buf); else while(len < 39) buf[len++] = ' ';
804 if(len) buf[len] = NULLCHAR, printf("%s\n", buf);
808 SlaveResize (Option *opt)
810 static int slaveW, slaveH, w, h;
813 gtk_widget_get_allocation(shells[DummyDlg], &a);
814 w = a.width; h = a.height;
815 gtk_widget_get_allocation(opt->handle, &a);
816 slaveW = w - opt->max; // [HGM] needed to set new shellWidget size when we resize board
817 slaveH = h - a.height + 13;
819 gtk_window_resize(GTK_WINDOW(shells[DummyDlg]), slaveW + opt->max, slaveH + opt->value);
823 LoadIconFile (gchar *svgFilename)
827 snprintf(buf, MSG_SIZ, "%s/%s" IMG, svgDir, svgFilename);
828 return gdk_pixbuf_new_from_file(buf, NULL);
832 static char clickedFile[MSG_SIZ];
836 StartNewXBoard(GtkosxApplication *app, gchar *path, gpointer user_data)
837 { // handler of OSX OpenFile signal, which sends us the filename of clicked file or first argument
838 if(suppress) { // we just started XBoard without arguments
839 strncpy(clickedFile, path, MSG_SIZ); // remember file name, but otherwise ignore
840 } else { // we are running something presumably useful
842 snprintf(buf, MSG_SIZ, "open -n -a \"xboard\" --args \"%s\"", path);
843 system(buf); // start new instance on this file
848 GtkosxApplication *theApp;
852 main (int argc, char **argv)
854 int i, clockFontPxlSize, coordFontPxlSize, fontPxlSize;
855 int boardWidth, w, h; //, boardHeight;
857 int forceMono = False;
859 srandom(time(0)); // [HGM] book: make random truly random
861 setbuf(stdout, NULL);
862 setbuf(stderr, NULL);
865 if(argc > 1 && (!strcmp(argv[1], "-v" ) || !strcmp(argv[1], "--version" ))) {
866 printf("%s version %s\n\n configure options: %s\n", PACKAGE_NAME, PACKAGE_VERSION, CONFIGURE_OPTIONS);
870 if(argc > 1 && !strcmp(argv[1], "--help" )) {
876 gtk_init (&argc, &argv);
878 { // prepare to catch OX OpenFile signal, which will tell us the clicked file
879 char *path = gtkosx_application_get_bundle_path();
881 char *res_path = gtkosx_application_get_resource_path();
882 snprintf(localeDir, MSG_SIZ, "%s/share/locale", res_path); // redefine locale dir for OSX bundle
884 theApp = g_object_new(GTKOSX_TYPE_APPLICATION, NULL);
885 snprintf(masterSettings, MSG_SIZ, "%s/Contents/Resources/etc/xboard.conf", path);
886 snprintf(dataDir, MSG_SIZ, "%s/Contents/Resources/share/xboard", path);
887 snprintf(svgDir, MSG_SIZ, "%s/themes/default", dataDir);
888 suppress = (argc == 1 || argc > 1 && argv[1][00] != '-'); // OSX sends signal even if name was already argv[1]!
889 g_signal_connect(theApp, "NSApplicationOpenFile", G_CALLBACK(StartNewXBoard), NULL);
890 // we must call application ready before we can get the signal,
891 // and supply a (dummy) menu bar before that, to avoid problems with dual apples in it
892 gtkosx_application_set_menu_bar(theApp, GTK_MENU_SHELL(gtk_menu_bar_new()));
893 gtkosx_application_ready(theApp);
894 if(argc == 1) { // called without args: OSX open-file signal might follow
895 static char *fakeArgv[3] = {NULL, clickedFile, NULL};
896 usleep(10000); // wait 10 msec (and hope this is long enough).
897 while(gtk_events_pending())
898 gtk_main_iteration(); // process all events that came in upto now
899 suppress = 0; // future open-file signals should start new instance
900 if(clickedFile[0]) { // we were sent an open-file signal with filename!
901 fakeArgv[0] = argv[0];
902 argc = 2; argv = fakeArgv; // fake that we were called as "xboard filename"
908 if(argc > 1 && !strcmp(argv[1], "--show-config")) { // [HGM] install: called to print config info
909 typedef struct {char *name, *value; } Config;
910 static Config configList[] = {
911 { "Datadir", DATADIR },
912 { "Sysconfdir", SYSCONFDIR },
917 for(i=0; configList[i].name; i++) {
918 if(argc > 2 && strcmp(argv[2], configList[i].name)) continue;
919 if(argc > 2) printf("%s", configList[i].value);
920 else printf("%-12s: %s\n", configList[i].name, configList[i].value);
925 /* set up keyboard accelerators group */
926 GtkAccelerators = gtk_accel_group_new();
928 programName = strrchr(argv[0], '/');
929 if (programName == NULL)
930 programName = argv[0];
935 // if (appData.debugMode) {
936 // fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
939 bindtextdomain(PACKAGE, LOCALEDIR);
940 bind_textdomain_codeset(PACKAGE, "UTF-8"); // needed when creating markup for the clocks
944 appData.boardSize = "";
945 InitAppData(ConvertToLine(argc, argv));
947 if (p == NULL) p = "/tmp";
948 i = strlen(p) + strlen("/.xboardXXXXXx.pgn") + 1;
949 gameCopyFilename = (char*) malloc(i);
950 gamePasteFilename = (char*) malloc(i);
951 snprintf(gameCopyFilename,i, "%s/.xboard%05uc.pgn", p, getpid());
952 snprintf(gamePasteFilename,i, "%s/.xboard%05up.pgn", p, getpid());
954 { // [HGM] initstring: kludge to fix bad bug. expand '\n' characters in init string and computer string.
955 static char buf[MSG_SIZ];
956 snprintf(buf, MSG_SIZ, appData.sysOpen, DATADIR);
957 ASSIGN(appData.sysOpen, buf); // expand %s in -openCommand to DATADIR (usefull for OS X configuring)
958 EscapeExpand(buf, appData.firstInitString);
959 appData.firstInitString = strdup(buf);
960 EscapeExpand(buf, appData.secondInitString);
961 appData.secondInitString = strdup(buf);
962 EscapeExpand(buf, appData.firstComputerString);
963 appData.firstComputerString = strdup(buf);
964 EscapeExpand(buf, appData.secondComputerString);
965 appData.secondComputerString = strdup(buf);
968 if ((chessDir = (char *) getenv("CHESSDIR")) == NULL) {
971 if (chdir(chessDir) != 0) {
972 fprintf(stderr, _("%s: can't cd to CHESSDIR: "), programName);
978 if (appData.debugMode && appData.nameOfDebugFile && strcmp(appData.nameOfDebugFile, "stderr")) {
979 /* [DM] debug info to file [HGM] make the filename a command-line option, and allow it to remain stderr */
980 if ((debugFP = fopen(appData.nameOfDebugFile, "w")) == NULL) {
981 printf(_("Failed to open file '%s'\n"), appData.nameOfDebugFile);
984 setbuf(debugFP, NULL);
988 if (appData.debugMode) {
989 fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
993 /* [HGM,HR] make sure board size is acceptable */
994 if(appData.NrFiles > BOARD_FILES ||
995 appData.NrRanks > BOARD_RANKS )
996 DisplayFatalError(_("Recompile with larger BOARD_RANKS or BOARD_FILES to support this size"), 0, 2);
999 /* This feature does not work; animation needs a rewrite */
1000 appData.highlightDragging = FALSE;
1004 gameInfo.variant = StringToVariant(appData.variant);
1005 InitPosition(FALSE);
1008 * determine size, based on supplied or remembered -size, or screen size
1010 if (isdigit(appData.boardSize[0])) {
1011 i = sscanf(appData.boardSize, "%d,%d,%d,%d,%d,%d,%d", &squareSize,
1012 &lineGap, &clockFontPxlSize, &coordFontPxlSize,
1013 &fontPxlSize, &smallLayout, &tinyLayout);
1015 fprintf(stderr, _("%s: bad boardSize syntax %s\n"),
1016 programName, appData.boardSize);
1020 squareSize = (squareSize*8 + BOARD_WIDTH/2)/BOARD_WIDTH; // scale height
1022 /* Find some defaults; use the nearest known size */
1023 SizeDefaults *szd, *nearest;
1024 int distance = 99999;
1025 nearest = szd = sizeDefaults;
1026 while (szd->name != NULL) {
1027 if (abs(szd->squareSize - squareSize) < distance) {
1029 distance = abs(szd->squareSize - squareSize);
1030 if (distance == 0) break;
1034 if (i < 2) lineGap = nearest->lineGap;
1035 if (i < 3) clockFontPxlSize = nearest->clockFontPxlSize;
1036 if (i < 4) coordFontPxlSize = nearest->coordFontPxlSize;
1037 if (i < 5) fontPxlSize = nearest->fontPxlSize;
1038 if (i < 6) smallLayout = nearest->smallLayout;
1039 if (i < 7) tinyLayout = nearest->tinyLayout;
1042 SizeDefaults *szd = sizeDefaults;
1043 if (*appData.boardSize == NULLCHAR) {
1044 // GdkScreen *screen = gtk_window_get_screen(GTK_WINDOW(mainwindow)); // TODO: this does not work, as no mainwindow yet
1045 GdkScreen *screen = gdk_screen_get_default();
1046 guint screenwidth = gdk_screen_get_width(screen);
1047 guint screenheight = gdk_screen_get_height(screen);
1048 while (screenwidth < (szd->minScreenSize*BOARD_WIDTH + 4)/8 ||
1049 screenheight < (szd->minScreenSize*BOARD_HEIGHT + 4)/8) {
1052 if (szd->name == NULL) szd--;
1053 appData.boardSize = strdup(szd->name); // [HGM] settings: remember name for saving settings
1055 while (szd->name != NULL &&
1056 StrCaseCmp(szd->name, appData.boardSize) != 0) szd++;
1057 if (szd->name == NULL) {
1058 fprintf(stderr, _("%s: unrecognized boardSize name %s\n"),
1059 programName, appData.boardSize);
1063 squareSize = szd->squareSize;
1064 lineGap = szd->lineGap;
1065 clockFontPxlSize = szd->clockFontPxlSize;
1066 coordFontPxlSize = szd->coordFontPxlSize;
1067 fontPxlSize = szd->fontPxlSize;
1068 smallLayout = szd->smallLayout;
1069 tinyLayout = szd->tinyLayout;
1070 // [HGM] font: use defaults from settings file if available and not overruled
1073 defaultLineGap = lineGap;
1074 if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
1076 /* [HR] height treated separately (hacked) */
1077 boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
1078 // boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
1081 * Determine what fonts to use.
1083 InitializeFonts((2*clockFontPxlSize+1)/3, coordFontPxlSize, fontPxlSize);
1086 * Detect if there are not enough colors available and adapt.
1089 if (DefaultDepth(xDisplay, xScreen) <= 2) {
1090 appData.monoMode = True;
1094 forceMono = MakeColors();
1097 fprintf(stderr, _("%s: too few colors available; trying monochrome mode\n"),
1099 appData.monoMode = True;
1102 ParseIcsTextColors();
1108 layoutName = "tinyLayout";
1109 } else if (smallLayout) {
1110 layoutName = "smallLayout";
1112 layoutName = "normalLayout";
1115 if(appData.logoSize) appData.logoSize = boardWidth/4-3;
1116 wpMain.width = -1; // prevent popup sizes window
1117 optList = BoardPopUp(squareSize, lineGap, (void*)
1127 InitDrawingHandle(optList + W_BOARD);
1128 shellWidget = shells[BoardWindow];
1129 currBoard = &optList[W_BOARD];
1130 boardWidget = optList[W_BOARD].handle;
1131 menuBarWidget = optList[W_MENU].handle;
1132 dropMenu = optList[W_DROP].handle;
1133 titleWidget = optList[optList[W_TITLE].type != -1 ? W_TITLE : W_SMALL].handle;
1135 formWidget = XtParent(boardWidget);
1136 XtSetArg(args[0], XtNbackground, &timerBackgroundPixel);
1137 XtSetArg(args[1], XtNforeground, &timerForegroundPixel);
1138 XtGetValues(optList[W_WHITE].handle, args, 2);
1139 if (appData.showButtonBar) { // can't we use timer pixels for this? (Or better yet, just black & white?)
1140 XtSetArg(args[0], XtNbackground, &buttonBackgroundPixel);
1141 XtSetArg(args[1], XtNforeground, &buttonForegroundPixel);
1142 XtGetValues(optList[W_PAUSE].handle, args, 2);
1146 // [HGM] it seems the layout code ends here, but perhaps the color stuff is size independent and would
1147 // not need to go into InitDrawingSizes().
1151 // add accelerators to main shell
1152 gtk_window_add_accel_group(GTK_WINDOW(shellWidget), GtkAccelerators);
1155 * Create an icon. (Use two icons, to indicate whther it is white's or black's turn.)
1157 WhiteIcon = LoadIconFile("icon_white");
1158 BlackIcon = LoadIconFile("icon_black");
1159 SetClockIcon(0); // sets white icon
1163 * Create a cursor for the board widget.
1166 window_attributes.cursor = XCreateFontCursor(xDisplay, XC_hand2);
1167 XChangeWindowAttributes(xDisplay, xBoardWindow,
1168 CWCursor, &window_attributes);
1172 * Inhibit shell resizing.
1175 shellArgs[0].value = (XtArgVal) &w;
1176 shellArgs[1].value = (XtArgVal) &h;
1177 XtGetValues(shellWidget, shellArgs, 2);
1178 shellArgs[4].value = shellArgs[2].value = w;
1179 shellArgs[5].value = shellArgs[3].value = h;
1180 // XtSetValues(shellWidget, &shellArgs[2], 4);
1183 // Note: We cannot do sensible sizing here, because the height of the clock widget is not yet known
1184 // It wil only become known asynchronously, when we first write a string into it.
1185 // This will then change the clock widget height, which triggers resizing the top-level window
1186 // and a configure event. Only then can we know the total height of the top-level window,
1187 // and calculate the height we need. The clockKludge flag suppresses all resizing until
1188 // that moment comes, after which the configure event-handler handles it through a (delayed) DragProg.
1191 gtk_widget_get_allocation(shells[BoardWindow], &a);
1192 w = a.width; h = a.height;
1193 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1194 clockKludge = hc = a.height;
1195 gtk_widget_get_allocation(boardWidget, &a);
1196 // marginW = w - boardWidth; // [HGM] needed to set new shellWidget size when we resize board
1197 marginH = h - a.height - hc; // subtract current clock height, so it can be added back dynamically
1203 if(appData.logoSize)
1204 { // locate and read user logo
1205 char buf[MSG_SIZ], name[MSG_SIZ];
1206 snprintf(name, MSG_SIZ, "/home/%s", UserName());
1207 if(!FindLogo(name, ".logo", buf))
1208 FindLogo(appData.logoDir, name + 6, buf);
1209 ASSIGN(userLogo, buf);
1212 if (appData.animate || appData.animateDragging)
1215 g_signal_connect(shells[BoardWindow], "key-press-event", G_CALLBACK(KeyPressProc), NULL);
1216 g_signal_connect(shells[BoardWindow], "configure-event", G_CALLBACK(EventProc), NULL);
1218 /* [AS] Restore layout */
1219 if( wpMoveHistory.visible ) {
1223 if( wpEvalGraph.visible )
1228 if( wpEngineOutput.visible ) {
1229 EngineOutputPopUp();
1232 if( wpConsole.visible && appData.icsActive ) {
1237 gameInfo.boardWidth = 0; // [HGM] pieces: kludge to ensure InitPosition() calls InitDrawingSizes()
1242 if (errorExitStatus == -1) {
1243 if (appData.icsActive) {
1244 /* We now wait until we see "login:" from the ICS before
1245 sending the logon script (problems with timestamp otherwise) */
1246 /*ICSInitScript();*/
1247 if (appData.icsInputBox) ICSInputBoxPopUp();
1251 signal(SIGWINCH, TermSizeSigHandler);
1253 signal(SIGINT, IntSigHandler);
1254 signal(SIGTERM, IntSigHandler);
1255 if (*appData.cmailGameName != NULLCHAR) {
1256 signal(SIGUSR1, CmailSigHandler);
1261 // XtSetKeyboardFocus(shellWidget, formWidget);
1263 XSetInputFocus(xDisplay, XtWindow(formWidget), RevertToPointerRoot, CurrentTime);
1266 /* check for GTK events and process them */
1269 gtk_main_iteration();
1272 if (appData.debugMode) fclose(debugFP); // [DM] debug
1279 while(gtk_events_pending()) gtk_main_iteration();
1283 TermSizeSigHandler (int sig)
1289 IntSigHandler (int sig)
1295 CmailSigHandler (int sig)
1300 signal(SIGUSR1, SIG_IGN); /* suspend handler */
1302 /* Activate call-back function CmailSigHandlerCallBack() */
1303 OutputToProcess(cmailPR, (char *)(&dummy), sizeof(int), &error);
1305 signal(SIGUSR1, CmailSigHandler); /* re-activate handler */
1309 CmailSigHandlerCallBack (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1312 ReloadCmailMsgEvent(TRUE); /* Reload cmail msg */
1314 /**** end signal code ****/
1317 #define Abs(n) ((n)<0 ? -(n) : (n))
1320 InsertPxlSize (char *pattern, int targetPxlSize)
1323 snprintf(buf, MSG_SIZ, pattern, targetPxlSize); // pattern is something like "Sans Bold %d"
1330 InsertPxlSize (char *pattern, int targetPxlSize)
1332 char *base_fnt_lst, strInt[12], *p, *q;
1333 int alternatives, i, len, strIntLen;
1336 * Replace the "*" (if present) in the pixel-size slot of each
1337 * alternative with the targetPxlSize.
1341 while ((p = strchr(p, ',')) != NULL) {
1345 snprintf(strInt, sizeof(strInt), "%d", targetPxlSize);
1346 strIntLen = strlen(strInt);
1347 base_fnt_lst = calloc(1, strlen(pattern) + strIntLen * alternatives + 1);
1351 while (alternatives--) {
1352 char *comma = strchr(p, ',');
1353 for (i=0; i<14; i++) {
1354 char *hyphen = strchr(p, '-');
1356 if (comma && hyphen > comma) break;
1357 len = hyphen + 1 - p;
1358 if (i == 7 && *p == '*' && len == 2) {
1360 memcpy(q, strInt, strIntLen);
1370 len = comma + 1 - p;
1377 return base_fnt_lst;
1383 CreateFontSet (char *base_fnt_lst)
1386 char **missing_list;
1390 fntSet = XCreateFontSet(xDisplay, base_fnt_lst,
1391 &missing_list, &missing_count, &def_string);
1392 if (appData.debugMode) {
1394 XFontStruct **font_struct_list;
1395 char **font_name_list;
1396 fprintf(debugFP, "Requested font set for list %s\n", base_fnt_lst);
1398 fprintf(debugFP, " got list %s, locale %s\n",
1399 XBaseFontNameListOfFontSet(fntSet),
1400 XLocaleOfFontSet(fntSet));
1401 count = XFontsOfFontSet(fntSet, &font_struct_list, &font_name_list);
1402 for (i = 0; i < count; i++) {
1403 fprintf(debugFP, " got charset %s\n", font_name_list[i]);
1406 for (i = 0; i < missing_count; i++) {
1407 fprintf(debugFP, " missing charset %s\n", missing_list[i]);
1410 if (fntSet == NULL) {
1411 fprintf(stderr, _("Unable to create font set for %s.\n"), base_fnt_lst);
1417 #else // not ENABLE_NLS
1419 * Find a font that matches "pattern" that is as close as
1420 * possible to the targetPxlSize. Prefer fonts that are k
1421 * pixels smaller to fonts that are k pixels larger. The
1422 * pattern must be in the X Consortium standard format,
1423 * e.g. "-*-helvetica-bold-r-normal--*-*-*-*-*-*-*-*".
1424 * The return value should be freed with XtFree when no
1429 FindFont (char *pattern, int targetPxlSize)
1431 char **fonts, *p, *best, *scalable, *scalableTail;
1432 int i, j, nfonts, minerr, err, pxlSize;
1434 fonts = XListFonts(xDisplay, pattern, 999999, &nfonts);
1436 fprintf(stderr, _("%s: no fonts match pattern %s\n"),
1437 programName, pattern);
1444 for (i=0; i<nfonts; i++) {
1447 if (*p != '-') continue;
1449 if (*p == NULLCHAR) break;
1450 if (*p++ == '-') j++;
1452 if (j < 7) continue;
1455 scalable = fonts[i];
1458 err = pxlSize - targetPxlSize;
1459 if (Abs(err) < Abs(minerr) ||
1460 (minerr > 0 && err < 0 && -err == minerr)) {
1466 if (scalable && Abs(minerr) > appData.fontSizeTolerance) {
1467 /* If the error is too big and there is a scalable font,
1468 use the scalable font. */
1469 int headlen = scalableTail - scalable;
1470 p = (char *) XtMalloc(strlen(scalable) + 10);
1471 while (isdigit(*scalableTail)) scalableTail++;
1472 sprintf(p, "%.*s%d%s", headlen, scalable, targetPxlSize, scalableTail);
1474 p = (char *) XtMalloc(strlen(best) + 2);
1475 safeStrCpy(p, best, strlen(best)+1 );
1477 if (appData.debugMode) {
1478 fprintf(debugFP, "resolved %s at pixel size %d\n to %s\n",
1479 pattern, targetPxlSize, p);
1481 XFreeFontNames(fonts);
1488 MarkMenuItem (char *menuRef, int state)
1490 MenuItem *item = MenuNameToItem(menuRef);
1492 if(item && item->handle) {
1493 ((GtkCheckMenuItem *) (item->handle))->active = state;
1499 EnableNamedMenuItem (char *menuRef, int state)
1501 MenuItem *item = MenuNameToItem(menuRef);
1503 if(item && item->handle) gtk_widget_set_sensitive(item->handle, state);
1508 EnableButtonBar (int state)
1511 XtSetSensitive(optList[W_BUTTON].handle, state);
1517 SetMenuEnables (Enables *enab)
1519 while (enab->name != NULL) {
1520 EnableNamedMenuItem(enab->name, enab->value);
1525 gboolean KeyPressProc(window, eventkey, data)
1527 GdkEventKey *eventkey;
1531 MoveTypeInProc(eventkey); // pop up for typed in moves
1534 /* check for other key values */
1535 switch(eventkey->keyval) {
1547 KeyBindingProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1548 { // [HGM] new method of key binding: specify MenuItem(FlipView) in stead of FlipViewProc in translation string
1550 if(*nprms == 0) return;
1551 item = MenuNameToItem(prms[0]);
1552 if(item) ((MenuProc *) item->proc) ();
1566 for (i=0; i<sizeof(dmEnables)/sizeof(DropMenuEnables); i++) {
1567 entry = XtNameToWidget(dropMenu, dmEnables[i].widget);
1568 p = strchr(gameMode == IcsPlayingWhite ? white_holding : black_holding,
1569 dmEnables[i].piece);
1570 XtSetSensitive(entry, p != NULL || !appData.testLegality
1571 /*!!temp:*/ || (gameInfo.variant == VariantCrazyhouse
1572 && !appData.icsActive));
1574 while (p && *p++ == dmEnables[i].piece) count++;
1575 snprintf(label, sizeof(label), "%s %d", dmEnables[i].widget, count);
1577 XtSetArg(args[j], XtNlabel, label); j++;
1578 XtSetValues(entry, args, j);
1584 do_flash_delay (unsigned long msec)
1590 FlashDelay (int flash_delay)
1592 if(flash_delay) do_flash_delay(flash_delay);
1596 Fraction (int x, int start, int stop)
1598 double f = ((double) x - start)/(stop - start);
1599 if(f > 1.) f = 1.; else if(f < 0.) f = 0.;
1603 static WindowPlacement wpNew;
1606 CoDrag (GtkWidget *sh, WindowPlacement *wp)
1608 int touch=0, fudge = 4, f = 3;
1609 GetActualPlacement(sh, wp);
1610 if(abs(wpMain.x + wpMain.width + 2*frameX - f - wp->x) < fudge) touch = 1; else // right touch
1611 if(abs(wp->x + wp->width + 2*frameX - f - wpMain.x) < fudge) touch = 2; else // left touch
1612 if(abs(wpMain.y + wpMain.height + frameX - f + frameY - wp->y) < fudge) touch = 3; else // bottom touch
1613 if(abs(wp->y + wp->height + frameX + frameY - f - wpMain.y) < fudge) touch = 4; // top touch
1614 //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);
1615 if(!touch ) return; // only windows that touch co-move
1616 if(touch < 3 && wpNew.height != wpMain.height) { // left or right and height changed
1617 int heightInc = wpNew.height - wpMain.height;
1618 double fracTop = Fraction(wp->y, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1619 double fracBot = Fraction(wp->y + wp->height + frameX + frameY + 1, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1620 wp->y += fracTop * heightInc;
1621 heightInc = (int) (fracBot * heightInc) - (int) (fracTop * heightInc);
1623 if(heightInc) XtSetArg(args[j], XtNheight, wp->height + heightInc), j++;
1625 wp->height += heightInc;
1626 } else if(touch > 2 && wpNew.width != wpMain.width) { // top or bottom and width changed
1627 int widthInc = wpNew.width - wpMain.width;
1628 double fracLeft = Fraction(wp->x, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1629 double fracRght = Fraction(wp->x + wp->width + 2*frameX + 1, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1630 wp->y += fracLeft * widthInc;
1631 widthInc = (int) (fracRght * widthInc) - (int) (fracLeft * widthInc);
1633 if(widthInc) XtSetArg(args[j], XtNwidth, wp->width + widthInc), j++;
1635 wp->width += widthInc;
1637 wp->x += wpNew.x - wpMain.x;
1638 wp->y += wpNew.y - wpMain.y;
1639 if(touch == 1) wp->x += wpNew.width - wpMain.width; else
1640 if(touch == 3) wp->y += wpNew.height - wpMain.height;
1642 XtSetArg(args[j], XtNx, wp->x); j++;
1643 XtSetArg(args[j], XtNy, wp->y); j++;
1644 XtSetValues(sh, args, j);
1646 gtk_window_move(GTK_WINDOW(sh), wp->x, wp->y);
1647 //printf("moved to (%d,%d)\n", wp->x, wp->y);
1648 gtk_window_resize(GTK_WINDOW(sh), wp->width, wp->height);
1652 ReSize (WindowPlacement *wp)
1655 int sqx, sqy, w, h, hc, lg = lineGap;
1656 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1657 hc = a.height; // clock height can depend on single / double line clock text!
1658 if(clockKludge && hc != clockKludge) wp->height += hc - clockKludge, clockKludge = 0;
1659 wpMain.height = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + marginH + hc;
1660 if(wp->width == wpMain.width && wp->height == wpMain.height) return; // not sized
1661 sqx = (wp->width - lg - marginW) / BOARD_WIDTH - lg;
1662 sqy = (wp->height - lg - marginH - hc) / BOARD_HEIGHT - lg;
1663 if(sqy < sqx) sqx = sqy;
1664 if(sqx < 20) return;
1665 if(appData.overrideLineGap < 0) { // do second iteration with adjusted lineGap
1667 lg = lineGap = sqx < 37 ? 1 : sqx < 59 ? 2 : sqx < 116 ? 3 : 4;
1668 sqx = (wp->width - lg - marginW) / BOARD_WIDTH - lg;
1669 sqy = (wp->height - lg - marginH - hc) / BOARD_HEIGHT - lg;
1670 if(sqy < sqx) sqx = sqy;
1671 lg = sqx < 37 ? 1 : sqx < 59 ? 2 : sqx < 116 ? 3 : 4;
1672 if(sqx == oldSqx + 1 && lg == lineGap + 1) sqx = oldSqx, squareSize = 0; // prevent oscillations, force resize by kludge
1674 if(sqx != squareSize) {
1675 squareSize = sqx; // adopt new square size
1676 CreatePNGPieces(); // make newly scaled pieces
1677 InitDrawingSizes(0, 0); // creates grid etc.
1678 } else ResizeBoardWindow(BOARD_WIDTH * (squareSize + lineGap) + lineGap, BOARD_HEIGHT * (squareSize + lineGap) + lineGap, 0);
1679 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
1680 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
1681 if(optList[W_BOARD].max > w) optList[W_BOARD].max = w;
1682 if(optList[W_BOARD].value > h) optList[W_BOARD].value = h;
1685 static guint delayedDragTag = 0;
1694 // GetActualPlacement(shellWidget, &wpNew);
1695 if(wpNew.x == wpMain.x && wpNew.y == wpMain.y && // not moved
1696 wpNew.width == wpMain.width && wpNew.height == wpMain.height) { // not sized
1697 busy = 0; return; // false alarm
1700 if(appData.useStickyWindows) {
1701 if(shellUp[EngOutDlg]) CoDrag(shells[EngOutDlg], &wpEngineOutput);
1702 if(shellUp[HistoryDlg]) CoDrag(shells[HistoryDlg], &wpMoveHistory);
1703 if(shellUp[EvalGraphDlg]) CoDrag(shells[EvalGraphDlg], &wpEvalGraph);
1704 if(shellUp[GameListDlg]) CoDrag(shells[GameListDlg], &wpGameList);
1705 if(shellUp[ChatDlg]) CoDrag(shells[ChatDlg], &wpConsole);
1708 DrawPosition(True, NULL);
1709 if(delayedDragTag) g_source_remove(delayedDragTag);
1710 delayedDragTag = 0; // now drag executed, make sure next DelayedDrag will not cancel timer event (which could now be used by other)
1717 //printf("old timr = %d\n", delayedDragTag);
1718 if(delayedDragTag) g_source_remove(delayedDragTag);
1719 delayedDragTag = g_timeout_add( 200, (GSourceFunc) DragProc, NULL);
1720 //printf("new timr = %d\n", delayedDragTag);
1724 EventProc (GtkWidget *widget, GdkEvent *event, gpointer g)
1726 //printf("event proc (%d,%d) %dx%d\n", event->configure.x, event->configure.y, event->configure.width, event->configure.height);
1728 wpNew.x = event->configure.x;
1729 wpNew.y = event->configure.y;
1730 wpNew.width = event->configure.width;
1731 wpNew.height = event->configure.height;
1732 DelayedDrag(); // as long as events keep coming in faster than 50 msec, they destroy each other
1738 /* Disable all user input other than deleting the window */
1739 static int frozen = 0;
1745 /* Grab by a widget that doesn't accept input */
1746 gtk_grab_add(optList[W_MESSG].handle);
1750 /* Undo a FreezeUI */
1754 if (!frozen) return;
1755 gtk_grab_remove(optList[W_MESSG].handle);
1762 static int oldPausing = FALSE;
1763 static GameMode oldmode = (GameMode) -1;
1765 if (!boardWidget) return;
1767 if (pausing != oldPausing) {
1768 oldPausing = pausing;
1769 MarkMenuItem("Mode.Pause", pausing);
1771 if (appData.showButtonBar) {
1772 /* Always toggle, don't set. Previous code messes up when
1773 invoked while the button is pressed, as releasing it
1774 toggles the state again. */
1776 gdk_color_parse( pausing ? "#808080" : "#F0F0F0", &color );
1777 gtk_widget_modify_bg ( GTK_WIDGET(optList[W_PAUSE].handle), GTK_STATE_NORMAL, &color );
1781 wname = ModeToWidgetName(oldmode);
1782 if (wname != NULL) {
1783 MarkMenuItem(wname, False);
1785 wname = ModeToWidgetName(gameMode);
1786 if (wname != NULL) {
1787 MarkMenuItem(wname, True);
1790 MarkMenuItem("Mode.MachineMatch", matchMode && matchGame < appData.matchGames);
1792 /* Maybe all the enables should be handled here, not just this one */
1793 EnableNamedMenuItem("Mode.Training", gameMode == Training || gameMode == PlayFromGameFile);
1795 DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
1800 * Button/menu procedures
1803 void CopyFileToClipboard(gchar *filename)
1805 gchar *selection_tmp;
1809 FILE* f = fopen(filename, "r");
1812 if (f == NULL) return;
1816 selection_tmp = g_try_malloc(len + 1);
1817 if (selection_tmp == NULL) {
1818 printf("Malloc failed in CopyFileToClipboard\n");
1821 count = fread(selection_tmp, 1, len, f);
1824 g_free(selection_tmp);
1827 selection_tmp[len] = NULLCHAR; // file is now in selection_tmp
1829 // copy selection_tmp to clipboard
1830 GdkDisplay *gdisp = gdk_display_get_default();
1832 g_free(selection_tmp);
1835 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1836 gtk_clipboard_set_text(cb, selection_tmp, -1);
1837 g_free(selection_tmp);
1841 CopySomething (char *src)
1843 GdkDisplay *gdisp = gdk_display_get_default();
1845 if(!src) { CopyFileToClipboard(gameCopyFilename); return; }
1846 if (gdisp == NULL) return;
1847 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1848 gtk_clipboard_set_text(cb, src, -1);
1852 PastePositionProc ()
1854 GdkDisplay *gdisp = gdk_display_get_default();
1858 if (gdisp == NULL) return;
1859 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1860 fenstr = gtk_clipboard_wait_for_text(cb);
1861 if (fenstr==NULL) return; // nothing had been selected to copy
1862 EditPositionPasteFEN(fenstr);
1874 // get game from clipboard
1875 GdkDisplay *gdisp = gdk_display_get_default();
1876 if (gdisp == NULL) return;
1877 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1878 text = gtk_clipboard_wait_for_text(cb);
1879 if (text == NULL) return; // nothing to paste
1882 // write to temp file
1883 if (text == NULL || len == 0) {
1884 return; //nothing to paste
1886 f = fopen(gamePasteFilename, "w");
1888 DisplayError(_("Can't open temp file"), errno);
1891 fwrite(text, 1, len, f);
1895 LoadGameFromFile(gamePasteFilename, 0, gamePasteFilename, TRUE);
1902 QuitWrapper (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1908 void MoveTypeInProc(eventkey)
1909 GdkEventKey *eventkey;
1913 // ingnore if ctrl, alt, or meta is pressed
1914 if (eventkey->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_META_MASK)) {
1918 buf[0]=eventkey->keyval;
1920 if (eventkey->keyval > 32 && eventkey->keyval < 256)
1921 ConsoleAutoPopUp (buf);
1926 TempBackwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1928 if (!TempBackwardActive) {
1929 TempBackwardActive = True;
1935 TempForwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1937 /* Check to see if triggered by a key release event for a repeating key.
1938 * If so the next queued event will be a key press of the same key at the same time */
1939 if (XEventsQueued(xDisplay, QueuedAfterReading)) {
1941 XPeekEvent(xDisplay, &next);
1942 if (next.type == KeyPress && next.xkey.time == event->xkey.time &&
1943 next.xkey.keycode == event->xkey.keycode)
1947 TempBackwardActive = False;
1953 { // called from menu
1956 snprintf(buf, MSG_SIZ, "%s ./man.command", appData.sysOpen);
1959 system("xterm -e man xboard &");
1964 SetWindowTitle (char *text, char *title, char *icon)
1969 if (appData.titleInWindow) {
1971 XtSetArg(args[i], XtNlabel, text); i++;
1972 XtSetValues(titleWidget, args, i);
1975 XtSetArg(args[i], XtNiconName, (XtArgVal) icon); i++;
1976 XtSetArg(args[i], XtNtitle, (XtArgVal) title); i++;
1977 XtSetValues(shellWidget, args, i);
1978 XSync(xDisplay, False);
1980 if (appData.titleInWindow) {
1981 SetWidgetLabel(titleWidget, text);
1983 gtk_window_set_title (GTK_WINDOW(shells[BoardWindow]), title);
1988 DisplayIcsInteractionTitle (String message)
1991 if (oldICSInteractionTitle == NULL) {
1992 /* Magic to find the old window title, adapted from vim */
1993 char *wina = getenv("WINDOWID");
1995 Window win = (Window) atoi(wina);
1996 Window root, parent, *children;
1997 unsigned int nchildren;
1998 int (*oldHandler)() = XSetErrorHandler(NullXErrorCheck);
2000 if (XFetchName(xDisplay, win, &oldICSInteractionTitle)) break;
2001 if (!XQueryTree(xDisplay, win, &root, &parent,
2002 &children, &nchildren)) break;
2003 if (children) XFree((void *)children);
2004 if (parent == root || parent == 0) break;
2007 XSetErrorHandler(oldHandler);
2009 if (oldICSInteractionTitle == NULL) {
2010 oldICSInteractionTitle = "xterm";
2013 printf("\033]0;%s\007", message);
2020 DisplayTimerLabel (Option *opt, char *color, long timer, int highlight)
2022 GtkWidget *w = (GtkWidget *) opt->handle;
2029 strcpy(bgcolor, "black");
2030 strcpy(fgcolor, "white");
2032 strcpy(bgcolor, "white");
2033 strcpy(fgcolor, "black");
2036 appData.lowTimeWarning &&
2037 (timer / 1000) < appData.icsAlarmTime) {
2038 strcpy(fgcolor, appData.lowTimeWarningColor);
2041 gdk_color_parse( bgcolor, &col );
2042 gtk_widget_modify_bg(gtk_widget_get_parent(opt->handle), GTK_STATE_NORMAL, &col);
2044 if (appData.clockMode) {
2045 markup = g_markup_printf_escaped("<span font=\"%s\" background=\"%s\" foreground=\"%s\">%s:%s%s</span>", appData.clockFont,
2046 bgcolor, fgcolor, color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
2047 // markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s:%s%s</span>",
2048 // bgcolor, fgcolor, color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
2050 markup = g_markup_printf_escaped("<span font=\"%s\" background=\"%s\" foreground=\"%s\">%s </span>", appData.clockFont,
2051 bgcolor, fgcolor, color);
2052 // markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s </span>",
2053 // bgcolor, fgcolor, color);
2055 gtk_label_set_markup(GTK_LABEL(w), markup);
2059 static GdkPixbuf **clockIcons[] = { &WhiteIcon, &BlackIcon };
2062 SetClockIcon (int color)
2064 GdkPixbuf *pm = *clockIcons[color];
2065 if (mainwindowIcon != pm) {
2066 mainwindowIcon = pm;
2068 gtkosx_application_set_dock_icon_pixbuf(theApp, mainwindowIcon);
2070 gtk_window_set_icon(GTK_WINDOW(shellWidget), mainwindowIcon);
2075 #define INPUT_SOURCE_BUF_SIZE 8192
2084 char buf[INPUT_SOURCE_BUF_SIZE];
2089 DoInputCallback(io, cond, data)
2094 /* read input from one of the input source (for example a chess program, ICS, etc).
2095 * and call a function that will handle the input
2102 /* All information (callback function, file descriptor, etc) is
2103 * saved in an InputSource structure
2105 InputSource *is = (InputSource *) data;
2107 if (is->lineByLine) {
2108 count = read(is->fd, is->unused,
2109 INPUT_SOURCE_BUF_SIZE - (is->unused - is->buf));
2111 if(count == 0 && is->kind == CPReal && shells[ChatDlg]) { // [HGM] absence of terminal is no error if ICS Console present
2112 RemoveInputSource(is); // cease reading stdin
2113 stdoutClosed = TRUE; // suppress future output
2116 (is->func)(is, is->closure, is->buf, count, count ? errno : 0);
2119 is->unused += count;
2121 /* break input into lines and call the callback function on each
2124 while (p < is->unused) {
2125 q = memchr(p, '\n', is->unused - p);
2126 if (q == NULL) break;
2128 (is->func)(is, is->closure, p, q - p, 0);
2131 /* remember not yet used part of the buffer */
2133 while (p < is->unused) {
2138 /* read maximum length of input buffer and send the whole buffer
2139 * to the callback function
2141 count = read(is->fd, is->buf, INPUT_SOURCE_BUF_SIZE);
2146 (is->func)(is, is->closure, is->buf, count, error);
2148 return True; // Must return true or the watch will be removed
2151 InputSourceRef AddInputSource(pr, lineByLine, func, closure)
2158 GIOChannel *channel;
2159 ChildProc *cp = (ChildProc *) pr;
2161 is = (InputSource *) calloc(1, sizeof(InputSource));
2162 is->lineByLine = lineByLine;
2166 is->fd = fileno(stdin);
2168 is->kind = cp->kind;
2169 is->fd = cp->fdFrom;
2172 is->unused = is->buf;
2176 /* GTK-TODO: will this work on windows?*/
2178 channel = g_io_channel_unix_new(is->fd);
2179 g_io_channel_set_close_on_unref (channel, TRUE);
2180 is->sid = g_io_add_watch(channel, G_IO_IN,(GIOFunc) DoInputCallback, is);
2182 is->closure = closure;
2183 return (InputSourceRef) is;
2188 RemoveInputSource(isr)
2191 InputSource *is = (InputSource *) isr;
2193 if (is->sid == 0) return;
2194 g_source_remove(is->sid);
2201 static Boolean frameWaiting;
2204 FrameAlarm (int sig)
2206 frameWaiting = False;
2207 /* In case System-V style signals. Needed?? */
2208 signal(SIGALRM, FrameAlarm);
2212 FrameDelay (int time)
2214 struct itimerval delay;
2217 frameWaiting = True;
2218 signal(SIGALRM, FrameAlarm);
2219 delay.it_interval.tv_sec =
2220 delay.it_value.tv_sec = time / 1000;
2221 delay.it_interval.tv_usec =
2222 delay.it_value.tv_usec = (time % 1000) * 1000;
2223 setitimer(ITIMER_REAL, &delay, NULL);
2224 while (frameWaiting) pause();
2225 delay.it_interval.tv_sec = delay.it_value.tv_sec = 0;
2226 delay.it_interval.tv_usec = delay.it_value.tv_usec = 0;
2227 setitimer(ITIMER_REAL, &delay, NULL);
2234 FrameDelay (int time)
2237 XSync(xDisplay, False);
2239 // gtk_main_iteration_do(False);
2242 usleep(time * 1000);
2248 FindLogo (char *place, char *name, char *buf)
2249 { // check if file exists in given place
2251 if(!place) return 0;
2252 snprintf(buf, MSG_SIZ, "%s/%s.png", place, name);
2253 if(*place && strcmp(place, ".") && (f = fopen(buf, "r")) ) {
2261 LoadLogo (ChessProgramState *cps, int n, Boolean ics)
2263 char buf[MSG_SIZ], *logoName = buf;
2264 if(appData.logo[n][0]) {
2265 logoName = appData.logo[n];
2266 } else if(appData.autoLogo) {
2267 if(ics) { // [HGM] logo: in ICS mode second can be used for ICS
2268 sprintf(buf, "%s/%s.png", appData.logoDir, appData.icsHost);
2269 } else { // engine; cascade
2270 if(!FindLogo(appData.logoDir, cps->tidy, buf) && // first try user log folder
2271 !FindLogo(appData.directory[n], "logo", buf) && // then engine directory
2272 !FindLogo("/usr/local/share/games/plugins/logos", cps->tidy, buf) ) // then system folders
2273 FindLogo("/usr/share/games/plugins/logos", cps->tidy, buf);
2277 { ASSIGN(cps->programLogo, logoName); }
2281 UpdateLogos (int displ)
2283 if(optList[W_WHITE-1].handle == NULL) return;
2284 LoadLogo(&first, 0, 0);
2285 LoadLogo(&second, 1, appData.icsActive);
2286 if(displ) DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
2290 void FileNamePopUpWrapper(label, def, filter, proc, pathFlag, openMode, name, fp)
2301 GtkFileFilter *gtkfilter;
2302 GtkFileFilter *gtkfilter_all;
2304 char fileext[10] = "";
2305 char *result = NULL;
2308 /* make a copy of the filter string, so that strtok can work with it*/
2309 cp = strdup(filter);
2311 /* add filters for file extensions */
2312 gtkfilter = gtk_file_filter_new();
2313 gtkfilter_all = gtk_file_filter_new();
2315 /* one filter to show everything */
2316 gtk_file_filter_add_pattern(gtkfilter_all, "*.*");
2317 gtk_file_filter_set_name (gtkfilter_all, "All Files");
2319 /* add filter if present */
2320 result = strtok(cp, space);
2321 while( result != NULL ) {
2322 snprintf(fileext,10,"*%s",result);
2323 result = strtok( NULL, space );
2324 gtk_file_filter_add_pattern(gtkfilter, fileext);
2327 /* second filter to only show what's useful */
2328 gtk_file_filter_set_name (gtkfilter,filter);
2330 if (openMode[0] == 'r')
2332 dialog = gtk_file_chooser_dialog_new (label,
2334 GTK_FILE_CHOOSER_ACTION_OPEN,
2335 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2336 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
2341 dialog = gtk_file_chooser_dialog_new (label,
2343 GTK_FILE_CHOOSER_ACTION_SAVE,
2344 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2345 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
2347 /* add filename suggestions */
2348 if (strlen(def) > 0 )
2349 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), def);
2351 //gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER (dialog),TRUE);
2355 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter_all);
2356 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2357 /* activate filter */
2358 gtk_file_chooser_set_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2360 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
2365 filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
2368 f = fopen(filename, openMode);
2371 DisplayError(_("Failed to open file"), errno);
2375 /* TODO add indec */
2377 ASSIGN(*name, filename);
2378 ScheduleDelayedEvent(DelayedLoad, 50);
2383 gtk_widget_destroy (dialog);