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 squareSize = (squareSize*8 + BOARD_WIDTH/2)/BOARD_WIDTH; // scale height
930 /* Find some defaults; use the nearest known size */
931 SizeDefaults *szd, *nearest;
932 int distance = 99999;
933 nearest = szd = sizeDefaults;
934 while (szd->name != NULL) {
935 if (abs(szd->squareSize - squareSize) < distance) {
937 distance = abs(szd->squareSize - squareSize);
938 if (distance == 0) break;
942 if (i < 2) lineGap = nearest->lineGap;
943 if (i < 3) clockFontPxlSize = nearest->clockFontPxlSize;
944 if (i < 4) coordFontPxlSize = nearest->coordFontPxlSize;
945 if (i < 5) fontPxlSize = nearest->fontPxlSize;
946 if (i < 6) smallLayout = nearest->smallLayout;
947 if (i < 7) tinyLayout = nearest->tinyLayout;
950 SizeDefaults *szd = sizeDefaults;
951 if (*appData.boardSize == NULLCHAR) {
952 GdkScreen *screen = gtk_window_get_screen(GTK_WINDOW(mainwindow)); // TODO: this does not work, as no mainwindow yet
953 guint screenwidth = gdk_screen_get_width(screen);
954 guint screenheight = gdk_screen_get_height(screen);
955 while (screenwidth < (szd->minScreenSize*BOARD_WIDTH + 4)/8 ||
956 screenheight < (szd->minScreenSize*BOARD_HEIGHT + 4)/8) {
959 if (szd->name == NULL) szd--;
960 appData.boardSize = strdup(szd->name); // [HGM] settings: remember name for saving settings
962 while (szd->name != NULL &&
963 StrCaseCmp(szd->name, appData.boardSize) != 0) szd++;
964 if (szd->name == NULL) {
965 fprintf(stderr, _("%s: unrecognized boardSize name %s\n"),
966 programName, appData.boardSize);
970 squareSize = szd->squareSize;
971 lineGap = szd->lineGap;
972 clockFontPxlSize = szd->clockFontPxlSize;
973 coordFontPxlSize = szd->coordFontPxlSize;
974 fontPxlSize = szd->fontPxlSize;
975 smallLayout = szd->smallLayout;
976 tinyLayout = szd->tinyLayout;
977 // [HGM] font: use defaults from settings file if available and not overruled
980 defaultLineGap = lineGap;
981 if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
983 /* [HR] height treated separately (hacked) */
984 boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
985 // boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
988 * Determine what fonts to use.
991 InitializeFonts(clockFontPxlSize, coordFontPxlSize, fontPxlSize);
995 * Detect if there are not enough colors available and adapt.
998 if (DefaultDepth(xDisplay, xScreen) <= 2) {
999 appData.monoMode = True;
1003 forceMono = MakeColors();
1006 fprintf(stderr, _("%s: too few colors available; trying monochrome mode\n"),
1008 appData.monoMode = True;
1011 ParseIcsTextColors();
1017 layoutName = "tinyLayout";
1018 } else if (smallLayout) {
1019 layoutName = "smallLayout";
1021 layoutName = "normalLayout";
1024 wpMain.width = -1; // prevent popup sizes window
1025 optList = BoardPopUp(squareSize, lineGap, (void*)
1035 InitDrawingHandle(optList + W_BOARD);
1036 shellWidget = shells[BoardWindow];
1037 currBoard = &optList[W_BOARD];
1038 boardWidget = optList[W_BOARD].handle;
1039 menuBarWidget = optList[W_MENU].handle;
1040 dropMenu = optList[W_DROP].handle;
1041 titleWidget = optList[optList[W_TITLE].type != -1 ? W_TITLE : W_SMALL].handle;
1043 formWidget = XtParent(boardWidget);
1044 XtSetArg(args[0], XtNbackground, &timerBackgroundPixel);
1045 XtSetArg(args[1], XtNforeground, &timerForegroundPixel);
1046 XtGetValues(optList[W_WHITE].handle, args, 2);
1047 if (appData.showButtonBar) { // can't we use timer pixels for this? (Or better yet, just black & white?)
1048 XtSetArg(args[0], XtNbackground, &buttonBackgroundPixel);
1049 XtSetArg(args[1], XtNforeground, &buttonForegroundPixel);
1050 XtGetValues(optList[W_PAUSE].handle, args, 2);
1054 // [HGM] it seems the layout code ends here, but perhaps the color stuff is size independent and would
1055 // not need to go into InitDrawingSizes().
1059 // add accelerators to main shell
1060 gtk_window_add_accel_group(GTK_WINDOW(shellWidget), GtkAccelerators);
1063 * Create an icon. (Use two icons, to indicate whther it is white's or black's turn.)
1065 WhiteIcon = gdk_pixbuf_new_from_file(SVGDIR "/icon_white.svg", NULL);
1066 BlackIcon = gdk_pixbuf_new_from_file(SVGDIR "/icon_black.svg", NULL);
1067 mainwindowIcon = WhiteIcon;
1068 gtk_window_set_icon(GTK_WINDOW(shellWidget), mainwindowIcon);
1072 * Create a cursor for the board widget.
1075 window_attributes.cursor = XCreateFontCursor(xDisplay, XC_hand2);
1076 XChangeWindowAttributes(xDisplay, xBoardWindow,
1077 CWCursor, &window_attributes);
1081 * Inhibit shell resizing.
1084 shellArgs[0].value = (XtArgVal) &w;
1085 shellArgs[1].value = (XtArgVal) &h;
1086 XtGetValues(shellWidget, shellArgs, 2);
1087 shellArgs[4].value = shellArgs[2].value = w;
1088 shellArgs[5].value = shellArgs[3].value = h;
1089 // XtSetValues(shellWidget, &shellArgs[2], 4);
1092 // Note: We cannot do sensible sizing here, because the height of the clock widget is not yet known
1093 // It wil only become known asynchronously, when we first write a string into it.
1094 // This will then change the clock widget height, which triggers resizing the top-level window
1095 // and a configure event. Only then can we know the total height of the top-level window,
1096 // and calculate the height we need. The clockKludge flag suppresses all resizing until
1097 // that moment comes, after which the configure event-handler handles it through a (delayed) DragProg.
1100 gtk_widget_get_allocation(shells[BoardWindow], &a);
1101 w = a.width; h = a.height;
1102 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1103 clockKludge = hc = a.height;
1104 gtk_widget_get_allocation(boardWidget, &a);
1105 marginW = w - boardWidth; // [HGM] needed to set new shellWidget size when we resize board
1106 marginH = h - a.height - hc; // subtract current clock height, so it can be added back dynamically
1112 if(appData.logoSize)
1113 { // locate and read user logo
1115 snprintf(buf, MSG_SIZ, "%s/%s.png", appData.logoDir, UserName());
1116 ASSIGN(userLogo, buf);
1119 if (appData.animate || appData.animateDragging)
1122 g_signal_connect(shells[BoardWindow], "key-press-event", G_CALLBACK(KeyPressProc), NULL);
1123 g_signal_connect(shells[BoardWindow], "configure-event", G_CALLBACK(EventProc), NULL);
1125 /* [AS] Restore layout */
1126 if( wpMoveHistory.visible ) {
1130 if( wpEvalGraph.visible )
1135 if( wpEngineOutput.visible ) {
1136 EngineOutputPopUp();
1139 if( wpConsole.visible && appData.icsActive ) {
1146 if (errorExitStatus == -1) {
1147 if (appData.icsActive) {
1148 /* We now wait until we see "login:" from the ICS before
1149 sending the logon script (problems with timestamp otherwise) */
1150 /*ICSInitScript();*/
1151 if (appData.icsInputBox) ICSInputBoxPopUp();
1155 signal(SIGWINCH, TermSizeSigHandler);
1157 signal(SIGINT, IntSigHandler);
1158 signal(SIGTERM, IntSigHandler);
1159 if (*appData.cmailGameName != NULLCHAR) {
1160 signal(SIGUSR1, CmailSigHandler);
1164 gameInfo.boardWidth = 0; // [HGM] pieces: kludge to ensure InitPosition() calls InitDrawingSizes()
1167 // XtSetKeyboardFocus(shellWidget, formWidget);
1169 XSetInputFocus(xDisplay, XtWindow(formWidget), RevertToPointerRoot, CurrentTime);
1172 /* check for GTK events and process them */
1175 gtk_main_iteration();
1178 if (appData.debugMode) fclose(debugFP); // [DM] debug
1183 TermSizeSigHandler (int sig)
1189 IntSigHandler (int sig)
1195 CmailSigHandler (int sig)
1200 signal(SIGUSR1, SIG_IGN); /* suspend handler */
1202 /* Activate call-back function CmailSigHandlerCallBack() */
1203 OutputToProcess(cmailPR, (char *)(&dummy), sizeof(int), &error);
1205 signal(SIGUSR1, CmailSigHandler); /* re-activate handler */
1209 CmailSigHandlerCallBack (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1212 ReloadCmailMsgEvent(TRUE); /* Reload cmail msg */
1214 /**** end signal code ****/
1217 #define Abs(n) ((n)<0 ? -(n) : (n))
1221 InsertPxlSize (char *pattern, int targetPxlSize)
1223 char *base_fnt_lst, strInt[12], *p, *q;
1224 int alternatives, i, len, strIntLen;
1227 * Replace the "*" (if present) in the pixel-size slot of each
1228 * alternative with the targetPxlSize.
1232 while ((p = strchr(p, ',')) != NULL) {
1236 snprintf(strInt, sizeof(strInt), "%d", targetPxlSize);
1237 strIntLen = strlen(strInt);
1238 base_fnt_lst = calloc(1, strlen(pattern) + strIntLen * alternatives + 1);
1242 while (alternatives--) {
1243 char *comma = strchr(p, ',');
1244 for (i=0; i<14; i++) {
1245 char *hyphen = strchr(p, '-');
1247 if (comma && hyphen > comma) break;
1248 len = hyphen + 1 - p;
1249 if (i == 7 && *p == '*' && len == 2) {
1251 memcpy(q, strInt, strIntLen);
1261 len = comma + 1 - p;
1268 return base_fnt_lst;
1273 CreateFontSet (char *base_fnt_lst)
1276 char **missing_list;
1280 fntSet = XCreateFontSet(xDisplay, base_fnt_lst,
1281 &missing_list, &missing_count, &def_string);
1282 if (appData.debugMode) {
1284 XFontStruct **font_struct_list;
1285 char **font_name_list;
1286 fprintf(debugFP, "Requested font set for list %s\n", base_fnt_lst);
1288 fprintf(debugFP, " got list %s, locale %s\n",
1289 XBaseFontNameListOfFontSet(fntSet),
1290 XLocaleOfFontSet(fntSet));
1291 count = XFontsOfFontSet(fntSet, &font_struct_list, &font_name_list);
1292 for (i = 0; i < count; i++) {
1293 fprintf(debugFP, " got charset %s\n", font_name_list[i]);
1296 for (i = 0; i < missing_count; i++) {
1297 fprintf(debugFP, " missing charset %s\n", missing_list[i]);
1300 if (fntSet == NULL) {
1301 fprintf(stderr, _("Unable to create font set for %s.\n"), base_fnt_lst);
1307 #else // not ENABLE_NLS
1309 * Find a font that matches "pattern" that is as close as
1310 * possible to the targetPxlSize. Prefer fonts that are k
1311 * pixels smaller to fonts that are k pixels larger. The
1312 * pattern must be in the X Consortium standard format,
1313 * e.g. "-*-helvetica-bold-r-normal--*-*-*-*-*-*-*-*".
1314 * The return value should be freed with XtFree when no
1318 FindFont (char *pattern, int targetPxlSize)
1320 char **fonts, *p, *best, *scalable, *scalableTail;
1321 int i, j, nfonts, minerr, err, pxlSize;
1324 fonts = XListFonts(xDisplay, pattern, 999999, &nfonts);
1326 fprintf(stderr, _("%s: no fonts match pattern %s\n"),
1327 programName, pattern);
1334 for (i=0; i<nfonts; i++) {
1337 if (*p != '-') continue;
1339 if (*p == NULLCHAR) break;
1340 if (*p++ == '-') j++;
1342 if (j < 7) continue;
1345 scalable = fonts[i];
1348 err = pxlSize - targetPxlSize;
1349 if (Abs(err) < Abs(minerr) ||
1350 (minerr > 0 && err < 0 && -err == minerr)) {
1356 if (scalable && Abs(minerr) > appData.fontSizeTolerance) {
1357 /* If the error is too big and there is a scalable font,
1358 use the scalable font. */
1359 int headlen = scalableTail - scalable;
1360 p = (char *) XtMalloc(strlen(scalable) + 10);
1361 while (isdigit(*scalableTail)) scalableTail++;
1362 sprintf(p, "%.*s%d%s", headlen, scalable, targetPxlSize, scalableTail);
1364 p = (char *) XtMalloc(strlen(best) + 2);
1365 safeStrCpy(p, best, strlen(best)+1 );
1367 if (appData.debugMode) {
1368 fprintf(debugFP, "resolved %s at pixel size %d\n to %s\n",
1369 pattern, targetPxlSize, p);
1371 XFreeFontNames(fonts);
1378 EnableNamedMenuItem (char *menuRef, int state)
1380 MenuItem *item = MenuNameToItem(menuRef);
1382 if(item && item->handle) gtk_widget_set_sensitive(item->handle, state);
1386 EnableButtonBar (int state)
1389 XtSetSensitive(optList[W_BUTTON].handle, state);
1395 SetMenuEnables (Enables *enab)
1397 while (enab->name != NULL) {
1398 EnableNamedMenuItem(enab->name, enab->value);
1403 gboolean KeyPressProc(window, eventkey, data)
1405 GdkEventKey *eventkey;
1409 MoveTypeInProc(eventkey); // pop up for typed in moves
1412 /* check for other key values */
1413 switch(eventkey->keyval) {
1425 KeyBindingProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1426 { // [HGM] new method of key binding: specify MenuItem(FlipView) in stead of FlipViewProc in translation string
1428 if(*nprms == 0) return;
1429 item = MenuNameToItem(prms[0]);
1430 if(item) ((MenuProc *) item->proc) ();
1444 for (i=0; i<sizeof(dmEnables)/sizeof(DropMenuEnables); i++) {
1445 entry = XtNameToWidget(dropMenu, dmEnables[i].widget);
1446 p = strchr(gameMode == IcsPlayingWhite ? white_holding : black_holding,
1447 dmEnables[i].piece);
1448 XtSetSensitive(entry, p != NULL || !appData.testLegality
1449 /*!!temp:*/ || (gameInfo.variant == VariantCrazyhouse
1450 && !appData.icsActive));
1452 while (p && *p++ == dmEnables[i].piece) count++;
1453 snprintf(label, sizeof(label), "%s %d", dmEnables[i].widget, count);
1455 XtSetArg(args[j], XtNlabel, label); j++;
1456 XtSetValues(entry, args, j);
1462 do_flash_delay (unsigned long msec)
1468 FlashDelay (int flash_delay)
1470 if(flash_delay) do_flash_delay(flash_delay);
1474 Fraction (int x, int start, int stop)
1476 double f = ((double) x - start)/(stop - start);
1477 if(f > 1.) f = 1.; else if(f < 0.) f = 0.;
1481 static WindowPlacement wpNew;
1484 CoDrag (GtkWidget *sh, WindowPlacement *wp)
1486 int touch=0, fudge = 2, f = 2;
1487 GetActualPlacement(sh, wp);
1488 if(abs(wpMain.x + wpMain.width + 2*frameX - f - wp->x) < fudge) touch = 1; else // right touch
1489 if(abs(wp->x + wp->width + 2*frameX + f - wpMain.x) < fudge) touch = 2; else // left touch
1490 if(abs(wpMain.y + wpMain.height + frameX - f + frameY - wp->y) < fudge) touch = 3; else // bottom touch
1491 if(abs(wp->y + wp->height + frameX + frameY + f - wpMain.y) < fudge) touch = 4; // top touch
1492 //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);
1493 if(!touch ) return; // only windows that touch co-move
1494 if(touch < 3 && wpNew.height != wpMain.height) { // left or right and height changed
1495 int heightInc = wpNew.height - wpMain.height;
1496 double fracTop = Fraction(wp->y, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1497 double fracBot = Fraction(wp->y + wp->height + frameX + frameY + 1, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1498 wp->y += fracTop * heightInc;
1499 heightInc = (int) (fracBot * heightInc) - (int) (fracTop * heightInc);
1501 if(heightInc) XtSetArg(args[j], XtNheight, wp->height + heightInc), j++;
1503 wp->height += heightInc;
1504 } else if(touch > 2 && wpNew.width != wpMain.width) { // top or bottom and width changed
1505 int widthInc = wpNew.width - wpMain.width;
1506 double fracLeft = Fraction(wp->x, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1507 double fracRght = Fraction(wp->x + wp->width + 2*frameX + 1, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1508 wp->y += fracLeft * widthInc;
1509 widthInc = (int) (fracRght * widthInc) - (int) (fracLeft * widthInc);
1511 if(widthInc) XtSetArg(args[j], XtNwidth, wp->width + widthInc), j++;
1513 wp->width += widthInc;
1515 wp->x += wpNew.x - wpMain.x;
1516 wp->y += wpNew.y - wpMain.y;
1517 if(touch == 1) wp->x += wpNew.width - wpMain.width; else
1518 if(touch == 3) wp->y += wpNew.height - wpMain.height;
1520 XtSetArg(args[j], XtNx, wp->x); j++;
1521 XtSetArg(args[j], XtNy, wp->y); j++;
1522 XtSetValues(sh, args, j);
1524 gtk_window_move(GTK_WINDOW(sh), wp->x, wp->y);
1525 //printf("moved to (%d,%d)\n", wp->x, wp->y);
1526 gtk_window_resize(GTK_WINDOW(sh), wp->width, wp->height);
1530 ReSize (WindowPlacement *wp)
1533 int sqx, sqy, w, h, hc, lg = lineGap;
1534 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1535 hc = a.height; // clock height can depend on single / double line clock text!
1536 if(clockKludge == a.height) return; // wait for clock to get final size at startup
1537 if(clockKludge) { // clock height OK now; calculate desired initial board height
1539 wp->height = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + marginH + hc;
1541 if(wp->width == wpMain.width && wp->height == wpMain.height) return; // not sized
1542 sqx = (wp->width - lg - marginW) / BOARD_WIDTH - lg;
1543 sqy = (wp->height - lg - marginH - hc) / BOARD_HEIGHT - lg;
1544 if(sqy < sqx) sqx = sqy;
1545 if(appData.overrideLineGap < 0) { // do second iteration with adjusted lineGap
1546 lg = lineGap = sqx < 37 ? 1 : sqx < 59 ? 2 : sqx < 116 ? 3 : 4;
1547 sqx = (wp->width - lg - marginW) / BOARD_WIDTH - lg;
1548 sqy = (wp->height - lg - marginH - hc) / BOARD_HEIGHT - lg;
1549 if(sqy < sqx) sqx = sqy;
1551 if(sqx != squareSize) {
1552 //printf("new sq size %d (%dx%d)\n", sqx, wp->width, wp->height);
1553 squareSize = sqx; // adopt new square size
1554 CreatePNGPieces(); // make newly scaled pieces
1555 InitDrawingSizes(0, 0); // creates grid etc.
1556 } else ResizeBoardWindow(BOARD_WIDTH * (squareSize + lineGap) + lineGap, BOARD_HEIGHT * (squareSize + lineGap) + lineGap, 0);
1557 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
1558 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
1559 if(optList[W_BOARD].max > w) optList[W_BOARD].max = w;
1560 if(optList[W_BOARD].value > h) optList[W_BOARD].value = h;
1563 static guint delayedDragTag = 0;
1572 // GetActualPlacement(shellWidget, &wpNew);
1573 if(wpNew.x == wpMain.x && wpNew.y == wpMain.y && // not moved
1574 wpNew.width == wpMain.width && wpNew.height == wpMain.height) { // not sized
1575 busy = 0; return; // false alarm
1578 if(appData.useStickyWindows) {
1579 if(shellUp[EngOutDlg]) CoDrag(shells[EngOutDlg], &wpEngineOutput);
1580 if(shellUp[HistoryDlg]) CoDrag(shells[HistoryDlg], &wpMoveHistory);
1581 if(shellUp[EvalGraphDlg]) CoDrag(shells[EvalGraphDlg], &wpEvalGraph);
1582 if(shellUp[GameListDlg]) CoDrag(shells[GameListDlg], &wpGameList);
1583 if(shellUp[ChatDlg]) CoDrag(shells[ChatDlg], &wpConsole);
1586 DrawPosition(True, NULL);
1587 if(delayedDragTag) g_source_remove(delayedDragTag);
1588 delayedDragTag = 0; // now drag executed, make sure next DelayedDrag will not cancel timer event (which could now be used by other)
1595 //printf("old timr = %d\n", delayedDragTag);
1596 if(delayedDragTag) g_source_remove(delayedDragTag);
1597 delayedDragTag = g_timeout_add( 200, (GSourceFunc) DragProc, NULL);
1598 //printf("new timr = %d\n", delayedDragTag);
1602 EventProc (GtkWidget *widget, GdkEvent *event, gpointer g)
1604 //printf("event proc (%d,%d) %dx%d\n", event->configure.x, event->configure.y, event->configure.width, event->configure.height);
1606 wpNew.x = event->configure.x;
1607 wpNew.y = event->configure.y;
1608 wpNew.width = event->configure.width;
1609 wpNew.height = event->configure.height;
1610 DelayedDrag(); // as long as events keep coming in faster than 50 msec, they destroy each other
1616 /* Disable all user input other than deleting the window */
1617 static int frozen = 0;
1623 /* Grab by a widget that doesn't accept input */
1624 gtk_grab_add(optList[W_MESSG].handle);
1628 /* Undo a FreezeUI */
1632 if (!frozen) return;
1633 gtk_grab_remove(optList[W_MESSG].handle);
1640 static int oldPausing = FALSE;
1641 static GameMode oldmode = (GameMode) -1;
1643 if (!boardWidget) return;
1645 if (pausing != oldPausing) {
1646 oldPausing = pausing;
1647 MarkMenuItem("Mode.Pause", pausing);
1649 if (appData.showButtonBar) {
1650 /* Always toggle, don't set. Previous code messes up when
1651 invoked while the button is pressed, as releasing it
1652 toggles the state again. */
1654 gdk_color_parse( pausing ? "#808080" : "#F0F0F0", &color );
1655 gtk_widget_modify_bg ( GTK_WIDGET(optList[W_PAUSE].handle), GTK_STATE_NORMAL, &color );
1659 wname = ModeToWidgetName(oldmode);
1660 if (wname != NULL) {
1661 MarkMenuItem(wname, False);
1663 wname = ModeToWidgetName(gameMode);
1664 if (wname != NULL) {
1665 MarkMenuItem(wname, True);
1668 MarkMenuItem("Mode.MachineMatch", matchMode && matchGame < appData.matchGames);
1670 /* Maybe all the enables should be handled here, not just this one */
1671 EnableNamedMenuItem("Mode.Training", gameMode == Training || gameMode == PlayFromGameFile);
1673 DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
1678 * Button/menu procedures
1681 void CopyFileToClipboard(gchar *filename)
1683 gchar *selection_tmp;
1687 FILE* f = fopen(filename, "r");
1690 if (f == NULL) return;
1694 selection_tmp = g_try_malloc(len + 1);
1695 if (selection_tmp == NULL) {
1696 printf("Malloc failed in CopyFileToClipboard\n");
1699 count = fread(selection_tmp, 1, len, f);
1702 g_free(selection_tmp);
1705 selection_tmp[len] = NULLCHAR; // file is now in selection_tmp
1707 // copy selection_tmp to clipboard
1708 GdkDisplay *gdisp = gdk_display_get_default();
1710 g_free(selection_tmp);
1713 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1714 gtk_clipboard_set_text(cb, selection_tmp, -1);
1715 g_free(selection_tmp);
1719 CopySomething (char *src)
1721 GdkDisplay *gdisp = gdk_display_get_default();
1723 if(!src) { CopyFileToClipboard(gameCopyFilename); return; }
1724 if (gdisp == NULL) return;
1725 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1726 gtk_clipboard_set_text(cb, src, -1);
1730 PastePositionProc ()
1732 GdkDisplay *gdisp = gdk_display_get_default();
1736 if (gdisp == NULL) return;
1737 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1738 fenstr = gtk_clipboard_wait_for_text(cb);
1739 if (fenstr==NULL) return; // nothing had been selected to copy
1740 EditPositionPasteFEN(fenstr);
1752 // get game from clipboard
1753 GdkDisplay *gdisp = gdk_display_get_default();
1754 if (gdisp == NULL) return;
1755 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1756 text = gtk_clipboard_wait_for_text(cb);
1757 if (text == NULL) return; // nothing to paste
1760 // write to temp file
1761 if (text == NULL || len == 0) {
1762 return; //nothing to paste
1764 f = fopen(gamePasteFilename, "w");
1766 DisplayError(_("Can't open temp file"), errno);
1769 fwrite(text, 1, len, f);
1773 LoadGameFromFile(gamePasteFilename, 0, gamePasteFilename, TRUE);
1780 QuitWrapper (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1786 void MoveTypeInProc(eventkey)
1787 GdkEventKey *eventkey;
1791 // ingnore if ctrl, alt, or meta is pressed
1792 if (eventkey->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_META_MASK)) {
1796 buf[0]=eventkey->keyval;
1798 if (eventkey->keyval > 32 && eventkey->keyval < 256)
1799 ConsoleAutoPopUp (buf);
1804 TempBackwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1806 if (!TempBackwardActive) {
1807 TempBackwardActive = True;
1813 TempForwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1815 /* Check to see if triggered by a key release event for a repeating key.
1816 * If so the next queued event will be a key press of the same key at the same time */
1817 if (XEventsQueued(xDisplay, QueuedAfterReading)) {
1819 XPeekEvent(xDisplay, &next);
1820 if (next.type == KeyPress && next.xkey.time == event->xkey.time &&
1821 next.xkey.keycode == event->xkey.keycode)
1825 TempBackwardActive = False;
1831 { // called from menu
1834 snprintf(buf, MSG_SIZ, "%s ./man.command", appData.sysOpen);
1837 system("xterm -e man xboard &");
1842 SetWindowTitle (char *text, char *title, char *icon)
1847 if (appData.titleInWindow) {
1849 XtSetArg(args[i], XtNlabel, text); i++;
1850 XtSetValues(titleWidget, args, i);
1853 XtSetArg(args[i], XtNiconName, (XtArgVal) icon); i++;
1854 XtSetArg(args[i], XtNtitle, (XtArgVal) title); i++;
1855 XtSetValues(shellWidget, args, i);
1856 XSync(xDisplay, False);
1858 if (appData.titleInWindow) {
1859 SetWidgetLabel(titleWidget, text);
1861 gtk_window_set_title (GTK_WINDOW(shells[BoardWindow]), title);
1866 DisplayIcsInteractionTitle (String message)
1869 if (oldICSInteractionTitle == NULL) {
1870 /* Magic to find the old window title, adapted from vim */
1871 char *wina = getenv("WINDOWID");
1873 Window win = (Window) atoi(wina);
1874 Window root, parent, *children;
1875 unsigned int nchildren;
1876 int (*oldHandler)() = XSetErrorHandler(NullXErrorCheck);
1878 if (XFetchName(xDisplay, win, &oldICSInteractionTitle)) break;
1879 if (!XQueryTree(xDisplay, win, &root, &parent,
1880 &children, &nchildren)) break;
1881 if (children) XFree((void *)children);
1882 if (parent == root || parent == 0) break;
1885 XSetErrorHandler(oldHandler);
1887 if (oldICSInteractionTitle == NULL) {
1888 oldICSInteractionTitle = "xterm";
1891 printf("\033]0;%s\007", message);
1898 DisplayTimerLabel (Option *opt, char *color, long timer, int highlight)
1900 GtkWidget *w = (GtkWidget *) opt->handle;
1907 strcpy(bgcolor, "black");
1908 strcpy(fgcolor, "white");
1910 strcpy(bgcolor, "white");
1911 strcpy(fgcolor, "black");
1914 appData.lowTimeWarning &&
1915 (timer / 1000) < appData.icsAlarmTime) {
1916 strcpy(fgcolor, appData.lowTimeWarningColor);
1919 gdk_color_parse( bgcolor, &col );
1920 gtk_widget_modify_bg(gtk_widget_get_parent(opt->handle), GTK_STATE_NORMAL, &col);
1922 if (appData.clockMode) {
1923 markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s:%s%s</span>",
1924 bgcolor, fgcolor, color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
1926 markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s </span>",
1927 bgcolor, fgcolor, color);
1929 gtk_label_set_markup(GTK_LABEL(w), markup);
1933 static GdkPixbuf **clockIcons[] = { &WhiteIcon, &BlackIcon };
1936 SetClockIcon (int color)
1938 GdkPixbuf *pm = *clockIcons[color];
1939 if (mainwindowIcon != pm) {
1940 mainwindowIcon = pm;
1941 gtk_window_set_icon(GTK_WINDOW(shellWidget), mainwindowIcon);
1945 #define INPUT_SOURCE_BUF_SIZE 8192
1954 char buf[INPUT_SOURCE_BUF_SIZE];
1959 DoInputCallback(io, cond, data)
1964 /* read input from one of the input source (for example a chess program, ICS, etc).
1965 * and call a function that will handle the input
1972 /* All information (callback function, file descriptor, etc) is
1973 * saved in an InputSource structure
1975 InputSource *is = (InputSource *) data;
1977 if (is->lineByLine) {
1978 count = read(is->fd, is->unused,
1979 INPUT_SOURCE_BUF_SIZE - (is->unused - is->buf));
1981 if(count == 0 && is->kind == CPReal && shells[ChatDlg]) { // [HGM] absence of terminal is no error if ICS Console present
1982 RemoveInputSource(is); // cease reading stdin
1983 stdoutClosed = TRUE; // suppress future output
1986 (is->func)(is, is->closure, is->buf, count, count ? errno : 0);
1989 is->unused += count;
1991 /* break input into lines and call the callback function on each
1994 while (p < is->unused) {
1995 q = memchr(p, '\n', is->unused - p);
1996 if (q == NULL) break;
1998 (is->func)(is, is->closure, p, q - p, 0);
2001 /* remember not yet used part of the buffer */
2003 while (p < is->unused) {
2008 /* read maximum length of input buffer and send the whole buffer
2009 * to the callback function
2011 count = read(is->fd, is->buf, INPUT_SOURCE_BUF_SIZE);
2016 (is->func)(is, is->closure, is->buf, count, error);
2018 return True; // Must return true or the watch will be removed
2021 InputSourceRef AddInputSource(pr, lineByLine, func, closure)
2028 GIOChannel *channel;
2029 ChildProc *cp = (ChildProc *) pr;
2031 is = (InputSource *) calloc(1, sizeof(InputSource));
2032 is->lineByLine = lineByLine;
2036 is->fd = fileno(stdin);
2038 is->kind = cp->kind;
2039 is->fd = cp->fdFrom;
2042 is->unused = is->buf;
2046 /* GTK-TODO: will this work on windows?*/
2048 channel = g_io_channel_unix_new(is->fd);
2049 g_io_channel_set_close_on_unref (channel, TRUE);
2050 is->sid = g_io_add_watch(channel, G_IO_IN,(GIOFunc) DoInputCallback, is);
2052 is->closure = closure;
2053 return (InputSourceRef) is;
2058 RemoveInputSource(isr)
2061 InputSource *is = (InputSource *) isr;
2063 if (is->sid == 0) return;
2064 g_source_remove(is->sid);
2071 static Boolean frameWaiting;
2074 FrameAlarm (int sig)
2076 frameWaiting = False;
2077 /* In case System-V style signals. Needed?? */
2078 signal(SIGALRM, FrameAlarm);
2082 FrameDelay (int time)
2084 struct itimerval delay;
2087 frameWaiting = True;
2088 signal(SIGALRM, FrameAlarm);
2089 delay.it_interval.tv_sec =
2090 delay.it_value.tv_sec = time / 1000;
2091 delay.it_interval.tv_usec =
2092 delay.it_value.tv_usec = (time % 1000) * 1000;
2093 setitimer(ITIMER_REAL, &delay, NULL);
2094 while (frameWaiting) pause();
2095 delay.it_interval.tv_sec = delay.it_value.tv_sec = 0;
2096 delay.it_interval.tv_usec = delay.it_value.tv_usec = 0;
2097 setitimer(ITIMER_REAL, &delay, NULL);
2104 FrameDelay (int time)
2107 XSync(xDisplay, False);
2109 // gtk_main_iteration_do(False);
2112 usleep(time * 1000);
2118 LoadLogo (ChessProgramState *cps, int n, Boolean ics)
2120 char buf[MSG_SIZ], *logoName = buf;
2121 if(appData.logo[n][0]) {
2122 logoName = appData.logo[n];
2123 } else if(appData.autoLogo) {
2124 if(ics) { // [HGM] logo: in ICS mode second can be used for ICS
2125 sprintf(buf, "%s/%s.png", appData.logoDir, appData.icsHost);
2126 } else if(appData.directory[n] && appData.directory[n][0]) {
2127 sprintf(buf, "%s/%s.png", appData.logoDir, cps->tidy);
2131 { ASSIGN(cps->programLogo, logoName); }
2135 UpdateLogos (int displ)
2137 if(optList[W_WHITE-1].handle == NULL) return;
2138 LoadLogo(&first, 0, 0);
2139 LoadLogo(&second, 1, appData.icsActive);
2140 if(displ) DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
2144 void FileNamePopUpWrapper(label, def, filter, proc, pathFlag, openMode, name, fp)
2155 GtkFileFilter *gtkfilter;
2156 GtkFileFilter *gtkfilter_all;
2158 char fileext[10] = "";
2159 char *result = NULL;
2162 /* make a copy of the filter string, so that strtok can work with it*/
2163 cp = strdup(filter);
2165 /* add filters for file extensions */
2166 gtkfilter = gtk_file_filter_new();
2167 gtkfilter_all = gtk_file_filter_new();
2169 /* one filter to show everything */
2170 gtk_file_filter_add_pattern(gtkfilter_all, "*.*");
2171 gtk_file_filter_set_name (gtkfilter_all, "All Files");
2173 /* add filter if present */
2174 result = strtok(cp, space);
2175 while( result != NULL ) {
2176 snprintf(fileext,10,"*%s",result);
2177 result = strtok( NULL, space );
2178 gtk_file_filter_add_pattern(gtkfilter, fileext);
2181 /* second filter to only show what's useful */
2182 gtk_file_filter_set_name (gtkfilter,filter);
2184 if (openMode[0] == 'r')
2186 dialog = gtk_file_chooser_dialog_new (label,
2188 GTK_FILE_CHOOSER_ACTION_OPEN,
2189 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2190 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
2195 dialog = gtk_file_chooser_dialog_new (label,
2197 GTK_FILE_CHOOSER_ACTION_SAVE,
2198 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2199 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
2201 /* add filename suggestions */
2202 if (strlen(def) > 0 )
2203 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), def);
2205 //gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER (dialog),TRUE);
2209 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter_all);
2210 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2211 /* activate filter */
2212 gtk_file_chooser_set_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2214 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
2219 filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
2222 f = fopen(filename, openMode);
2225 DisplayError(_("Failed to open file"), errno);
2229 /* TODO add indec */
2231 ASSIGN(*name, filename);
2232 ScheduleDelayedEvent(DelayedLoad, 50);
2237 gtk_widget_destroy (dialog);