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).
175 // redefine some defaults
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];
192 #define usleep(t) _sleep2(((t)+500)/1000)
196 # define _(s) gettext (s)
197 # define N_(s) gettext_noop (s)
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));
208 char *InsertPxlSize P((char *pattern, int targetPxlSize));
209 XFontSet CreateFontSet P((char *base_fnt_lst));
211 char *FindFont P((char *pattern, int targetPxlSize));
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));
225 XFontSet fontSet, clockFontSet;
228 XFontStruct *clockFontStruct;
230 Font coordFontID, countFontID;
231 XFontStruct *coordFontStruct, *countFontStruct;
233 void *shellWidget, *formWidget, *boardWidget, *titleWidget, *dropMenu, *menuBarWidget;
234 GtkWidget *mainwindow;
236 Option *optList; // contains all widgets of main window
239 char installDir[] = "."; // [HGM] UCI: needed for UCI; probably needs run-time initializtion
242 static GdkPixbuf *mainwindowIcon=NULL;
243 static GdkPixbuf *WhiteIcon=NULL;
244 static GdkPixbuf *BlackIcon=NULL;
246 /* key board accelerators */
247 GtkAccelGroup *GtkAccelerators;
249 typedef unsigned int BoardSize;
251 Boolean chessProgram;
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;
259 Dimension textHeight;
261 char *chessDir, *programName, *programVersion;
262 Boolean alwaysOnTop = False;
263 char *icsTextMenuString;
265 char *firstChessProgramNames;
266 char *secondChessProgramNames;
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;
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. */
283 SizeDefaults sizeDefaults[] = SIZE_DEFAULTS;
290 DropMenuEnables dmEnables[] = {
299 XtResource clientResources[] = {
300 { "flashCount", "flashCount", XtRInt, sizeof(int),
301 XtOffset(AppDataPtr, flashCount), XtRImmediate,
302 (XtPointer) FLASH_COUNT },
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";
313 char ICSInputTranslations[] =
314 "<Key>Up: UpKeyProc() \n "
315 "<Key>Down: DownKeyProc() \n "
316 "<Key>Return: EnterKeyProc() \n";
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";
323 String xboardResources[] = {
324 "*Error*translations: #override\\n <Key>Return: ErrorPopDown()",
332 gtk_window_present(GTK_WINDOW(shells[BoardWindow]));
335 //---------------------------------------------------------------------------------------------------------
336 // some symbol definitions to provide the proper (= XBoard) context for the code in args.h
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)
347 // The option definition and parsing code common to XBoard and WinBoard is collected in this file
350 // front-end part of option handling
352 // [HGM] This platform-dependent table provides the location for storing the color info
353 extern char *crWhite, * crBlack;
357 &appData.whitePieceColor,
358 &appData.blackPieceColor,
359 &appData.lightSquareColor,
360 &appData.darkSquareColor,
361 &appData.highlightSquareColor,
362 &appData.premoveHighlightColor,
363 &appData.lowTimeWarningColor,
374 // [HGM] font: keep a font for each square size, even non-stndard ones
377 Boolean fontIsSet[NUM_FONTS], fontValid[NUM_FONTS][MAX_SIZE];
378 char *fontTable[NUM_FONTS][MAX_SIZE];
381 ParseFont (char *name, int number)
382 { // in XBoard, only 2 of the fonts are currently implemented, and we just copy their name
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;
394 case 0: // CLOCK_FONT
395 appData.clockFont = strdup(name);
397 case 1: // MESSAGE_FONT
398 appData.font = strdup(name);
400 case 2: // COORD_FONT
401 appData.coordFont = strdup(name);
406 fontIsSet[number] = True; // [HGM] font: indicate a font was specified (not from settings file)
411 { // only 2 fonts currently
412 appData.clockFont = CLOCK_FONT_NAME;
413 appData.coordFont = COORD_FONT_NAME;
414 appData.font = DEFAULT_FONT_NAME;
419 { // no-op, until we identify the code for this already in XBoard and move it here
423 ParseColor (int n, char *name)
424 { // in XBoard, just copy the color-name string
425 if(colorVariable[n]) *(char**)colorVariable[n] = strdup(name);
431 return *(char**)colorVariable[n];
435 ParseTextAttribs (ColorClass cc, char *s)
437 (&appData.colorShout)[cc] = strdup(s);
441 ParseBoardSize (void *addr, char *name)
443 appData.boardSize = strdup(name);
448 { // In XBoard the sound-playing program takes care of obtaining the actual sound
452 SetCommPortDefaults ()
453 { // for now, this is a no-op, as the corresponding option does not exist in XBoard
456 // [HGM] args: these three cases taken out to stay in front-end
458 SaveFontArg (FILE *f, ArgDescriptor *ad)
461 int i, n = (int)(intptr_t)ad->argLoc;
463 case 0: // CLOCK_FONT
464 name = appData.clockFont;
466 case 1: // MESSAGE_FONT
469 case 2: // COORD_FONT
470 name = appData.coordFont;
475 for(i=0; i<NUM_SIZES; i++) // [HGM] font: current font becomes standard for current size
476 if(sizeDefaults[i].squareSize == squareSize) { // only for standard sizes!
477 fontTable[n][squareSize] = strdup(name);
478 fontValid[n][squareSize] = True;
481 for(i=0; i<MAX_SIZE; i++) if(fontValid[n][i]) // [HGM] font: store all standard fonts
482 fprintf(f, OPTCHAR "%s" SEPCHAR "\"size%d:%s\"\n", ad->argName, i, fontTable[n][i]);
487 { // nothing to do, as the sounds are at all times represented by their text-string names already
491 SaveAttribsArg (FILE *f, ArgDescriptor *ad)
492 { // here the "argLoc" defines a table index. It could have contained the 'ta' pointer itself, though
493 fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, (&appData.colorShout)[(int)(intptr_t)ad->argLoc]);
497 SaveColor (FILE *f, ArgDescriptor *ad)
498 { // in WinBoard the color is an int and has to be converted to text. In X it would be a string already?
499 if(colorVariable[(int)(intptr_t)ad->argLoc])
500 fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, *(char**)colorVariable[(int)(intptr_t)ad->argLoc]);
504 SaveBoardSize (FILE *f, char *name, void *addr)
505 { // wrapper to shield back-end from BoardSize & sizeInfo
506 fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", name, appData.boardSize);
510 ParseCommPortSettings (char *s)
511 { // no such option in XBoard (yet)
517 GetActualPlacement (GtkWidget *shell, WindowPlacement *wp)
521 gtk_widget_get_allocation(shell, &a);
522 gtk_window_get_position(GTK_WINDOW(shell), &a.x, &a.y);
526 wp->height = a.height;
527 //printf("placement: (%d,%d) %dx%d\n", a.x, a.y, a.width, a.height);
528 frameX = 3; frameY = 3; // remember to decide if windows touch
532 GetPlacement (DialogClass dlg, WindowPlacement *wp)
533 { // wrapper to shield back-end from widget type
534 if(shellUp[dlg]) GetActualPlacement(shells[dlg], wp);
539 { // wrapper to shield use of window handles from back-end (make addressible by number?)
540 // In XBoard this will have to wait until awareness of window parameters is implemented
541 GetActualPlacement(shellWidget, &wpMain);
542 if(shellUp[EngOutDlg]) GetActualPlacement(shells[EngOutDlg], &wpEngineOutput);
543 if(shellUp[HistoryDlg]) GetActualPlacement(shells[HistoryDlg], &wpMoveHistory);
544 if(shellUp[EvalGraphDlg]) GetActualPlacement(shells[EvalGraphDlg], &wpEvalGraph);
545 if(shellUp[GameListDlg]) GetActualPlacement(shells[GameListDlg], &wpGameList);
546 if(shellUp[CommentDlg]) GetActualPlacement(shells[CommentDlg], &wpComment);
547 if(shellUp[TagsDlg]) GetActualPlacement(shells[TagsDlg], &wpTags);
548 GetPlacement(ChatDlg, &wpConsole); if(appData.icsActive) wpConsole.visible = shellUp[ChatDlg];
552 PrintCommPortSettings (FILE *f, char *name)
553 { // This option does not exist in XBoard
557 EnsureOnScreen (int *x, int *y, int minX, int minY)
564 { // [HGM] args: allows testing if main window is realized from back-end
565 return DialogExists(BoardWindow);
569 PopUpStartupDialog ()
570 { // start menu not implemented in XBoard
574 ConvertToLine (int argc, char **argv)
576 static char line[128*1024], buf[1024];
580 for(i=1; i<argc; i++)
582 if( (strchr(argv[i], ' ') || strchr(argv[i], '\n') ||strchr(argv[i], '\t') || argv[i][0] == NULLCHAR)
583 && argv[i][0] != '{' )
584 snprintf(buf, sizeof(buf)/sizeof(buf[0]), "{%s} ", argv[i]);
586 snprintf(buf, sizeof(buf)/sizeof(buf[0]), "%s ", argv[i]);
587 strncat(line, buf, 128*1024 - strlen(line) - 1 );
590 line[strlen(line)-1] = NULLCHAR;
594 //--------------------------------------------------------------------------------------------
599 ResizeBoardWindow (int w, int h, int inhibit)
602 if(clockKludge) return; // ignore as long as clock does not have final height
603 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
604 w += marginW + 1; // [HGM] not sure why the +1 is (sometimes) needed...
605 h += marginH + a.height + 1;
606 gtk_window_resize(GTK_WINDOW(shellWidget), w, h);
611 { // dummy, as the GTK code does not make colors in advance
616 InitializeFonts (int clockFontPxlSize, int coordFontPxlSize, int fontPxlSize)
617 { // determine what fonts to use, and create them
622 if(!fontIsSet[CLOCK_FONT] && fontValid[CLOCK_FONT][squareSize])
623 appData.clockFont = fontTable[CLOCK_FONT][squareSize];
624 if(!fontIsSet[MESSAGE_FONT] && fontValid[MESSAGE_FONT][squareSize])
625 appData.font = fontTable[MESSAGE_FONT][squareSize];
626 if(!fontIsSet[COORD_FONT] && fontValid[COORD_FONT][squareSize])
627 appData.coordFont = fontTable[COORD_FONT][squareSize];
630 appData.font = InsertPxlSize(appData.font, fontPxlSize);
631 appData.clockFont = InsertPxlSize(appData.clockFont, clockFontPxlSize);
632 appData.coordFont = InsertPxlSize(appData.coordFont, coordFontPxlSize);
633 fontSet = CreateFontSet(appData.font);
634 clockFontSet = CreateFontSet(appData.clockFont);
636 /* For the coordFont, use the 0th font of the fontset. */
637 XFontSet coordFontSet = CreateFontSet(appData.coordFont);
638 XFontStruct **font_struct_list;
639 XFontSetExtents *fontSize;
640 char **font_name_list;
641 XFontsOfFontSet(coordFontSet, &font_struct_list, &font_name_list);
642 coordFontID = XLoadFont(xDisplay, font_name_list[0]);
643 coordFontStruct = XQueryFont(xDisplay, coordFontID);
644 fontSize = XExtentsOfFontSet(fontSet); // [HGM] figure out how much vertical space font takes
645 textHeight = fontSize->max_logical_extent.height + 5; // add borderWidth
648 appData.font = FindFont(appData.font, fontPxlSize);
649 appData.clockFont = FindFont(appData.clockFont, clockFontPxlSize);
650 appData.coordFont = FindFont(appData.coordFont, coordFontPxlSize);
651 clockFontID = XLoadFont(xDisplay, appData.clockFont);
652 clockFontStruct = XQueryFont(xDisplay, clockFontID);
653 coordFontID = XLoadFont(xDisplay, appData.coordFont);
654 coordFontStruct = XQueryFont(xDisplay, coordFontID);
655 // textHeight in !NLS mode!
657 countFontID = coordFontID; // [HGM] holdings
658 countFontStruct = coordFontStruct;
660 xdb = XtDatabase(xDisplay);
662 XrmPutLineResource(&xdb, "*international: True");
663 vTo.size = sizeof(XFontSet);
664 vTo.addr = (XtPointer) &fontSet;
665 XrmPutResource(&xdb, "*fontSet", XtRFontSet, &vTo);
667 XrmPutStringResource(&xdb, "*font", appData.font);
678 case ArgInt: p = " N"; break;
679 case ArgString: p = " STR"; break;
680 case ArgBoolean: p = " TF"; break;
681 case ArgSettingsFilename:
682 case ArgBackupSettingsFile:
683 case ArgFilename: p = " FILE"; break;
684 case ArgX: p = " Nx"; break;
685 case ArgY: p = " Ny"; break;
686 case ArgAttribs: p = " TEXTCOL"; break;
687 case ArgColor: p = " COL"; break;
688 case ArgFont: p = " FONT"; break;
689 case ArgBoardSize: p = " SIZE"; break;
690 case ArgFloat: p = " FLOAT"; break;
695 case ArgCommSettings:
707 ArgDescriptor *q, *p = argDescriptors+5;
708 printf("\nXBoard accepts the following options:\n"
709 "(N = integer, TF = true or false, STR = text string, FILE = filename,\n"
710 " Nx, Ny = relative coordinates, COL = color, FONT = X-font spec,\n"
711 " SIZE = board-size spec(s)\n"
712 " Within parentheses are short forms, or options to set to true or false.\n"
713 " Persistent options (saved in the settings file) are marked with *)\n\n");
715 if(p->argType == ArgCommSettings) { p++; continue; } // XBoard has no comm port
716 snprintf(buf+len, MSG_SIZ, "-%s%s", p->argName, PrintArg(p->argType));
717 if(p->save) strcat(buf+len, "*");
718 for(q=p+1; q->argLoc == p->argLoc; q++) {
719 if(q->argName[0] == '-') continue;
720 strcat(buf+len, q == p+1 ? " (" : " ");
721 sprintf(buf+strlen(buf), "-%s%s", q->argName, PrintArg(q->argType));
723 if(q != p+1) strcat(buf+len, ")");
725 if(len > 39) len = 0, printf("%s\n", buf); else while(len < 39) buf[len++] = ' ';
728 if(len) buf[len] = NULLCHAR, printf("%s\n", buf);
732 SlaveResize (Option *opt)
734 static int slaveW, slaveH, w, h;
737 gtk_widget_get_allocation(shells[DummyDlg], &a);
738 w = a.width; h = a.height;
739 gtk_widget_get_allocation(opt->handle, &a);
740 slaveW = w - opt->max; // [HGM] needed to set new shellWidget size when we resize board
741 slaveH = h - a.height + 13;
743 gtk_window_resize(GTK_WINDOW(shells[DummyDlg]), slaveW + opt->max, slaveH + opt->value);
747 static char clickedFile[MSG_SIZ];
751 StartNewXBoard(GtkosxApplication *app, gchar *path, gpointer user_data)
752 { // handler of OSX OpenFile signal, which sends us the filename of clicked file or first argument
753 if(suppress) { // we just started XBoard without arguments
754 strncpy(clickedFile, path, MSG_SIZ); // remember file name, but otherwise ignore
755 } else { // we are running something presumably useful
757 snprintf(buf, MSG_SIZ, "open -n -a \"xboard\" --args \"%s\"", path);
758 system(buf); // start new instance on this file
765 main (int argc, char **argv)
767 int i, clockFontPxlSize, coordFontPxlSize, fontPxlSize;
768 int boardWidth, w, h; //, boardHeight;
770 int forceMono = False;
772 srandom(time(0)); // [HGM] book: make random truly random
774 setbuf(stdout, NULL);
775 setbuf(stderr, NULL);
778 if(argc > 1 && (!strcmp(argv[1], "-v" ) || !strcmp(argv[1], "--version" ))) {
779 printf("%s version %s\n\n configure options: %s\n", PACKAGE_NAME, PACKAGE_VERSION, CONFIGURE_OPTIONS);
783 if(argc > 1 && !strcmp(argv[1], "--help" )) {
789 gtk_init (&argc, &argv);
791 { // prepare to catch OX OpenFile signal, which will tell us the clicked file
792 GtkosxApplication *theApp = g_object_new(GTKOSX_TYPE_APPLICATION, NULL);
793 char *path = gtkosx_application_get_bundle_path();
794 strncpy(dataDir, path, MSG_SIZ);
795 snprintf(masterSettings, MSG_SIZ, "%s/Contents/Resources/etc/xboard.conf", path);
796 g_signal_connect(theApp, "NSApplicationOpenFile", G_CALLBACK(StartNewXBoard), NULL);
797 // we must call application ready before we can get the signal,
798 // and supply a (dummy) menu bar before that, to avoid problems with dual apples in it
799 gtkosx_application_set_menu_bar(theApp, GTK_MENU_SHELL(gtk_menu_bar_new()));
800 gtkosx_application_ready(theApp);
801 suppress = (argc == 1 || argc > 1 && argv[1][00] != '-'); // OSX sends signal even if name was already argv[1]!
802 if(argc == 1) { // called without args: OSX open-file signal might follow
803 static char *fakeArgv[3] = {NULL, clickedFile, NULL};
804 usleep(10000); // wait 10 msec (and hope this is long enough).
805 while(gtk_events_pending())
806 gtk_main_iteration(); // process all events that came in upto now
807 suppress = 0; // future open-file signals should start new instance
808 if(clickedFile[0]) { // we were sent an open-file signal with filename!
809 fakeArgv[0] = argv[0];
810 argc = 2; argv = fakeArgv; // fake that we were called as "xboard filename"
816 if(argc > 1 && !strcmp(argv[1], "--show-config")) { // [HGM] install: called to print config info
817 typedef struct {char *name, *value; } Config;
818 static Config configList[] = {
819 { "Datadir", DATADIR },
820 { "Sysconfdir", SYSCONFDIR },
825 for(i=0; configList[i].name; i++) {
826 if(argc > 2 && strcmp(argv[2], configList[i].name)) continue;
827 if(argc > 2) printf("%s", configList[i].value);
828 else printf("%-12s: %s\n", configList[i].name, configList[i].value);
833 /* set up keyboard accelerators group */
834 GtkAccelerators = gtk_accel_group_new();
836 programName = strrchr(argv[0], '/');
837 if (programName == NULL)
838 programName = argv[0];
843 // if (appData.debugMode) {
844 // fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
847 bindtextdomain(PACKAGE, LOCALEDIR);
848 bind_textdomain_codeset(PACKAGE, "UTF-8"); // needed when creating markup for the clocks
852 appData.boardSize = "";
853 InitAppData(ConvertToLine(argc, argv));
855 if (p == NULL) p = "/tmp";
856 i = strlen(p) + strlen("/.xboardXXXXXx.pgn") + 1;
857 gameCopyFilename = (char*) malloc(i);
858 gamePasteFilename = (char*) malloc(i);
859 snprintf(gameCopyFilename,i, "%s/.xboard%05uc.pgn", p, getpid());
860 snprintf(gamePasteFilename,i, "%s/.xboard%05up.pgn", p, getpid());
862 { // [HGM] initstring: kludge to fix bad bug. expand '\n' characters in init string and computer string.
863 static char buf[MSG_SIZ];
864 snprintf(buf, MSG_SIZ, appData.sysOpen, DATADIR);
865 ASSIGN(appData.sysOpen, buf); // expand %s in -openCommand to DATADIR (usefull for OS X configuring)
866 EscapeExpand(buf, appData.firstInitString);
867 appData.firstInitString = strdup(buf);
868 EscapeExpand(buf, appData.secondInitString);
869 appData.secondInitString = strdup(buf);
870 EscapeExpand(buf, appData.firstComputerString);
871 appData.firstComputerString = strdup(buf);
872 EscapeExpand(buf, appData.secondComputerString);
873 appData.secondComputerString = strdup(buf);
876 if ((chessDir = (char *) getenv("CHESSDIR")) == NULL) {
879 if (chdir(chessDir) != 0) {
880 fprintf(stderr, _("%s: can't cd to CHESSDIR: "), programName);
886 if (appData.debugMode && appData.nameOfDebugFile && strcmp(appData.nameOfDebugFile, "stderr")) {
887 /* [DM] debug info to file [HGM] make the filename a command-line option, and allow it to remain stderr */
888 if ((debugFP = fopen(appData.nameOfDebugFile, "w")) == NULL) {
889 printf(_("Failed to open file '%s'\n"), appData.nameOfDebugFile);
892 setbuf(debugFP, NULL);
896 if (appData.debugMode) {
897 fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
901 /* [HGM,HR] make sure board size is acceptable */
902 if(appData.NrFiles > BOARD_FILES ||
903 appData.NrRanks > BOARD_RANKS )
904 DisplayFatalError(_("Recompile with larger BOARD_RANKS or BOARD_FILES to support this size"), 0, 2);
907 /* This feature does not work; animation needs a rewrite */
908 appData.highlightDragging = FALSE;
912 gameInfo.variant = StringToVariant(appData.variant);
916 * determine size, based on supplied or remembered -size, or screen size
918 if (isdigit(appData.boardSize[0])) {
919 i = sscanf(appData.boardSize, "%d,%d,%d,%d,%d,%d,%d", &squareSize,
920 &lineGap, &clockFontPxlSize, &coordFontPxlSize,
921 &fontPxlSize, &smallLayout, &tinyLayout);
923 fprintf(stderr, _("%s: bad boardSize syntax %s\n"),
924 programName, appData.boardSize);
928 /* Find some defaults; use the nearest known size */
929 SizeDefaults *szd, *nearest;
930 int distance = 99999;
931 nearest = szd = sizeDefaults;
932 while (szd->name != NULL) {
933 if (abs(szd->squareSize - squareSize) < distance) {
935 distance = abs(szd->squareSize - squareSize);
936 if (distance == 0) break;
940 if (i < 2) lineGap = nearest->lineGap;
941 if (i < 3) clockFontPxlSize = nearest->clockFontPxlSize;
942 if (i < 4) coordFontPxlSize = nearest->coordFontPxlSize;
943 if (i < 5) fontPxlSize = nearest->fontPxlSize;
944 if (i < 6) smallLayout = nearest->smallLayout;
945 if (i < 7) tinyLayout = nearest->tinyLayout;
948 SizeDefaults *szd = sizeDefaults;
949 if (*appData.boardSize == NULLCHAR) {
950 GdkScreen *screen = gtk_window_get_screen(GTK_WINDOW(mainwindow));
951 guint screenwidth = gdk_screen_get_width(screen);
952 guint screenheight = gdk_screen_get_height(screen);
953 while (screenwidth < szd->minScreenSize ||
954 screenheight < szd->minScreenSize) {
957 if (szd->name == NULL) szd--;
958 appData.boardSize = strdup(szd->name); // [HGM] settings: remember name for saving settings
960 while (szd->name != NULL &&
961 StrCaseCmp(szd->name, appData.boardSize) != 0) szd++;
962 if (szd->name == NULL) {
963 fprintf(stderr, _("%s: unrecognized boardSize name %s\n"),
964 programName, appData.boardSize);
968 squareSize = szd->squareSize;
969 lineGap = szd->lineGap;
970 clockFontPxlSize = szd->clockFontPxlSize;
971 coordFontPxlSize = szd->coordFontPxlSize;
972 fontPxlSize = szd->fontPxlSize;
973 smallLayout = szd->smallLayout;
974 tinyLayout = szd->tinyLayout;
975 // [HGM] font: use defaults from settings file if available and not overruled
978 defaultLineGap = lineGap;
979 if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
981 /* [HR] height treated separately (hacked) */
982 boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
983 // boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
986 * Determine what fonts to use.
989 InitializeFonts(clockFontPxlSize, coordFontPxlSize, fontPxlSize);
993 * Detect if there are not enough colors available and adapt.
996 if (DefaultDepth(xDisplay, xScreen) <= 2) {
997 appData.monoMode = True;
1001 forceMono = MakeColors();
1004 fprintf(stderr, _("%s: too few colors available; trying monochrome mode\n"),
1006 appData.monoMode = True;
1009 ParseIcsTextColors();
1015 layoutName = "tinyLayout";
1016 } else if (smallLayout) {
1017 layoutName = "smallLayout";
1019 layoutName = "normalLayout";
1022 wpMain.width = -1; // prevent popup sizes window
1023 optList = BoardPopUp(squareSize, lineGap, (void*)
1033 InitDrawingHandle(optList + W_BOARD);
1034 shellWidget = shells[BoardWindow];
1035 currBoard = &optList[W_BOARD];
1036 boardWidget = optList[W_BOARD].handle;
1037 menuBarWidget = optList[W_MENU].handle;
1038 dropMenu = optList[W_DROP].handle;
1039 titleWidget = optList[optList[W_TITLE].type != -1 ? W_TITLE : W_SMALL].handle;
1041 formWidget = XtParent(boardWidget);
1042 XtSetArg(args[0], XtNbackground, &timerBackgroundPixel);
1043 XtSetArg(args[1], XtNforeground, &timerForegroundPixel);
1044 XtGetValues(optList[W_WHITE].handle, args, 2);
1045 if (appData.showButtonBar) { // can't we use timer pixels for this? (Or better yet, just black & white?)
1046 XtSetArg(args[0], XtNbackground, &buttonBackgroundPixel);
1047 XtSetArg(args[1], XtNforeground, &buttonForegroundPixel);
1048 XtGetValues(optList[W_PAUSE].handle, args, 2);
1052 // [HGM] it seems the layout code ends here, but perhaps the color stuff is size independent and would
1053 // not need to go into InitDrawingSizes().
1057 // add accelerators to main shell
1058 gtk_window_add_accel_group(GTK_WINDOW(shellWidget), GtkAccelerators);
1061 * Create an icon. (Use two icons, to indicate whther it is white's or black's turn.)
1063 WhiteIcon = gdk_pixbuf_new_from_file(SVGDIR "/icon_white.svg", NULL);
1064 BlackIcon = gdk_pixbuf_new_from_file(SVGDIR "/icon_black.svg", NULL);
1065 mainwindowIcon = WhiteIcon;
1066 gtk_window_set_icon(GTK_WINDOW(shellWidget), mainwindowIcon);
1070 * Create a cursor for the board widget.
1073 window_attributes.cursor = XCreateFontCursor(xDisplay, XC_hand2);
1074 XChangeWindowAttributes(xDisplay, xBoardWindow,
1075 CWCursor, &window_attributes);
1079 * Inhibit shell resizing.
1082 shellArgs[0].value = (XtArgVal) &w;
1083 shellArgs[1].value = (XtArgVal) &h;
1084 XtGetValues(shellWidget, shellArgs, 2);
1085 shellArgs[4].value = shellArgs[2].value = w;
1086 shellArgs[5].value = shellArgs[3].value = h;
1087 // XtSetValues(shellWidget, &shellArgs[2], 4);
1090 // Note: We cannot do sensible sizing here, because the height of the clock widget is not yet known
1091 // It wil only become known asynchronously, when we first write a string into it.
1092 // This will then change the clock widget height, which triggers resizing the top-level window
1093 // and a configure event. Only then can we know the total height of the top-level window,
1094 // and calculate the height we need. The clockKludge flag suppresses all resizing until
1095 // that moment comes, after which the configure event-handler handles it through a (delayed) DragProg.
1098 gtk_widget_get_allocation(shells[BoardWindow], &a);
1099 w = a.width; h = a.height;
1100 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1101 clockKludge = hc = a.height;
1102 gtk_widget_get_allocation(boardWidget, &a);
1103 marginW = w - boardWidth; // [HGM] needed to set new shellWidget size when we resize board
1104 marginH = h - a.height - hc; // subtract current clock height, so it can be added back dynamically
1110 if(appData.logoSize)
1111 { // locate and read user logo
1113 snprintf(buf, MSG_SIZ, "%s/%s.png", appData.logoDir, UserName());
1114 ASSIGN(userLogo, buf);
1117 if (appData.animate || appData.animateDragging)
1120 g_signal_connect(shells[BoardWindow], "key-press-event", G_CALLBACK(KeyPressProc), NULL);
1121 g_signal_connect(shells[BoardWindow], "configure-event", G_CALLBACK(EventProc), NULL);
1123 /* [AS] Restore layout */
1124 if( wpMoveHistory.visible ) {
1128 if( wpEvalGraph.visible )
1133 if( wpEngineOutput.visible ) {
1134 EngineOutputPopUp();
1137 if( wpConsole.visible && appData.icsActive ) {
1144 if (errorExitStatus == -1) {
1145 if (appData.icsActive) {
1146 /* We now wait until we see "login:" from the ICS before
1147 sending the logon script (problems with timestamp otherwise) */
1148 /*ICSInitScript();*/
1149 if (appData.icsInputBox) ICSInputBoxPopUp();
1153 signal(SIGWINCH, TermSizeSigHandler);
1155 signal(SIGINT, IntSigHandler);
1156 signal(SIGTERM, IntSigHandler);
1157 if (*appData.cmailGameName != NULLCHAR) {
1158 signal(SIGUSR1, CmailSigHandler);
1162 gameInfo.boardWidth = 0; // [HGM] pieces: kludge to ensure InitPosition() calls InitDrawingSizes()
1165 // XtSetKeyboardFocus(shellWidget, formWidget);
1167 XSetInputFocus(xDisplay, XtWindow(formWidget), RevertToPointerRoot, CurrentTime);
1170 /* check for GTK events and process them */
1173 gtk_main_iteration();
1176 if (appData.debugMode) fclose(debugFP); // [DM] debug
1181 TermSizeSigHandler (int sig)
1187 IntSigHandler (int sig)
1193 CmailSigHandler (int sig)
1198 signal(SIGUSR1, SIG_IGN); /* suspend handler */
1200 /* Activate call-back function CmailSigHandlerCallBack() */
1201 OutputToProcess(cmailPR, (char *)(&dummy), sizeof(int), &error);
1203 signal(SIGUSR1, CmailSigHandler); /* re-activate handler */
1207 CmailSigHandlerCallBack (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1210 ReloadCmailMsgEvent(TRUE); /* Reload cmail msg */
1212 /**** end signal code ****/
1215 #define Abs(n) ((n)<0 ? -(n) : (n))
1219 InsertPxlSize (char *pattern, int targetPxlSize)
1221 char *base_fnt_lst, strInt[12], *p, *q;
1222 int alternatives, i, len, strIntLen;
1225 * Replace the "*" (if present) in the pixel-size slot of each
1226 * alternative with the targetPxlSize.
1230 while ((p = strchr(p, ',')) != NULL) {
1234 snprintf(strInt, sizeof(strInt), "%d", targetPxlSize);
1235 strIntLen = strlen(strInt);
1236 base_fnt_lst = calloc(1, strlen(pattern) + strIntLen * alternatives + 1);
1240 while (alternatives--) {
1241 char *comma = strchr(p, ',');
1242 for (i=0; i<14; i++) {
1243 char *hyphen = strchr(p, '-');
1245 if (comma && hyphen > comma) break;
1246 len = hyphen + 1 - p;
1247 if (i == 7 && *p == '*' && len == 2) {
1249 memcpy(q, strInt, strIntLen);
1259 len = comma + 1 - p;
1266 return base_fnt_lst;
1271 CreateFontSet (char *base_fnt_lst)
1274 char **missing_list;
1278 fntSet = XCreateFontSet(xDisplay, base_fnt_lst,
1279 &missing_list, &missing_count, &def_string);
1280 if (appData.debugMode) {
1282 XFontStruct **font_struct_list;
1283 char **font_name_list;
1284 fprintf(debugFP, "Requested font set for list %s\n", base_fnt_lst);
1286 fprintf(debugFP, " got list %s, locale %s\n",
1287 XBaseFontNameListOfFontSet(fntSet),
1288 XLocaleOfFontSet(fntSet));
1289 count = XFontsOfFontSet(fntSet, &font_struct_list, &font_name_list);
1290 for (i = 0; i < count; i++) {
1291 fprintf(debugFP, " got charset %s\n", font_name_list[i]);
1294 for (i = 0; i < missing_count; i++) {
1295 fprintf(debugFP, " missing charset %s\n", missing_list[i]);
1298 if (fntSet == NULL) {
1299 fprintf(stderr, _("Unable to create font set for %s.\n"), base_fnt_lst);
1305 #else // not ENABLE_NLS
1307 * Find a font that matches "pattern" that is as close as
1308 * possible to the targetPxlSize. Prefer fonts that are k
1309 * pixels smaller to fonts that are k pixels larger. The
1310 * pattern must be in the X Consortium standard format,
1311 * e.g. "-*-helvetica-bold-r-normal--*-*-*-*-*-*-*-*".
1312 * The return value should be freed with XtFree when no
1316 FindFont (char *pattern, int targetPxlSize)
1318 char **fonts, *p, *best, *scalable, *scalableTail;
1319 int i, j, nfonts, minerr, err, pxlSize;
1322 fonts = XListFonts(xDisplay, pattern, 999999, &nfonts);
1324 fprintf(stderr, _("%s: no fonts match pattern %s\n"),
1325 programName, pattern);
1332 for (i=0; i<nfonts; i++) {
1335 if (*p != '-') continue;
1337 if (*p == NULLCHAR) break;
1338 if (*p++ == '-') j++;
1340 if (j < 7) continue;
1343 scalable = fonts[i];
1346 err = pxlSize - targetPxlSize;
1347 if (Abs(err) < Abs(minerr) ||
1348 (minerr > 0 && err < 0 && -err == minerr)) {
1354 if (scalable && Abs(minerr) > appData.fontSizeTolerance) {
1355 /* If the error is too big and there is a scalable font,
1356 use the scalable font. */
1357 int headlen = scalableTail - scalable;
1358 p = (char *) XtMalloc(strlen(scalable) + 10);
1359 while (isdigit(*scalableTail)) scalableTail++;
1360 sprintf(p, "%.*s%d%s", headlen, scalable, targetPxlSize, scalableTail);
1362 p = (char *) XtMalloc(strlen(best) + 2);
1363 safeStrCpy(p, best, strlen(best)+1 );
1365 if (appData.debugMode) {
1366 fprintf(debugFP, "resolved %s at pixel size %d\n to %s\n",
1367 pattern, targetPxlSize, p);
1369 XFreeFontNames(fonts);
1376 EnableNamedMenuItem (char *menuRef, int state)
1378 MenuItem *item = MenuNameToItem(menuRef);
1380 if(item && item->handle) gtk_widget_set_sensitive(item->handle, state);
1384 EnableButtonBar (int state)
1387 XtSetSensitive(optList[W_BUTTON].handle, state);
1393 SetMenuEnables (Enables *enab)
1395 while (enab->name != NULL) {
1396 EnableNamedMenuItem(enab->name, enab->value);
1401 gboolean KeyPressProc(window, eventkey, data)
1403 GdkEventKey *eventkey;
1407 MoveTypeInProc(eventkey); // pop up for typed in moves
1410 /* check for other key values */
1411 switch(eventkey->keyval) {
1423 KeyBindingProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1424 { // [HGM] new method of key binding: specify MenuItem(FlipView) in stead of FlipViewProc in translation string
1426 if(*nprms == 0) return;
1427 item = MenuNameToItem(prms[0]);
1428 if(item) ((MenuProc *) item->proc) ();
1442 for (i=0; i<sizeof(dmEnables)/sizeof(DropMenuEnables); i++) {
1443 entry = XtNameToWidget(dropMenu, dmEnables[i].widget);
1444 p = strchr(gameMode == IcsPlayingWhite ? white_holding : black_holding,
1445 dmEnables[i].piece);
1446 XtSetSensitive(entry, p != NULL || !appData.testLegality
1447 /*!!temp:*/ || (gameInfo.variant == VariantCrazyhouse
1448 && !appData.icsActive));
1450 while (p && *p++ == dmEnables[i].piece) count++;
1451 snprintf(label, sizeof(label), "%s %d", dmEnables[i].widget, count);
1453 XtSetArg(args[j], XtNlabel, label); j++;
1454 XtSetValues(entry, args, j);
1460 do_flash_delay (unsigned long msec)
1466 FlashDelay (int flash_delay)
1468 if(flash_delay) do_flash_delay(flash_delay);
1472 Fraction (int x, int start, int stop)
1474 double f = ((double) x - start)/(stop - start);
1475 if(f > 1.) f = 1.; else if(f < 0.) f = 0.;
1479 static WindowPlacement wpNew;
1482 CoDrag (GtkWidget *sh, WindowPlacement *wp)
1484 int touch=0, fudge = 2, f = 2;
1485 GetActualPlacement(sh, wp);
1486 if(abs(wpMain.x + wpMain.width + 2*frameX - f - wp->x) < fudge) touch = 1; else // right touch
1487 if(abs(wp->x + wp->width + 2*frameX + f - wpMain.x) < fudge) touch = 2; else // left touch
1488 if(abs(wpMain.y + wpMain.height + frameX - f + frameY - wp->y) < fudge) touch = 3; else // bottom touch
1489 if(abs(wp->y + wp->height + frameX + frameY + f - wpMain.y) < fudge) touch = 4; // top touch
1490 //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);
1491 if(!touch ) return; // only windows that touch co-move
1492 if(touch < 3 && wpNew.height != wpMain.height) { // left or right and height changed
1493 int heightInc = wpNew.height - wpMain.height;
1494 double fracTop = Fraction(wp->y, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1495 double fracBot = Fraction(wp->y + wp->height + frameX + frameY + 1, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1496 wp->y += fracTop * heightInc;
1497 heightInc = (int) (fracBot * heightInc) - (int) (fracTop * heightInc);
1499 if(heightInc) XtSetArg(args[j], XtNheight, wp->height + heightInc), j++;
1501 wp->height += heightInc;
1502 } else if(touch > 2 && wpNew.width != wpMain.width) { // top or bottom and width changed
1503 int widthInc = wpNew.width - wpMain.width;
1504 double fracLeft = Fraction(wp->x, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1505 double fracRght = Fraction(wp->x + wp->width + 2*frameX + 1, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1506 wp->y += fracLeft * widthInc;
1507 widthInc = (int) (fracRght * widthInc) - (int) (fracLeft * widthInc);
1509 if(widthInc) XtSetArg(args[j], XtNwidth, wp->width + widthInc), j++;
1511 wp->width += widthInc;
1513 wp->x += wpNew.x - wpMain.x;
1514 wp->y += wpNew.y - wpMain.y;
1515 if(touch == 1) wp->x += wpNew.width - wpMain.width; else
1516 if(touch == 3) wp->y += wpNew.height - wpMain.height;
1518 XtSetArg(args[j], XtNx, wp->x); j++;
1519 XtSetArg(args[j], XtNy, wp->y); j++;
1520 XtSetValues(sh, args, j);
1522 gtk_window_move(GTK_WINDOW(sh), wp->x, wp->y);
1523 //printf("moved to (%d,%d)\n", wp->x, wp->y);
1524 gtk_window_resize(GTK_WINDOW(sh), wp->width, wp->height);
1528 ReSize (WindowPlacement *wp)
1531 int sqx, sqy, w, h, hc, lg = lineGap;
1532 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1533 hc = a.height; // clock height can depend on single / double line clock text!
1534 if(clockKludge == a.height) return; // wait for clock to get final size at startup
1535 if(clockKludge) { // clock height OK now; calculate desired initial board height
1537 wp->height = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + marginH + hc;
1539 if(wp->width == wpMain.width && wp->height == wpMain.height) return; // not sized
1540 sqx = (wp->width - lg - marginW) / BOARD_WIDTH - lg;
1541 sqy = (wp->height - lg - marginH - hc) / BOARD_HEIGHT - lg;
1542 if(sqy < sqx) sqx = sqy;
1543 if(appData.overrideLineGap < 0) { // do second iteration with adjusted lineGap
1544 lg = lineGap = sqx < 37 ? 1 : sqx < 59 ? 2 : sqx < 116 ? 3 : 4;
1545 sqx = (wp->width - lg - marginW) / BOARD_WIDTH - lg;
1546 sqy = (wp->height - lg - marginH - hc) / BOARD_HEIGHT - lg;
1547 if(sqy < sqx) sqx = sqy;
1549 if(sqx != squareSize) {
1550 //printf("new sq size %d (%dx%d)\n", sqx, wp->width, wp->height);
1551 squareSize = sqx; // adopt new square size
1552 CreatePNGPieces(); // make newly scaled pieces
1553 InitDrawingSizes(0, 0); // creates grid etc.
1554 } else ResizeBoardWindow(BOARD_WIDTH * (squareSize + lineGap) + lineGap, BOARD_HEIGHT * (squareSize + lineGap) + lineGap, 0);
1555 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
1556 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
1557 if(optList[W_BOARD].max > w) optList[W_BOARD].max = w;
1558 if(optList[W_BOARD].value > h) optList[W_BOARD].value = h;
1561 static guint delayedDragTag = 0;
1570 // GetActualPlacement(shellWidget, &wpNew);
1571 if(wpNew.x == wpMain.x && wpNew.y == wpMain.y && // not moved
1572 wpNew.width == wpMain.width && wpNew.height == wpMain.height) { // not sized
1573 busy = 0; return; // false alarm
1576 if(appData.useStickyWindows) {
1577 if(shellUp[EngOutDlg]) CoDrag(shells[EngOutDlg], &wpEngineOutput);
1578 if(shellUp[HistoryDlg]) CoDrag(shells[HistoryDlg], &wpMoveHistory);
1579 if(shellUp[EvalGraphDlg]) CoDrag(shells[EvalGraphDlg], &wpEvalGraph);
1580 if(shellUp[GameListDlg]) CoDrag(shells[GameListDlg], &wpGameList);
1581 if(shellUp[ChatDlg]) CoDrag(shells[ChatDlg], &wpConsole);
1584 DrawPosition(True, NULL);
1585 if(delayedDragTag) g_source_remove(delayedDragTag);
1586 delayedDragTag = 0; // now drag executed, make sure next DelayedDrag will not cancel timer event (which could now be used by other)
1593 //printf("old timr = %d\n", delayedDragTag);
1594 if(delayedDragTag) g_source_remove(delayedDragTag);
1595 delayedDragTag = g_timeout_add( 200, (GSourceFunc) DragProc, NULL);
1596 //printf("new timr = %d\n", delayedDragTag);
1600 EventProc (GtkWidget *widget, GdkEvent *event, gpointer g)
1602 //printf("event proc (%d,%d) %dx%d\n", event->configure.x, event->configure.y, event->configure.width, event->configure.height);
1604 wpNew.x = event->configure.x;
1605 wpNew.y = event->configure.y;
1606 wpNew.width = event->configure.width;
1607 wpNew.height = event->configure.height;
1608 DelayedDrag(); // as long as events keep coming in faster than 50 msec, they destroy each other
1614 /* Disable all user input other than deleting the window */
1615 static int frozen = 0;
1621 /* Grab by a widget that doesn't accept input */
1622 gtk_grab_add(optList[W_MESSG].handle);
1626 /* Undo a FreezeUI */
1630 if (!frozen) return;
1631 gtk_grab_remove(optList[W_MESSG].handle);
1638 static int oldPausing = FALSE;
1639 static GameMode oldmode = (GameMode) -1;
1641 if (!boardWidget) return;
1643 if (pausing != oldPausing) {
1644 oldPausing = pausing;
1645 MarkMenuItem("Mode.Pause", pausing);
1647 if (appData.showButtonBar) {
1648 /* Always toggle, don't set. Previous code messes up when
1649 invoked while the button is pressed, as releasing it
1650 toggles the state again. */
1652 gdk_color_parse( pausing ? "#808080" : "#F0F0F0", &color );
1653 gtk_widget_modify_bg ( GTK_WIDGET(optList[W_PAUSE].handle), GTK_STATE_NORMAL, &color );
1657 wname = ModeToWidgetName(oldmode);
1658 if (wname != NULL) {
1659 MarkMenuItem(wname, False);
1661 wname = ModeToWidgetName(gameMode);
1662 if (wname != NULL) {
1663 MarkMenuItem(wname, True);
1666 MarkMenuItem("Mode.MachineMatch", matchMode && matchGame < appData.matchGames);
1668 /* Maybe all the enables should be handled here, not just this one */
1669 EnableNamedMenuItem("Mode.Training", gameMode == Training || gameMode == PlayFromGameFile);
1671 DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
1676 * Button/menu procedures
1679 void CopyFileToClipboard(gchar *filename)
1681 gchar *selection_tmp;
1685 FILE* f = fopen(filename, "r");
1688 if (f == NULL) return;
1692 selection_tmp = g_try_malloc(len + 1);
1693 if (selection_tmp == NULL) {
1694 printf("Malloc failed in CopyFileToClipboard\n");
1697 count = fread(selection_tmp, 1, len, f);
1700 g_free(selection_tmp);
1703 selection_tmp[len] = NULLCHAR; // file is now in selection_tmp
1705 // copy selection_tmp to clipboard
1706 GdkDisplay *gdisp = gdk_display_get_default();
1708 g_free(selection_tmp);
1711 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1712 gtk_clipboard_set_text(cb, selection_tmp, -1);
1713 g_free(selection_tmp);
1717 CopySomething (char *src)
1719 GdkDisplay *gdisp = gdk_display_get_default();
1721 if(!src) { CopyFileToClipboard(gameCopyFilename); return; }
1722 if (gdisp == NULL) return;
1723 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1724 gtk_clipboard_set_text(cb, src, -1);
1728 PastePositionProc ()
1730 GdkDisplay *gdisp = gdk_display_get_default();
1734 if (gdisp == NULL) return;
1735 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1736 fenstr = gtk_clipboard_wait_for_text(cb);
1737 if (fenstr==NULL) return; // nothing had been selected to copy
1738 EditPositionPasteFEN(fenstr);
1750 // get game from clipboard
1751 GdkDisplay *gdisp = gdk_display_get_default();
1752 if (gdisp == NULL) return;
1753 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1754 text = gtk_clipboard_wait_for_text(cb);
1755 if (text == NULL) return; // nothing to paste
1758 // write to temp file
1759 if (text == NULL || len == 0) {
1760 return; //nothing to paste
1762 f = fopen(gamePasteFilename, "w");
1764 DisplayError(_("Can't open temp file"), errno);
1767 fwrite(text, 1, len, f);
1771 LoadGameFromFile(gamePasteFilename, 0, gamePasteFilename, TRUE);
1778 QuitWrapper (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1784 void MoveTypeInProc(eventkey)
1785 GdkEventKey *eventkey;
1789 // ingnore if ctrl, alt, or meta is pressed
1790 if (eventkey->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_META_MASK)) {
1794 buf[0]=eventkey->keyval;
1796 if (eventkey->keyval > 32 && eventkey->keyval < 256)
1797 ConsoleAutoPopUp (buf);
1802 TempBackwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1804 if (!TempBackwardActive) {
1805 TempBackwardActive = True;
1811 TempForwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1813 /* Check to see if triggered by a key release event for a repeating key.
1814 * If so the next queued event will be a key press of the same key at the same time */
1815 if (XEventsQueued(xDisplay, QueuedAfterReading)) {
1817 XPeekEvent(xDisplay, &next);
1818 if (next.type == KeyPress && next.xkey.time == event->xkey.time &&
1819 next.xkey.keycode == event->xkey.keycode)
1823 TempBackwardActive = False;
1829 { // called from menu
1832 snprintf(buf, MSG_SIZ, "%s ./man.command", appData.sysOpen);
1835 system("xterm -e man xboard &");
1840 SetWindowTitle (char *text, char *title, char *icon)
1845 if (appData.titleInWindow) {
1847 XtSetArg(args[i], XtNlabel, text); i++;
1848 XtSetValues(titleWidget, args, i);
1851 XtSetArg(args[i], XtNiconName, (XtArgVal) icon); i++;
1852 XtSetArg(args[i], XtNtitle, (XtArgVal) title); i++;
1853 XtSetValues(shellWidget, args, i);
1854 XSync(xDisplay, False);
1856 if (appData.titleInWindow) {
1857 SetWidgetLabel(titleWidget, text);
1859 gtk_window_set_title (GTK_WINDOW(shells[BoardWindow]), title);
1864 DisplayIcsInteractionTitle (String message)
1867 if (oldICSInteractionTitle == NULL) {
1868 /* Magic to find the old window title, adapted from vim */
1869 char *wina = getenv("WINDOWID");
1871 Window win = (Window) atoi(wina);
1872 Window root, parent, *children;
1873 unsigned int nchildren;
1874 int (*oldHandler)() = XSetErrorHandler(NullXErrorCheck);
1876 if (XFetchName(xDisplay, win, &oldICSInteractionTitle)) break;
1877 if (!XQueryTree(xDisplay, win, &root, &parent,
1878 &children, &nchildren)) break;
1879 if (children) XFree((void *)children);
1880 if (parent == root || parent == 0) break;
1883 XSetErrorHandler(oldHandler);
1885 if (oldICSInteractionTitle == NULL) {
1886 oldICSInteractionTitle = "xterm";
1889 printf("\033]0;%s\007", message);
1896 DisplayTimerLabel (Option *opt, char *color, long timer, int highlight)
1898 GtkWidget *w = (GtkWidget *) opt->handle;
1905 strcpy(bgcolor, "black");
1906 strcpy(fgcolor, "white");
1908 strcpy(bgcolor, "white");
1909 strcpy(fgcolor, "black");
1912 appData.lowTimeWarning &&
1913 (timer / 1000) < appData.icsAlarmTime) {
1914 strcpy(fgcolor, appData.lowTimeWarningColor);
1917 gdk_color_parse( bgcolor, &col );
1918 gtk_widget_modify_bg(gtk_widget_get_parent(opt->handle), GTK_STATE_NORMAL, &col);
1920 if (appData.clockMode) {
1921 markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s:%s%s</span>",
1922 bgcolor, fgcolor, color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
1924 markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s </span>",
1925 bgcolor, fgcolor, color);
1927 gtk_label_set_markup(GTK_LABEL(w), markup);
1931 static GdkPixbuf **clockIcons[] = { &WhiteIcon, &BlackIcon };
1934 SetClockIcon (int color)
1936 GdkPixbuf *pm = *clockIcons[color];
1937 if (mainwindowIcon != pm) {
1938 mainwindowIcon = pm;
1939 gtk_window_set_icon(GTK_WINDOW(shellWidget), mainwindowIcon);
1943 #define INPUT_SOURCE_BUF_SIZE 8192
1952 char buf[INPUT_SOURCE_BUF_SIZE];
1957 DoInputCallback(io, cond, data)
1962 /* read input from one of the input source (for example a chess program, ICS, etc).
1963 * and call a function that will handle the input
1970 /* All information (callback function, file descriptor, etc) is
1971 * saved in an InputSource structure
1973 InputSource *is = (InputSource *) data;
1975 if (is->lineByLine) {
1976 count = read(is->fd, is->unused,
1977 INPUT_SOURCE_BUF_SIZE - (is->unused - is->buf));
1979 if(count == 0 && is->kind == CPReal && shells[ChatDlg]) { // [HGM] absence of terminal is no error if ICS Console present
1980 RemoveInputSource(is); // cease reading stdin
1981 stdoutClosed = TRUE; // suppress future output
1984 (is->func)(is, is->closure, is->buf, count, count ? errno : 0);
1987 is->unused += count;
1989 /* break input into lines and call the callback function on each
1992 while (p < is->unused) {
1993 q = memchr(p, '\n', is->unused - p);
1994 if (q == NULL) break;
1996 (is->func)(is, is->closure, p, q - p, 0);
1999 /* remember not yet used part of the buffer */
2001 while (p < is->unused) {
2006 /* read maximum length of input buffer and send the whole buffer
2007 * to the callback function
2009 count = read(is->fd, is->buf, INPUT_SOURCE_BUF_SIZE);
2014 (is->func)(is, is->closure, is->buf, count, error);
2016 return True; // Must return true or the watch will be removed
2019 InputSourceRef AddInputSource(pr, lineByLine, func, closure)
2026 GIOChannel *channel;
2027 ChildProc *cp = (ChildProc *) pr;
2029 is = (InputSource *) calloc(1, sizeof(InputSource));
2030 is->lineByLine = lineByLine;
2034 is->fd = fileno(stdin);
2036 is->kind = cp->kind;
2037 is->fd = cp->fdFrom;
2040 is->unused = is->buf;
2044 /* GTK-TODO: will this work on windows?*/
2046 channel = g_io_channel_unix_new(is->fd);
2047 g_io_channel_set_close_on_unref (channel, TRUE);
2048 is->sid = g_io_add_watch(channel, G_IO_IN,(GIOFunc) DoInputCallback, is);
2050 is->closure = closure;
2051 return (InputSourceRef) is;
2056 RemoveInputSource(isr)
2059 InputSource *is = (InputSource *) isr;
2061 if (is->sid == 0) return;
2062 g_source_remove(is->sid);
2069 static Boolean frameWaiting;
2072 FrameAlarm (int sig)
2074 frameWaiting = False;
2075 /* In case System-V style signals. Needed?? */
2076 signal(SIGALRM, FrameAlarm);
2080 FrameDelay (int time)
2082 struct itimerval delay;
2085 frameWaiting = True;
2086 signal(SIGALRM, FrameAlarm);
2087 delay.it_interval.tv_sec =
2088 delay.it_value.tv_sec = time / 1000;
2089 delay.it_interval.tv_usec =
2090 delay.it_value.tv_usec = (time % 1000) * 1000;
2091 setitimer(ITIMER_REAL, &delay, NULL);
2092 while (frameWaiting) pause();
2093 delay.it_interval.tv_sec = delay.it_value.tv_sec = 0;
2094 delay.it_interval.tv_usec = delay.it_value.tv_usec = 0;
2095 setitimer(ITIMER_REAL, &delay, NULL);
2102 FrameDelay (int time)
2105 XSync(xDisplay, False);
2107 // gtk_main_iteration_do(False);
2110 usleep(time * 1000);
2116 LoadLogo (ChessProgramState *cps, int n, Boolean ics)
2118 char buf[MSG_SIZ], *logoName = buf;
2119 if(appData.logo[n][0]) {
2120 logoName = appData.logo[n];
2121 } else if(appData.autoLogo) {
2122 if(ics) { // [HGM] logo: in ICS mode second can be used for ICS
2123 sprintf(buf, "%s/%s.png", appData.logoDir, appData.icsHost);
2124 } else if(appData.directory[n] && appData.directory[n][0]) {
2125 sprintf(buf, "%s/%s.png", appData.logoDir, cps->tidy);
2129 { ASSIGN(cps->programLogo, logoName); }
2133 UpdateLogos (int displ)
2135 if(optList[W_WHITE-1].handle == NULL) return;
2136 LoadLogo(&first, 0, 0);
2137 LoadLogo(&second, 1, appData.icsActive);
2138 if(displ) DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
2142 void FileNamePopUpWrapper(label, def, filter, proc, pathFlag, openMode, name, fp)
2153 GtkFileFilter *gtkfilter;
2154 GtkFileFilter *gtkfilter_all;
2156 char fileext[10] = "";
2157 char *result = NULL;
2160 /* make a copy of the filter string, so that strtok can work with it*/
2161 cp = strdup(filter);
2163 /* add filters for file extensions */
2164 gtkfilter = gtk_file_filter_new();
2165 gtkfilter_all = gtk_file_filter_new();
2167 /* one filter to show everything */
2168 gtk_file_filter_add_pattern(gtkfilter_all, "*.*");
2169 gtk_file_filter_set_name (gtkfilter_all, "All Files");
2171 /* add filter if present */
2172 result = strtok(cp, space);
2173 while( result != NULL ) {
2174 snprintf(fileext,10,"*%s",result);
2175 result = strtok( NULL, space );
2176 gtk_file_filter_add_pattern(gtkfilter, fileext);
2179 /* second filter to only show what's useful */
2180 gtk_file_filter_set_name (gtkfilter,filter);
2182 if (openMode[0] == 'r')
2184 dialog = gtk_file_chooser_dialog_new (label,
2186 GTK_FILE_CHOOSER_ACTION_OPEN,
2187 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2188 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
2193 dialog = gtk_file_chooser_dialog_new (label,
2195 GTK_FILE_CHOOSER_ACTION_SAVE,
2196 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2197 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
2199 /* add filename suggestions */
2200 if (strlen(def) > 0 )
2201 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), def);
2203 //gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER (dialog),TRUE);
2207 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter_all);
2208 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2209 /* activate filter */
2210 gtk_file_chooser_set_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2212 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
2217 filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
2220 f = fopen(filename, openMode);
2223 DisplayError(_("Failed to open file"), errno);
2227 /* TODO add indec */
2229 ASSIGN(*name, filename);
2230 ScheduleDelayedEvent(DelayedLoad, 50);
2235 gtk_widget_destroy (dialog);