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 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 # define ICS_LOGON "Library/Preferences/XboardICS.conf"
179 # define SYSCONFDIR "../etc"
180 # define DATADIR dataDir
181 char *dataDir; // for expanding ~~
184 # define DATADIR "~~"
191 #define usleep(t) _sleep2(((t)+500)/1000)
195 # define _(s) gettext (s)
196 # define N_(s) gettext_noop (s)
202 int main P((int argc, char **argv));
203 RETSIGTYPE CmailSigHandler P((int sig));
204 RETSIGTYPE IntSigHandler P((int sig));
205 RETSIGTYPE TermSizeSigHandler P((int sig));
207 char *InsertPxlSize P((char *pattern, int targetPxlSize));
208 XFontSet CreateFontSet P((char *base_fnt_lst));
210 char *FindFont P((char *pattern, int targetPxlSize));
212 void DelayedDrag P((void));
213 void ICSInputBoxPopUp P((void));
214 void MoveTypeInProc P((GdkEventKey *eventkey));
215 gboolean KeyPressProc P((GtkWindow *window, GdkEventKey *eventkey, gpointer data));
216 Boolean TempBackwardActive = False;
217 void DisplayMove P((int moveNumber));
218 void update_ics_width P(());
219 int CopyMemoProc P(());
220 static gboolean EventProc P((GtkWidget *widget, GdkEvent *event, gpointer g));
224 XFontSet fontSet, clockFontSet;
227 XFontStruct *clockFontStruct;
229 Font coordFontID, countFontID;
230 XFontStruct *coordFontStruct, *countFontStruct;
232 void *shellWidget, *formWidget, *boardWidget, *titleWidget, *dropMenu, *menuBarWidget;
233 GtkWidget *mainwindow;
235 Option *optList; // contains all widgets of main window
238 char installDir[] = "."; // [HGM] UCI: needed for UCI; probably needs run-time initializtion
241 static GdkPixbuf *mainwindowIcon=NULL;
242 static GdkPixbuf *WhiteIcon=NULL;
243 static GdkPixbuf *BlackIcon=NULL;
245 /* key board accelerators */
246 GtkAccelGroup *GtkAccelerators;
248 typedef unsigned int BoardSize;
250 Boolean chessProgram;
252 int minX, minY; // [HGM] placement: volatile limits on upper-left corner
253 int smallLayout = 0, tinyLayout = 0,
254 marginW, marginH, // [HGM] for run-time resizing
255 fromX = -1, fromY = -1, toX, toY, commentUp = False,
256 errorExitStatus = -1, defaultLineGap;
258 Dimension textHeight;
260 char *chessDir, *programName, *programVersion;
261 Boolean alwaysOnTop = False;
262 char *icsTextMenuString;
264 char *firstChessProgramNames;
265 char *secondChessProgramNames;
267 WindowPlacement wpMain;
268 WindowPlacement wpConsole;
269 WindowPlacement wpComment;
270 WindowPlacement wpMoveHistory;
271 WindowPlacement wpEvalGraph;
272 WindowPlacement wpEngineOutput;
273 WindowPlacement wpGameList;
274 WindowPlacement wpTags;
275 WindowPlacement wpDualBoard;
277 /* This magic number is the number of intermediate frames used
278 in each half of the animation. For short moves it's reduced
279 by 1. The total number of frames will be factor * 2 + 1. */
282 SizeDefaults sizeDefaults[] = SIZE_DEFAULTS;
289 DropMenuEnables dmEnables[] = {
298 XtResource clientResources[] = {
299 { "flashCount", "flashCount", XtRInt, sizeof(int),
300 XtOffset(AppDataPtr, flashCount), XtRImmediate,
301 (XtPointer) FLASH_COUNT },
305 /* keyboard shortcuts not yet transistioned int menuitem @ menu.c */
306 char globalTranslations[] =
307 ":Ctrl<Key>Down: LoadSelectedProc(3) \n \
308 :Ctrl<Key>Up: LoadSelectedProc(-3) \n \
309 :<KeyDown>Return: TempBackwardProc() \n \
310 :<KeyUp>Return: TempForwardProc() \n";
312 char ICSInputTranslations[] =
313 "<Key>Up: UpKeyProc() \n "
314 "<Key>Down: DownKeyProc() \n "
315 "<Key>Return: EnterKeyProc() \n";
317 // [HGM] vari: another hideous kludge: call extend-end first so we can be sure select-start works,
318 // as the widget is destroyed before the up-click can call extend-end
319 char commentTranslations[] = "<Btn3Down>: extend-end() select-start() CommentClick() \n";
322 String xboardResources[] = {
323 "*Error*translations: #override\\n <Key>Return: ErrorPopDown()",
331 gtk_window_present(GTK_WINDOW(shells[BoardWindow]));
334 //---------------------------------------------------------------------------------------------------------
335 // some symbol definitions to provide the proper (= XBoard) context for the code in args.h
338 #define CW_USEDEFAULT (1<<31)
339 #define ICS_TEXT_MENU_SIZE 90
340 #define DEBUG_FILE "xboard.debug"
341 #define SetCurrentDirectory chdir
342 #define GetCurrentDirectory(SIZE, NAME) getcwd(NAME, SIZE)
346 // The option definition and parsing code common to XBoard and WinBoard is collected in this file
349 // front-end part of option handling
351 // [HGM] This platform-dependent table provides the location for storing the color info
352 extern char *crWhite, * crBlack;
356 &appData.whitePieceColor,
357 &appData.blackPieceColor,
358 &appData.lightSquareColor,
359 &appData.darkSquareColor,
360 &appData.highlightSquareColor,
361 &appData.premoveHighlightColor,
362 &appData.lowTimeWarningColor,
373 // [HGM] font: keep a font for each square size, even non-stndard ones
376 Boolean fontIsSet[NUM_FONTS], fontValid[NUM_FONTS][MAX_SIZE];
377 char *fontTable[NUM_FONTS][MAX_SIZE];
380 ParseFont (char *name, int number)
381 { // in XBoard, only 2 of the fonts are currently implemented, and we just copy their name
383 if(sscanf(name, "size%d:", &size)) {
384 // [HGM] font: font is meant for specific boardSize (likely from settings file);
385 // defer processing it until we know if it matches our board size
386 if(size >= 0 && size<MAX_SIZE) { // for now, fixed limit
387 fontTable[number][size] = strdup(strchr(name, ':')+1);
388 fontValid[number][size] = True;
393 case 0: // CLOCK_FONT
394 appData.clockFont = strdup(name);
396 case 1: // MESSAGE_FONT
397 appData.font = strdup(name);
399 case 2: // COORD_FONT
400 appData.coordFont = strdup(name);
405 fontIsSet[number] = True; // [HGM] font: indicate a font was specified (not from settings file)
410 { // only 2 fonts currently
411 appData.clockFont = CLOCK_FONT_NAME;
412 appData.coordFont = COORD_FONT_NAME;
413 appData.font = DEFAULT_FONT_NAME;
418 { // no-op, until we identify the code for this already in XBoard and move it here
422 ParseColor (int n, char *name)
423 { // in XBoard, just copy the color-name string
424 if(colorVariable[n]) *(char**)colorVariable[n] = strdup(name);
428 ParseTextAttribs (ColorClass cc, char *s)
430 (&appData.colorShout)[cc] = strdup(s);
434 ParseBoardSize (void *addr, char *name)
436 appData.boardSize = strdup(name);
441 { // In XBoard the sound-playing program takes care of obtaining the actual sound
445 SetCommPortDefaults ()
446 { // for now, this is a no-op, as the corresponding option does not exist in XBoard
449 // [HGM] args: these three cases taken out to stay in front-end
451 SaveFontArg (FILE *f, ArgDescriptor *ad)
454 int i, n = (int)(intptr_t)ad->argLoc;
456 case 0: // CLOCK_FONT
457 name = appData.clockFont;
459 case 1: // MESSAGE_FONT
462 case 2: // COORD_FONT
463 name = appData.coordFont;
468 for(i=0; i<NUM_SIZES; i++) // [HGM] font: current font becomes standard for current size
469 if(sizeDefaults[i].squareSize == squareSize) { // only for standard sizes!
470 fontTable[n][squareSize] = strdup(name);
471 fontValid[n][squareSize] = True;
474 for(i=0; i<MAX_SIZE; i++) if(fontValid[n][i]) // [HGM] font: store all standard fonts
475 fprintf(f, OPTCHAR "%s" SEPCHAR "\"size%d:%s\"\n", ad->argName, i, fontTable[n][i]);
480 { // nothing to do, as the sounds are at all times represented by their text-string names already
484 SaveAttribsArg (FILE *f, ArgDescriptor *ad)
485 { // here the "argLoc" defines a table index. It could have contained the 'ta' pointer itself, though
486 fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, (&appData.colorShout)[(int)(intptr_t)ad->argLoc]);
490 SaveColor (FILE *f, ArgDescriptor *ad)
491 { // in WinBoard the color is an int and has to be converted to text. In X it would be a string already?
492 if(colorVariable[(int)(intptr_t)ad->argLoc])
493 fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, *(char**)colorVariable[(int)(intptr_t)ad->argLoc]);
497 SaveBoardSize (FILE *f, char *name, void *addr)
498 { // wrapper to shield back-end from BoardSize & sizeInfo
499 fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", name, appData.boardSize);
503 ParseCommPortSettings (char *s)
504 { // no such option in XBoard (yet)
510 GetActualPlacement (GtkWidget *shell, WindowPlacement *wp)
514 gtk_widget_get_allocation(shell, &a);
515 gtk_window_get_position(GTK_WINDOW(shell), &a.x, &a.y);
519 wp->height = a.height;
520 //printf("placement: (%d,%d) %dx%d\n", a.x, a.y, a.width, a.height);
521 frameX = 3; frameY = 3; // remember to decide if windows touch
526 { // wrapper to shield use of window handles from back-end (make addressible by number?)
527 // In XBoard this will have to wait until awareness of window parameters is implemented
528 GetActualPlacement(shellWidget, &wpMain);
529 if(shellUp[EngOutDlg]) GetActualPlacement(shells[EngOutDlg], &wpEngineOutput);
530 if(shellUp[HistoryDlg]) GetActualPlacement(shells[HistoryDlg], &wpMoveHistory);
531 if(shellUp[EvalGraphDlg]) GetActualPlacement(shells[EvalGraphDlg], &wpEvalGraph);
532 if(shellUp[GameListDlg]) GetActualPlacement(shells[GameListDlg], &wpGameList);
533 if(shellUp[CommentDlg]) GetActualPlacement(shells[CommentDlg], &wpComment);
534 if(shellUp[TagsDlg]) GetActualPlacement(shells[TagsDlg], &wpTags);
538 PrintCommPortSettings (FILE *f, char *name)
539 { // This option does not exist in XBoard
543 EnsureOnScreen (int *x, int *y, int minX, int minY)
550 { // [HGM] args: allows testing if main window is realized from back-end
551 return DialogExists(BoardWindow);
555 PopUpStartupDialog ()
556 { // start menu not implemented in XBoard
560 ConvertToLine (int argc, char **argv)
562 static char line[128*1024], buf[1024];
566 for(i=1; i<argc; i++)
568 if( (strchr(argv[i], ' ') || strchr(argv[i], '\n') ||strchr(argv[i], '\t') || argv[i][0] == NULLCHAR)
569 && argv[i][0] != '{' )
570 snprintf(buf, sizeof(buf)/sizeof(buf[0]), "{%s} ", argv[i]);
572 snprintf(buf, sizeof(buf)/sizeof(buf[0]), "%s ", argv[i]);
573 strncat(line, buf, 128*1024 - strlen(line) - 1 );
576 line[strlen(line)-1] = NULLCHAR;
580 //--------------------------------------------------------------------------------------------
585 ResizeBoardWindow (int w, int h, int inhibit)
588 if(clockKludge) return; // ignore as long as clock does not have final height
589 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
590 w += marginW + 1; // [HGM] not sure why the +1 is (sometimes) needed...
591 h += marginH + a.height + 1;
592 gtk_window_resize(GTK_WINDOW(shellWidget), w, h);
597 { // dummy, as the GTK code does not make colors in advance
602 InitializeFonts (int clockFontPxlSize, int coordFontPxlSize, int fontPxlSize)
603 { // determine what fonts to use, and create them
608 if(!fontIsSet[CLOCK_FONT] && fontValid[CLOCK_FONT][squareSize])
609 appData.clockFont = fontTable[CLOCK_FONT][squareSize];
610 if(!fontIsSet[MESSAGE_FONT] && fontValid[MESSAGE_FONT][squareSize])
611 appData.font = fontTable[MESSAGE_FONT][squareSize];
612 if(!fontIsSet[COORD_FONT] && fontValid[COORD_FONT][squareSize])
613 appData.coordFont = fontTable[COORD_FONT][squareSize];
616 appData.font = InsertPxlSize(appData.font, fontPxlSize);
617 appData.clockFont = InsertPxlSize(appData.clockFont, clockFontPxlSize);
618 appData.coordFont = InsertPxlSize(appData.coordFont, coordFontPxlSize);
619 fontSet = CreateFontSet(appData.font);
620 clockFontSet = CreateFontSet(appData.clockFont);
622 /* For the coordFont, use the 0th font of the fontset. */
623 XFontSet coordFontSet = CreateFontSet(appData.coordFont);
624 XFontStruct **font_struct_list;
625 XFontSetExtents *fontSize;
626 char **font_name_list;
627 XFontsOfFontSet(coordFontSet, &font_struct_list, &font_name_list);
628 coordFontID = XLoadFont(xDisplay, font_name_list[0]);
629 coordFontStruct = XQueryFont(xDisplay, coordFontID);
630 fontSize = XExtentsOfFontSet(fontSet); // [HGM] figure out how much vertical space font takes
631 textHeight = fontSize->max_logical_extent.height + 5; // add borderWidth
634 appData.font = FindFont(appData.font, fontPxlSize);
635 appData.clockFont = FindFont(appData.clockFont, clockFontPxlSize);
636 appData.coordFont = FindFont(appData.coordFont, coordFontPxlSize);
637 clockFontID = XLoadFont(xDisplay, appData.clockFont);
638 clockFontStruct = XQueryFont(xDisplay, clockFontID);
639 coordFontID = XLoadFont(xDisplay, appData.coordFont);
640 coordFontStruct = XQueryFont(xDisplay, coordFontID);
641 // textHeight in !NLS mode!
643 countFontID = coordFontID; // [HGM] holdings
644 countFontStruct = coordFontStruct;
646 xdb = XtDatabase(xDisplay);
648 XrmPutLineResource(&xdb, "*international: True");
649 vTo.size = sizeof(XFontSet);
650 vTo.addr = (XtPointer) &fontSet;
651 XrmPutResource(&xdb, "*fontSet", XtRFontSet, &vTo);
653 XrmPutStringResource(&xdb, "*font", appData.font);
664 case ArgInt: p = " N"; break;
665 case ArgString: p = " STR"; break;
666 case ArgBoolean: p = " TF"; break;
667 case ArgSettingsFilename:
668 case ArgBackupSettingsFile:
669 case ArgFilename: p = " FILE"; break;
670 case ArgX: p = " Nx"; break;
671 case ArgY: p = " Ny"; break;
672 case ArgAttribs: p = " TEXTCOL"; break;
673 case ArgColor: p = " COL"; break;
674 case ArgFont: p = " FONT"; break;
675 case ArgBoardSize: p = " SIZE"; break;
676 case ArgFloat: p = " FLOAT"; break;
681 case ArgCommSettings:
692 ArgDescriptor *q, *p = argDescriptors+5;
693 printf("\nXBoard accepts the following options:\n"
694 "(N = integer, TF = true or false, STR = text string, FILE = filename,\n"
695 " Nx, Ny = relative coordinates, COL = color, FONT = X-font spec,\n"
696 " SIZE = board-size spec(s)\n"
697 " Within parentheses are short forms, or options to set to true or false.\n"
698 " Persistent options (saved in the settings file) are marked with *)\n\n");
700 if(p->argType == ArgCommSettings) { p++; continue; } // XBoard has no comm port
701 snprintf(buf+len, MSG_SIZ, "-%s%s", p->argName, PrintArg(p->argType));
702 if(p->save) strcat(buf+len, "*");
703 for(q=p+1; q->argLoc == p->argLoc; q++) {
704 if(q->argName[0] == '-') continue;
705 strcat(buf+len, q == p+1 ? " (" : " ");
706 sprintf(buf+strlen(buf), "-%s%s", q->argName, PrintArg(q->argType));
708 if(q != p+1) strcat(buf+len, ")");
710 if(len > 39) len = 0, printf("%s\n", buf); else while(len < 39) buf[len++] = ' ';
713 if(len) buf[len] = NULLCHAR, printf("%s\n", buf);
717 SlaveResize (Option *opt)
719 static int slaveW, slaveH, w, h;
722 gtk_widget_get_allocation(shells[DummyDlg], &a);
723 w = a.width; h = a.height;
724 gtk_widget_get_allocation(opt->handle, &a);
725 slaveW = w - opt->max; // [HGM] needed to set new shellWidget size when we resize board
726 slaveH = h - a.height + 13;
728 gtk_window_resize(GTK_WINDOW(shells[DummyDlg]), slaveW + opt->max, slaveH + opt->value);
732 static char clickedFile[MSG_SIZ];
736 StartNewXBoard(GtkosxApplication *app, gchar *path, gpointer user_data)
737 { // handler of OSX OpenFile signal, which sends us the filename of clicked file or first argument
738 if(suppress) { // we just started XBoard without arguments
739 strncpy(clickedFile, path, MSG_SIZ); // remember file name, but otherwise ignore
740 } else { // we are running something presumably useful
742 snprintf(buf, MSG_SIZ, "open -n -a \"xboard\" --args \"%s\"", path);
743 system(buf); // start new instance on this file
750 main (int argc, char **argv)
752 int i, clockFontPxlSize, coordFontPxlSize, fontPxlSize;
753 int boardWidth, boardHeight, w, h;
755 int forceMono = False;
757 srandom(time(0)); // [HGM] book: make random truly random
759 setbuf(stdout, NULL);
760 setbuf(stderr, NULL);
763 if(argc > 1 && (!strcmp(argv[1], "-v" ) || !strcmp(argv[1], "--version" ))) {
764 printf("%s version %s\n", PACKAGE_NAME, PACKAGE_VERSION);
768 if(argc > 1 && !strcmp(argv[1], "--help" )) {
774 gtk_init (&argc, &argv);
776 { // prepare to catch OX OpenFile signal, which will tell us the clicked file
777 GtkosxApplication *theApp = g_object_new(GTKOSX_TYPE_APPLICATION, NULL);
778 dataDir = gtkosx_application_get_bundle_path();
779 g_signal_connect(theApp, "NSApplicationOpenFile", G_CALLBACK(StartNewXBoard), NULL);
780 // we must call application ready before we can get the signal,
781 // and supply a (dummy) menu bar before that, to avoid problems with dual apples in it
782 gtkosx_application_set_menu_bar(theApp, GTK_MENU_SHELL(gtk_menu_bar_new()));
783 gtkosx_application_ready(theApp);
784 suppress = (argc == 1 || argc > 1 && argv[1][00] != '-'); // OSX sends signal even if name was already argv[1]!
785 if(argc == 1) { // called without args: OSX open-file signal might follow
786 static char *fakeArgv[3] = {NULL, clickedFile, NULL};
787 usleep(10000); // wait 10 msec (and hope this is long enough).
788 while(gtk_events_pending())
789 gtk_main_iteration(); // process all events that came in upto now
790 suppress = 0; // future open-file signals should start new instance
791 if(clickedFile[0]) { // we were sent an open-file signal with filename!
792 fakeArgv[0] = argv[0];
793 argc = 2; argv = fakeArgv; // fake that we were called as "xboard filename"
799 /* set up keyboard accelerators group */
800 GtkAccelerators = gtk_accel_group_new();
802 programName = strrchr(argv[0], '/');
803 if (programName == NULL)
804 programName = argv[0];
809 // if (appData.debugMode) {
810 // fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
813 bindtextdomain(PACKAGE, LOCALEDIR);
814 bind_textdomain_codeset(PACKAGE, "UTF-8"); // needed when creating markup for the clocks
818 appData.boardSize = "";
819 InitAppData(ConvertToLine(argc, argv));
821 if (p == NULL) p = "/tmp";
822 i = strlen(p) + strlen("/.xboardXXXXXx.pgn") + 1;
823 gameCopyFilename = (char*) malloc(i);
824 gamePasteFilename = (char*) malloc(i);
825 snprintf(gameCopyFilename,i, "%s/.xboard%05uc.pgn", p, getpid());
826 snprintf(gamePasteFilename,i, "%s/.xboard%05up.pgn", p, getpid());
828 { // [HGM] initstring: kludge to fix bad bug. expand '\n' characters in init string and computer string.
829 static char buf[MSG_SIZ];
830 EscapeExpand(buf, appData.firstInitString);
831 appData.firstInitString = strdup(buf);
832 EscapeExpand(buf, appData.secondInitString);
833 appData.secondInitString = strdup(buf);
834 EscapeExpand(buf, appData.firstComputerString);
835 appData.firstComputerString = strdup(buf);
836 EscapeExpand(buf, appData.secondComputerString);
837 appData.secondComputerString = strdup(buf);
840 if ((chessDir = (char *) getenv("CHESSDIR")) == NULL) {
843 if (chdir(chessDir) != 0) {
844 fprintf(stderr, _("%s: can't cd to CHESSDIR: "), programName);
850 if (appData.debugMode && appData.nameOfDebugFile && strcmp(appData.nameOfDebugFile, "stderr")) {
851 /* [DM] debug info to file [HGM] make the filename a command-line option, and allow it to remain stderr */
852 if ((debugFP = fopen(appData.nameOfDebugFile, "w")) == NULL) {
853 printf(_("Failed to open file '%s'\n"), appData.nameOfDebugFile);
856 setbuf(debugFP, NULL);
860 if (appData.debugMode) {
861 fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
865 /* [HGM,HR] make sure board size is acceptable */
866 if(appData.NrFiles > BOARD_FILES ||
867 appData.NrRanks > BOARD_RANKS )
868 DisplayFatalError(_("Recompile with larger BOARD_RANKS or BOARD_FILES to support this size"), 0, 2);
871 /* This feature does not work; animation needs a rewrite */
872 appData.highlightDragging = FALSE;
876 gameInfo.variant = StringToVariant(appData.variant);
880 * determine size, based on supplied or remembered -size, or screen size
882 if (isdigit(appData.boardSize[0])) {
883 i = sscanf(appData.boardSize, "%d,%d,%d,%d,%d,%d,%d", &squareSize,
884 &lineGap, &clockFontPxlSize, &coordFontPxlSize,
885 &fontPxlSize, &smallLayout, &tinyLayout);
887 fprintf(stderr, _("%s: bad boardSize syntax %s\n"),
888 programName, appData.boardSize);
892 /* Find some defaults; use the nearest known size */
893 SizeDefaults *szd, *nearest;
894 int distance = 99999;
895 nearest = szd = sizeDefaults;
896 while (szd->name != NULL) {
897 if (abs(szd->squareSize - squareSize) < distance) {
899 distance = abs(szd->squareSize - squareSize);
900 if (distance == 0) break;
904 if (i < 2) lineGap = nearest->lineGap;
905 if (i < 3) clockFontPxlSize = nearest->clockFontPxlSize;
906 if (i < 4) coordFontPxlSize = nearest->coordFontPxlSize;
907 if (i < 5) fontPxlSize = nearest->fontPxlSize;
908 if (i < 6) smallLayout = nearest->smallLayout;
909 if (i < 7) tinyLayout = nearest->tinyLayout;
912 SizeDefaults *szd = sizeDefaults;
913 if (*appData.boardSize == NULLCHAR) {
914 GdkScreen *screen = gtk_window_get_screen(GTK_WINDOW(mainwindow));
915 guint screenwidth = gdk_screen_get_width(screen);
916 guint screenheight = gdk_screen_get_height(screen);
917 while (screenwidth < szd->minScreenSize ||
918 screenheight < szd->minScreenSize) {
921 if (szd->name == NULL) szd--;
922 appData.boardSize = strdup(szd->name); // [HGM] settings: remember name for saving settings
924 while (szd->name != NULL &&
925 StrCaseCmp(szd->name, appData.boardSize) != 0) szd++;
926 if (szd->name == NULL) {
927 fprintf(stderr, _("%s: unrecognized boardSize name %s\n"),
928 programName, appData.boardSize);
932 squareSize = szd->squareSize;
933 lineGap = szd->lineGap;
934 clockFontPxlSize = szd->clockFontPxlSize;
935 coordFontPxlSize = szd->coordFontPxlSize;
936 fontPxlSize = szd->fontPxlSize;
937 smallLayout = szd->smallLayout;
938 tinyLayout = szd->tinyLayout;
939 // [HGM] font: use defaults from settings file if available and not overruled
942 defaultLineGap = lineGap;
943 if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
945 /* [HR] height treated separately (hacked) */
946 boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
947 boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
950 * Determine what fonts to use.
953 InitializeFonts(clockFontPxlSize, coordFontPxlSize, fontPxlSize);
957 * Detect if there are not enough colors available and adapt.
960 if (DefaultDepth(xDisplay, xScreen) <= 2) {
961 appData.monoMode = True;
965 forceMono = MakeColors();
968 fprintf(stderr, _("%s: too few colors available; trying monochrome mode\n"),
970 appData.monoMode = True;
973 ParseIcsTextColors();
979 layoutName = "tinyLayout";
980 } else if (smallLayout) {
981 layoutName = "smallLayout";
983 layoutName = "normalLayout";
986 wpMain.width = -1; // prevent popup sizes window
987 optList = BoardPopUp(squareSize, lineGap, (void*)
997 InitDrawingHandle(optList + W_BOARD);
998 shellWidget = shells[BoardWindow];
999 currBoard = &optList[W_BOARD];
1000 boardWidget = optList[W_BOARD].handle;
1001 menuBarWidget = optList[W_MENU].handle;
1002 dropMenu = optList[W_DROP].handle;
1003 titleWidget = optList[optList[W_TITLE].type != -1 ? W_TITLE : W_SMALL].handle;
1005 formWidget = XtParent(boardWidget);
1006 XtSetArg(args[0], XtNbackground, &timerBackgroundPixel);
1007 XtSetArg(args[1], XtNforeground, &timerForegroundPixel);
1008 XtGetValues(optList[W_WHITE].handle, args, 2);
1009 if (appData.showButtonBar) { // can't we use timer pixels for this? (Or better yet, just black & white?)
1010 XtSetArg(args[0], XtNbackground, &buttonBackgroundPixel);
1011 XtSetArg(args[1], XtNforeground, &buttonForegroundPixel);
1012 XtGetValues(optList[W_PAUSE].handle, args, 2);
1016 // [HGM] it seems the layout code ends here, but perhaps the color stuff is size independent and would
1017 // not need to go into InitDrawingSizes().
1021 // add accelerators to main shell
1022 gtk_window_add_accel_group(GTK_WINDOW(shellWidget), GtkAccelerators);
1025 * Create an icon. (Use two icons, to indicate whther it is white's or black's turn.)
1027 WhiteIcon = gdk_pixbuf_new_from_file(SVGDIR "/icon_white.svg", NULL);
1028 BlackIcon = gdk_pixbuf_new_from_file(SVGDIR "/icon_black.svg", NULL);
1029 mainwindowIcon = WhiteIcon;
1030 gtk_window_set_icon(GTK_WINDOW(shellWidget), mainwindowIcon);
1034 * Create a cursor for the board widget.
1037 window_attributes.cursor = XCreateFontCursor(xDisplay, XC_hand2);
1038 XChangeWindowAttributes(xDisplay, xBoardWindow,
1039 CWCursor, &window_attributes);
1043 * Inhibit shell resizing.
1046 shellArgs[0].value = (XtArgVal) &w;
1047 shellArgs[1].value = (XtArgVal) &h;
1048 XtGetValues(shellWidget, shellArgs, 2);
1049 shellArgs[4].value = shellArgs[2].value = w;
1050 shellArgs[5].value = shellArgs[3].value = h;
1051 // XtSetValues(shellWidget, &shellArgs[2], 4);
1054 // Note: We cannot do sensible sizing here, because the height of the clock widget is not yet known
1055 // It wil only become known asynchronously, when we first write a string into it.
1056 // This will then change the clock widget height, which triggers resizing the top-level window
1057 // and a configure event. Only then can we know the total height of the top-level window,
1058 // and calculate the height we need. The clockKludge flag suppresses all resizing until
1059 // that moment comes, after which the configure event-handler handles it through a (delayed) DragProg.
1062 gtk_widget_get_allocation(shells[BoardWindow], &a);
1063 w = a.width; h = a.height;
1064 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1065 clockKludge = hc = a.height;
1066 gtk_widget_get_allocation(boardWidget, &a);
1067 marginW = w - boardWidth; // [HGM] needed to set new shellWidget size when we resize board
1068 marginH = h - a.height - hc; // subtract current clock height, so it can be added back dynamically
1074 if(appData.logoSize)
1075 { // locate and read user logo
1077 snprintf(buf, MSG_SIZ, "%s/%s.png", appData.logoDir, UserName());
1078 ASSIGN(userLogo, buf);
1081 if (appData.animate || appData.animateDragging)
1084 g_signal_connect(shells[BoardWindow], "key-press-event", G_CALLBACK(KeyPressProc), NULL);
1085 g_signal_connect(shells[BoardWindow], "configure-event", G_CALLBACK(EventProc), NULL);
1087 /* [AS] Restore layout */
1088 if( wpMoveHistory.visible ) {
1092 if( wpEvalGraph.visible )
1097 if( wpEngineOutput.visible ) {
1098 EngineOutputPopUp();
1103 if (errorExitStatus == -1) {
1104 if (appData.icsActive) {
1105 /* We now wait until we see "login:" from the ICS before
1106 sending the logon script (problems with timestamp otherwise) */
1107 /*ICSInitScript();*/
1108 if (appData.icsInputBox) ICSInputBoxPopUp();
1112 signal(SIGWINCH, TermSizeSigHandler);
1114 signal(SIGINT, IntSigHandler);
1115 signal(SIGTERM, IntSigHandler);
1116 if (*appData.cmailGameName != NULLCHAR) {
1117 signal(SIGUSR1, CmailSigHandler);
1121 gameInfo.boardWidth = 0; // [HGM] pieces: kludge to ensure InitPosition() calls InitDrawingSizes()
1124 // XtSetKeyboardFocus(shellWidget, formWidget);
1126 XSetInputFocus(xDisplay, XtWindow(formWidget), RevertToPointerRoot, CurrentTime);
1129 /* check for GTK events and process them */
1132 gtk_main_iteration();
1135 if (appData.debugMode) fclose(debugFP); // [DM] debug
1140 TermSizeSigHandler (int sig)
1146 IntSigHandler (int sig)
1152 CmailSigHandler (int sig)
1157 signal(SIGUSR1, SIG_IGN); /* suspend handler */
1159 /* Activate call-back function CmailSigHandlerCallBack() */
1160 OutputToProcess(cmailPR, (char *)(&dummy), sizeof(int), &error);
1162 signal(SIGUSR1, CmailSigHandler); /* re-activate handler */
1166 CmailSigHandlerCallBack (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1169 ReloadCmailMsgEvent(TRUE); /* Reload cmail msg */
1171 /**** end signal code ****/
1174 #define Abs(n) ((n)<0 ? -(n) : (n))
1178 InsertPxlSize (char *pattern, int targetPxlSize)
1180 char *base_fnt_lst, strInt[12], *p, *q;
1181 int alternatives, i, len, strIntLen;
1184 * Replace the "*" (if present) in the pixel-size slot of each
1185 * alternative with the targetPxlSize.
1189 while ((p = strchr(p, ',')) != NULL) {
1193 snprintf(strInt, sizeof(strInt), "%d", targetPxlSize);
1194 strIntLen = strlen(strInt);
1195 base_fnt_lst = calloc(1, strlen(pattern) + strIntLen * alternatives + 1);
1199 while (alternatives--) {
1200 char *comma = strchr(p, ',');
1201 for (i=0; i<14; i++) {
1202 char *hyphen = strchr(p, '-');
1204 if (comma && hyphen > comma) break;
1205 len = hyphen + 1 - p;
1206 if (i == 7 && *p == '*' && len == 2) {
1208 memcpy(q, strInt, strIntLen);
1218 len = comma + 1 - p;
1225 return base_fnt_lst;
1230 CreateFontSet (char *base_fnt_lst)
1233 char **missing_list;
1237 fntSet = XCreateFontSet(xDisplay, base_fnt_lst,
1238 &missing_list, &missing_count, &def_string);
1239 if (appData.debugMode) {
1241 XFontStruct **font_struct_list;
1242 char **font_name_list;
1243 fprintf(debugFP, "Requested font set for list %s\n", base_fnt_lst);
1245 fprintf(debugFP, " got list %s, locale %s\n",
1246 XBaseFontNameListOfFontSet(fntSet),
1247 XLocaleOfFontSet(fntSet));
1248 count = XFontsOfFontSet(fntSet, &font_struct_list, &font_name_list);
1249 for (i = 0; i < count; i++) {
1250 fprintf(debugFP, " got charset %s\n", font_name_list[i]);
1253 for (i = 0; i < missing_count; i++) {
1254 fprintf(debugFP, " missing charset %s\n", missing_list[i]);
1257 if (fntSet == NULL) {
1258 fprintf(stderr, _("Unable to create font set for %s.\n"), base_fnt_lst);
1264 #else // not ENABLE_NLS
1266 * Find a font that matches "pattern" that is as close as
1267 * possible to the targetPxlSize. Prefer fonts that are k
1268 * pixels smaller to fonts that are k pixels larger. The
1269 * pattern must be in the X Consortium standard format,
1270 * e.g. "-*-helvetica-bold-r-normal--*-*-*-*-*-*-*-*".
1271 * The return value should be freed with XtFree when no
1275 FindFont (char *pattern, int targetPxlSize)
1277 char **fonts, *p, *best, *scalable, *scalableTail;
1278 int i, j, nfonts, minerr, err, pxlSize;
1281 fonts = XListFonts(xDisplay, pattern, 999999, &nfonts);
1283 fprintf(stderr, _("%s: no fonts match pattern %s\n"),
1284 programName, pattern);
1291 for (i=0; i<nfonts; i++) {
1294 if (*p != '-') continue;
1296 if (*p == NULLCHAR) break;
1297 if (*p++ == '-') j++;
1299 if (j < 7) continue;
1302 scalable = fonts[i];
1305 err = pxlSize - targetPxlSize;
1306 if (Abs(err) < Abs(minerr) ||
1307 (minerr > 0 && err < 0 && -err == minerr)) {
1313 if (scalable && Abs(minerr) > appData.fontSizeTolerance) {
1314 /* If the error is too big and there is a scalable font,
1315 use the scalable font. */
1316 int headlen = scalableTail - scalable;
1317 p = (char *) XtMalloc(strlen(scalable) + 10);
1318 while (isdigit(*scalableTail)) scalableTail++;
1319 sprintf(p, "%.*s%d%s", headlen, scalable, targetPxlSize, scalableTail);
1321 p = (char *) XtMalloc(strlen(best) + 2);
1322 safeStrCpy(p, best, strlen(best)+1 );
1324 if (appData.debugMode) {
1325 fprintf(debugFP, "resolved %s at pixel size %d\n to %s\n",
1326 pattern, targetPxlSize, p);
1328 XFreeFontNames(fonts);
1335 EnableNamedMenuItem (char *menuRef, int state)
1337 MenuItem *item = MenuNameToItem(menuRef);
1339 if(item) gtk_widget_set_sensitive(item->handle, state);
1343 EnableButtonBar (int state)
1346 XtSetSensitive(optList[W_BUTTON].handle, state);
1352 SetMenuEnables (Enables *enab)
1354 while (enab->name != NULL) {
1355 EnableNamedMenuItem(enab->name, enab->value);
1360 gboolean KeyPressProc(window, eventkey, data)
1362 GdkEventKey *eventkey;
1366 MoveTypeInProc(eventkey); // pop up for typed in moves
1369 /* check for other key values */
1370 switch(eventkey->keyval) {
1382 KeyBindingProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1383 { // [HGM] new method of key binding: specify MenuItem(FlipView) in stead of FlipViewProc in translation string
1385 if(*nprms == 0) return;
1386 item = MenuNameToItem(prms[0]);
1387 if(item) ((MenuProc *) item->proc) ();
1401 for (i=0; i<sizeof(dmEnables)/sizeof(DropMenuEnables); i++) {
1402 entry = XtNameToWidget(dropMenu, dmEnables[i].widget);
1403 p = strchr(gameMode == IcsPlayingWhite ? white_holding : black_holding,
1404 dmEnables[i].piece);
1405 XtSetSensitive(entry, p != NULL || !appData.testLegality
1406 /*!!temp:*/ || (gameInfo.variant == VariantCrazyhouse
1407 && !appData.icsActive));
1409 while (p && *p++ == dmEnables[i].piece) count++;
1410 snprintf(label, sizeof(label), "%s %d", dmEnables[i].widget, count);
1412 XtSetArg(args[j], XtNlabel, label); j++;
1413 XtSetValues(entry, args, j);
1419 do_flash_delay (unsigned long msec)
1425 FlashDelay (int flash_delay)
1427 if(flash_delay) do_flash_delay(flash_delay);
1431 Fraction (int x, int start, int stop)
1433 double f = ((double) x - start)/(stop - start);
1434 if(f > 1.) f = 1.; else if(f < 0.) f = 0.;
1438 static WindowPlacement wpNew;
1441 CoDrag (GtkWidget *sh, WindowPlacement *wp)
1443 int touch=0, fudge = 2, f = 2;
1444 GetActualPlacement(sh, wp);
1445 if(abs(wpMain.x + wpMain.width + 2*frameX - f - wp->x) < fudge) touch = 1; else // right touch
1446 if(abs(wp->x + wp->width + 2*frameX + f - wpMain.x) < fudge) touch = 2; else // left touch
1447 if(abs(wpMain.y + wpMain.height + frameX - f + frameY - wp->y) < fudge) touch = 3; else // bottom touch
1448 if(abs(wp->y + wp->height + frameX + frameY + f - wpMain.y) < fudge) touch = 4; // top touch
1449 //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);
1450 if(!touch ) return; // only windows that touch co-move
1451 if(touch < 3 && wpNew.height != wpMain.height) { // left or right and height changed
1452 int heightInc = wpNew.height - wpMain.height;
1453 double fracTop = Fraction(wp->y, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1454 double fracBot = Fraction(wp->y + wp->height + frameX + frameY + 1, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1455 wp->y += fracTop * heightInc;
1456 heightInc = (int) (fracBot * heightInc) - (int) (fracTop * heightInc);
1458 if(heightInc) XtSetArg(args[j], XtNheight, wp->height + heightInc), j++;
1460 wp->height += heightInc;
1461 } else if(touch > 2 && wpNew.width != wpMain.width) { // top or bottom and width changed
1462 int widthInc = wpNew.width - wpMain.width;
1463 double fracLeft = Fraction(wp->x, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1464 double fracRght = Fraction(wp->x + wp->width + 2*frameX + 1, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1465 wp->y += fracLeft * widthInc;
1466 widthInc = (int) (fracRght * widthInc) - (int) (fracLeft * widthInc);
1468 if(widthInc) XtSetArg(args[j], XtNwidth, wp->width + widthInc), j++;
1470 wp->width += widthInc;
1472 wp->x += wpNew.x - wpMain.x;
1473 wp->y += wpNew.y - wpMain.y;
1474 if(touch == 1) wp->x += wpNew.width - wpMain.width; else
1475 if(touch == 3) wp->y += wpNew.height - wpMain.height;
1477 XtSetArg(args[j], XtNx, wp->x); j++;
1478 XtSetArg(args[j], XtNy, wp->y); j++;
1479 XtSetValues(sh, args, j);
1481 gtk_window_move(GTK_WINDOW(sh), wp->x, wp->y);
1482 //printf("moved to (%d,%d)\n", wp->x, wp->y);
1483 gtk_window_resize(GTK_WINDOW(sh), wp->width, wp->height);
1487 ReSize (WindowPlacement *wp)
1490 int sqx, sqy, w, h, hc, lg = lineGap;
1491 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1492 hc = a.height; // clock height can depend on single / double line clock text!
1493 if(clockKludge == a.height) return; // wait for clock to get final size at startup
1494 if(clockKludge) { // clock height OK now; calculate desired initial board height
1496 wp->height = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + marginH + hc;
1498 if(wp->width == wpMain.width && wp->height == wpMain.height) return; // not sized
1499 sqx = (wp->width - lg - marginW) / BOARD_WIDTH - lg;
1500 sqy = (wp->height - lg - marginH - hc) / BOARD_HEIGHT - lg;
1501 if(sqy < sqx) sqx = sqy;
1502 if(appData.overrideLineGap < 0) { // do second iteration with adjusted lineGap
1503 lg = lineGap = sqx < 37 ? 1 : sqx < 59 ? 2 : sqx < 116 ? 3 : 4;
1504 sqx = (wp->width - lg - marginW) / BOARD_WIDTH - lg;
1505 sqy = (wp->height - lg - marginH - hc) / BOARD_HEIGHT - lg;
1506 if(sqy < sqx) sqx = sqy;
1508 if(sqx != squareSize) {
1509 //printf("new sq size %d (%dx%d)\n", sqx, wp->width, wp->height);
1510 squareSize = sqx; // adopt new square size
1511 CreatePNGPieces(); // make newly scaled pieces
1512 InitDrawingSizes(0, 0); // creates grid etc.
1513 } else ResizeBoardWindow(BOARD_WIDTH * (squareSize + lineGap) + lineGap, BOARD_HEIGHT * (squareSize + lineGap) + lineGap, 0);
1514 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
1515 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
1516 if(optList[W_BOARD].max > w) optList[W_BOARD].max = w;
1517 if(optList[W_BOARD].value > h) optList[W_BOARD].value = h;
1520 static guint delayedDragTag = 0;
1529 // GetActualPlacement(shellWidget, &wpNew);
1530 if(wpNew.x == wpMain.x && wpNew.y == wpMain.y && // not moved
1531 wpNew.width == wpMain.width && wpNew.height == wpMain.height) { // not sized
1532 busy = 0; return; // false alarm
1535 if(appData.useStickyWindows) {
1536 if(shellUp[EngOutDlg]) CoDrag(shells[EngOutDlg], &wpEngineOutput);
1537 if(shellUp[HistoryDlg]) CoDrag(shells[HistoryDlg], &wpMoveHistory);
1538 if(shellUp[EvalGraphDlg]) CoDrag(shells[EvalGraphDlg], &wpEvalGraph);
1539 if(shellUp[GameListDlg]) CoDrag(shells[GameListDlg], &wpGameList);
1542 DrawPosition(True, NULL);
1543 if(delayedDragTag) g_source_remove(delayedDragTag);
1544 delayedDragTag = 0; // now drag executed, make sure next DelayedDrag will not cancel timer event (which could now be used by other)
1551 //printf("old timr = %d\n", delayedDragTag);
1552 if(delayedDragTag) g_source_remove(delayedDragTag);
1553 delayedDragTag = g_timeout_add( 200, (GSourceFunc) DragProc, NULL);
1554 //printf("new timr = %d\n", delayedDragTag);
1558 EventProc (GtkWidget *widget, GdkEvent *event, gpointer g)
1560 //printf("event proc (%d,%d) %dx%d\n", event->configure.x, event->configure.y, event->configure.width, event->configure.height);
1562 wpNew.x = event->configure.x;
1563 wpNew.y = event->configure.y;
1564 wpNew.width = event->configure.width;
1565 wpNew.height = event->configure.height;
1566 DelayedDrag(); // as long as events keep coming in faster than 50 msec, they destroy each other
1572 /* Disable all user input other than deleting the window */
1573 static int frozen = 0;
1579 /* Grab by a widget that doesn't accept input */
1580 gtk_grab_add(optList[W_MESSG].handle);
1584 /* Undo a FreezeUI */
1588 if (!frozen) return;
1589 gtk_grab_remove(optList[W_MESSG].handle);
1596 static int oldPausing = FALSE;
1597 static GameMode oldmode = (GameMode) -1;
1599 if (!boardWidget) return;
1601 if (pausing != oldPausing) {
1602 oldPausing = pausing;
1603 MarkMenuItem("Mode.Pause", pausing);
1605 if (appData.showButtonBar) {
1606 /* Always toggle, don't set. Previous code messes up when
1607 invoked while the button is pressed, as releasing it
1608 toggles the state again. */
1610 gdk_color_parse( pausing ? "#808080" : "#F0F0F0", &color );
1611 gtk_widget_modify_bg ( GTK_WIDGET(optList[W_PAUSE].handle), GTK_STATE_NORMAL, &color );
1615 wname = ModeToWidgetName(oldmode);
1616 if (wname != NULL) {
1617 MarkMenuItem(wname, False);
1619 wname = ModeToWidgetName(gameMode);
1620 if (wname != NULL) {
1621 MarkMenuItem(wname, True);
1624 MarkMenuItem("Mode.MachineMatch", matchMode && matchGame < appData.matchGames);
1626 /* Maybe all the enables should be handled here, not just this one */
1627 EnableNamedMenuItem("Mode.Training", gameMode == Training || gameMode == PlayFromGameFile);
1629 DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
1634 * Button/menu procedures
1637 void CopyFileToClipboard(gchar *filename)
1639 gchar *selection_tmp;
1643 FILE* f = fopen(filename, "r");
1646 if (f == NULL) return;
1650 selection_tmp = g_try_malloc(len + 1);
1651 if (selection_tmp == NULL) {
1652 printf("Malloc failed in CopyFileToClipboard\n");
1655 count = fread(selection_tmp, 1, len, f);
1658 g_free(selection_tmp);
1661 selection_tmp[len] = NULLCHAR; // file is now in selection_tmp
1663 // copy selection_tmp to clipboard
1664 GdkDisplay *gdisp = gdk_display_get_default();
1666 g_free(selection_tmp);
1669 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1670 gtk_clipboard_set_text(cb, selection_tmp, -1);
1671 g_free(selection_tmp);
1675 CopySomething (char *src)
1677 GdkDisplay *gdisp = gdk_display_get_default();
1679 if(!src) { CopyFileToClipboard(gameCopyFilename); return; }
1680 if (gdisp == NULL) return;
1681 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1682 gtk_clipboard_set_text(cb, src, -1);
1686 PastePositionProc ()
1688 GdkDisplay *gdisp = gdk_display_get_default();
1692 if (gdisp == NULL) return;
1693 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1694 fenstr = gtk_clipboard_wait_for_text(cb);
1695 if (fenstr==NULL) return; // nothing had been selected to copy
1696 EditPositionPasteFEN(fenstr);
1708 // get game from clipboard
1709 GdkDisplay *gdisp = gdk_display_get_default();
1710 if (gdisp == NULL) return;
1711 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1712 text = gtk_clipboard_wait_for_text(cb);
1713 if (text == NULL) return; // nothing to paste
1716 // write to temp file
1717 if (text == NULL || len == 0) {
1718 return; //nothing to paste
1720 f = fopen(gamePasteFilename, "w");
1722 DisplayError(_("Can't open temp file"), errno);
1725 fwrite(text, 1, len, f);
1729 LoadGameFromFile(gamePasteFilename, 0, gamePasteFilename, TRUE);
1736 QuitWrapper (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1742 void MoveTypeInProc(eventkey)
1743 GdkEventKey *eventkey;
1747 // ingnore if ctrl, alt, or meta is pressed
1748 if (eventkey->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_META_MASK)) {
1752 buf[0]=eventkey->keyval;
1754 if (eventkey->keyval > 32 && eventkey->keyval < 256)
1760 TempBackwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1762 if (!TempBackwardActive) {
1763 TempBackwardActive = True;
1769 TempForwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1771 /* Check to see if triggered by a key release event for a repeating key.
1772 * If so the next queued event will be a key press of the same key at the same time */
1773 if (XEventsQueued(xDisplay, QueuedAfterReading)) {
1775 XPeekEvent(xDisplay, &next);
1776 if (next.type == KeyPress && next.xkey.time == event->xkey.time &&
1777 next.xkey.keycode == event->xkey.keycode)
1781 TempBackwardActive = False;
1787 { // called from menu
1789 system("%s ./man.command", appData.sysOpen);
1791 system("xterm -e man xboard &");
1796 SetWindowTitle (char *text, char *title, char *icon)
1801 if (appData.titleInWindow) {
1803 XtSetArg(args[i], XtNlabel, text); i++;
1804 XtSetValues(titleWidget, args, i);
1807 XtSetArg(args[i], XtNiconName, (XtArgVal) icon); i++;
1808 XtSetArg(args[i], XtNtitle, (XtArgVal) title); i++;
1809 XtSetValues(shellWidget, args, i);
1810 XSync(xDisplay, False);
1812 if (appData.titleInWindow) {
1813 SetWidgetLabel(titleWidget, text);
1815 gtk_window_set_title (GTK_WINDOW(shells[BoardWindow]), title);
1820 DisplayIcsInteractionTitle (String message)
1823 if (oldICSInteractionTitle == NULL) {
1824 /* Magic to find the old window title, adapted from vim */
1825 char *wina = getenv("WINDOWID");
1827 Window win = (Window) atoi(wina);
1828 Window root, parent, *children;
1829 unsigned int nchildren;
1830 int (*oldHandler)() = XSetErrorHandler(NullXErrorCheck);
1832 if (XFetchName(xDisplay, win, &oldICSInteractionTitle)) break;
1833 if (!XQueryTree(xDisplay, win, &root, &parent,
1834 &children, &nchildren)) break;
1835 if (children) XFree((void *)children);
1836 if (parent == root || parent == 0) break;
1839 XSetErrorHandler(oldHandler);
1841 if (oldICSInteractionTitle == NULL) {
1842 oldICSInteractionTitle = "xterm";
1845 printf("\033]0;%s\007", message);
1852 DisplayTimerLabel (Option *opt, char *color, long timer, int highlight)
1854 GtkWidget *w = (GtkWidget *) opt->handle;
1861 strcpy(bgcolor, "black");
1862 strcpy(fgcolor, "white");
1864 strcpy(bgcolor, "white");
1865 strcpy(fgcolor, "black");
1868 appData.lowTimeWarning &&
1869 (timer / 1000) < appData.icsAlarmTime) {
1870 strcpy(fgcolor, appData.lowTimeWarningColor);
1873 gdk_color_parse( bgcolor, &col );
1874 gtk_widget_modify_bg(gtk_widget_get_parent(opt->handle), GTK_STATE_NORMAL, &col);
1876 if (appData.clockMode) {
1877 markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s:%s%s</span>",
1878 bgcolor, fgcolor, color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
1880 markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s </span>",
1881 bgcolor, fgcolor, color);
1883 gtk_label_set_markup(GTK_LABEL(w), markup);
1887 static GdkPixbuf **clockIcons[] = { &WhiteIcon, &BlackIcon };
1890 SetClockIcon (int color)
1892 GdkPixbuf *pm = *clockIcons[color];
1893 if (mainwindowIcon != pm) {
1894 mainwindowIcon = pm;
1895 gtk_window_set_icon(GTK_WINDOW(shellWidget), mainwindowIcon);
1899 #define INPUT_SOURCE_BUF_SIZE 8192
1908 char buf[INPUT_SOURCE_BUF_SIZE];
1913 DoInputCallback(io, cond, data)
1918 /* read input from one of the input source (for example a chess program, ICS, etc).
1919 * and call a function that will handle the input
1926 /* All information (callback function, file descriptor, etc) is
1927 * saved in an InputSource structure
1929 InputSource *is = (InputSource *) data;
1931 if (is->lineByLine) {
1932 count = read(is->fd, is->unused,
1933 INPUT_SOURCE_BUF_SIZE - (is->unused - is->buf));
1935 (is->func)(is, is->closure, is->buf, count, count ? errno : 0);
1938 is->unused += count;
1940 /* break input into lines and call the callback function on each
1943 while (p < is->unused) {
1944 q = memchr(p, '\n', is->unused - p);
1945 if (q == NULL) break;
1947 (is->func)(is, is->closure, p, q - p, 0);
1950 /* remember not yet used part of the buffer */
1952 while (p < is->unused) {
1957 /* read maximum length of input buffer and send the whole buffer
1958 * to the callback function
1960 count = read(is->fd, is->buf, INPUT_SOURCE_BUF_SIZE);
1965 (is->func)(is, is->closure, is->buf, count, error);
1967 return True; // Must return true or the watch will be removed
1970 InputSourceRef AddInputSource(pr, lineByLine, func, closure)
1977 GIOChannel *channel;
1978 ChildProc *cp = (ChildProc *) pr;
1980 is = (InputSource *) calloc(1, sizeof(InputSource));
1981 is->lineByLine = lineByLine;
1985 is->fd = fileno(stdin);
1987 is->kind = cp->kind;
1988 is->fd = cp->fdFrom;
1991 is->unused = is->buf;
1995 /* GTK-TODO: will this work on windows?*/
1997 channel = g_io_channel_unix_new(is->fd);
1998 g_io_channel_set_close_on_unref (channel, TRUE);
1999 is->sid = g_io_add_watch(channel, G_IO_IN,(GIOFunc) DoInputCallback, is);
2001 is->closure = closure;
2002 return (InputSourceRef) is;
2007 RemoveInputSource(isr)
2010 InputSource *is = (InputSource *) isr;
2012 if (is->sid == 0) return;
2013 g_source_remove(is->sid);
2020 static Boolean frameWaiting;
2023 FrameAlarm (int sig)
2025 frameWaiting = False;
2026 /* In case System-V style signals. Needed?? */
2027 signal(SIGALRM, FrameAlarm);
2031 FrameDelay (int time)
2033 struct itimerval delay;
2036 frameWaiting = True;
2037 signal(SIGALRM, FrameAlarm);
2038 delay.it_interval.tv_sec =
2039 delay.it_value.tv_sec = time / 1000;
2040 delay.it_interval.tv_usec =
2041 delay.it_value.tv_usec = (time % 1000) * 1000;
2042 setitimer(ITIMER_REAL, &delay, NULL);
2043 while (frameWaiting) pause();
2044 delay.it_interval.tv_sec = delay.it_value.tv_sec = 0;
2045 delay.it_interval.tv_usec = delay.it_value.tv_usec = 0;
2046 setitimer(ITIMER_REAL, &delay, NULL);
2053 FrameDelay (int time)
2056 XSync(xDisplay, False);
2058 // gtk_main_iteration_do(False);
2061 usleep(time * 1000);
2067 LoadLogo (ChessProgramState *cps, int n, Boolean ics)
2069 char buf[MSG_SIZ], *logoName = buf;
2070 if(appData.logo[n][0]) {
2071 logoName = appData.logo[n];
2072 } else if(appData.autoLogo) {
2073 if(ics) { // [HGM] logo: in ICS mode second can be used for ICS
2074 sprintf(buf, "%s/%s.png", appData.logoDir, appData.icsHost);
2075 } else if(appData.directory[n] && appData.directory[n][0]) {
2076 sprintf(buf, "%s/%s.png", appData.logoDir, cps->tidy);
2080 { ASSIGN(cps->programLogo, logoName); }
2084 UpdateLogos (int displ)
2086 if(optList[W_WHITE-1].handle == NULL) return;
2087 LoadLogo(&first, 0, 0);
2088 LoadLogo(&second, 1, appData.icsActive);
2089 if(displ) DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
2093 void FileNamePopUpWrapper(label, def, filter, proc, pathFlag, openMode, name, fp)
2104 GtkFileFilter *gtkfilter;
2105 GtkFileFilter *gtkfilter_all;
2107 char fileext[10] = "";
2108 char *result = NULL;
2111 /* make a copy of the filter string, so that strtok can work with it*/
2112 cp = strdup(filter);
2114 /* add filters for file extensions */
2115 gtkfilter = gtk_file_filter_new();
2116 gtkfilter_all = gtk_file_filter_new();
2118 /* one filter to show everything */
2119 gtk_file_filter_add_pattern(gtkfilter_all, "*.*");
2120 gtk_file_filter_set_name (gtkfilter_all, "All Files");
2122 /* add filter if present */
2123 result = strtok(cp, space);
2124 while( result != NULL ) {
2125 snprintf(fileext,10,"*%s",result);
2126 result = strtok( NULL, space );
2127 gtk_file_filter_add_pattern(gtkfilter, fileext);
2130 /* second filter to only show what's useful */
2131 gtk_file_filter_set_name (gtkfilter,filter);
2133 if (openMode[0] == 'r')
2135 dialog = gtk_file_chooser_dialog_new (label,
2137 GTK_FILE_CHOOSER_ACTION_OPEN,
2138 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2139 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
2144 dialog = gtk_file_chooser_dialog_new (label,
2146 GTK_FILE_CHOOSER_ACTION_SAVE,
2147 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2148 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
2150 /* add filename suggestions */
2151 if (strlen(def) > 0 )
2152 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), def);
2154 //gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER (dialog),TRUE);
2158 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter_all);
2159 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2160 /* activate filter */
2161 gtk_file_chooser_set_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2163 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
2168 filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
2171 f = fopen(filename, openMode);
2174 DisplayError(_("Failed to open file"), errno);
2178 /* TODO add indec */
2180 ASSIGN(*name, filename);
2181 ScheduleDelayedEvent(DelayedLoad, 50);
2186 gtk_widget_destroy (dialog);