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);
429 ParseTextAttribs (ColorClass cc, char *s)
431 (&appData.colorShout)[cc] = strdup(s);
435 ParseBoardSize (void *addr, char *name)
437 appData.boardSize = strdup(name);
442 { // In XBoard the sound-playing program takes care of obtaining the actual sound
446 SetCommPortDefaults ()
447 { // for now, this is a no-op, as the corresponding option does not exist in XBoard
450 // [HGM] args: these three cases taken out to stay in front-end
452 SaveFontArg (FILE *f, ArgDescriptor *ad)
455 int i, n = (int)(intptr_t)ad->argLoc;
457 case 0: // CLOCK_FONT
458 name = appData.clockFont;
460 case 1: // MESSAGE_FONT
463 case 2: // COORD_FONT
464 name = appData.coordFont;
469 for(i=0; i<NUM_SIZES; i++) // [HGM] font: current font becomes standard for current size
470 if(sizeDefaults[i].squareSize == squareSize) { // only for standard sizes!
471 fontTable[n][squareSize] = strdup(name);
472 fontValid[n][squareSize] = True;
475 for(i=0; i<MAX_SIZE; i++) if(fontValid[n][i]) // [HGM] font: store all standard fonts
476 fprintf(f, OPTCHAR "%s" SEPCHAR "\"size%d:%s\"\n", ad->argName, i, fontTable[n][i]);
481 { // nothing to do, as the sounds are at all times represented by their text-string names already
485 SaveAttribsArg (FILE *f, ArgDescriptor *ad)
486 { // here the "argLoc" defines a table index. It could have contained the 'ta' pointer itself, though
487 fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, (&appData.colorShout)[(int)(intptr_t)ad->argLoc]);
491 SaveColor (FILE *f, ArgDescriptor *ad)
492 { // in WinBoard the color is an int and has to be converted to text. In X it would be a string already?
493 if(colorVariable[(int)(intptr_t)ad->argLoc])
494 fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, *(char**)colorVariable[(int)(intptr_t)ad->argLoc]);
498 SaveBoardSize (FILE *f, char *name, void *addr)
499 { // wrapper to shield back-end from BoardSize & sizeInfo
500 fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", name, appData.boardSize);
504 ParseCommPortSettings (char *s)
505 { // no such option in XBoard (yet)
511 GetActualPlacement (GtkWidget *shell, WindowPlacement *wp)
515 gtk_widget_get_allocation(shell, &a);
516 gtk_window_get_position(GTK_WINDOW(shell), &a.x, &a.y);
520 wp->height = a.height;
521 //printf("placement: (%d,%d) %dx%d\n", a.x, a.y, a.width, a.height);
522 frameX = 3; frameY = 3; // remember to decide if windows touch
527 { // wrapper to shield use of window handles from back-end (make addressible by number?)
528 // In XBoard this will have to wait until awareness of window parameters is implemented
529 GetActualPlacement(shellWidget, &wpMain);
530 if(shellUp[EngOutDlg]) GetActualPlacement(shells[EngOutDlg], &wpEngineOutput);
531 if(shellUp[HistoryDlg]) GetActualPlacement(shells[HistoryDlg], &wpMoveHistory);
532 if(shellUp[EvalGraphDlg]) GetActualPlacement(shells[EvalGraphDlg], &wpEvalGraph);
533 if(shellUp[GameListDlg]) GetActualPlacement(shells[GameListDlg], &wpGameList);
534 if(shellUp[CommentDlg]) GetActualPlacement(shells[CommentDlg], &wpComment);
535 if(shellUp[TagsDlg]) GetActualPlacement(shells[TagsDlg], &wpTags);
536 GetPlacement(ChatDlg, &wpConsole); if(appData.icsActive) wpConsole.visible = shellUp[ChatDlg];
540 PrintCommPortSettings (FILE *f, char *name)
541 { // This option does not exist in XBoard
545 EnsureOnScreen (int *x, int *y, int minX, int minY)
552 { // [HGM] args: allows testing if main window is realized from back-end
553 return DialogExists(BoardWindow);
557 PopUpStartupDialog ()
558 { // start menu not implemented in XBoard
562 ConvertToLine (int argc, char **argv)
564 static char line[128*1024], buf[1024];
568 for(i=1; i<argc; i++)
570 if( (strchr(argv[i], ' ') || strchr(argv[i], '\n') ||strchr(argv[i], '\t') || argv[i][0] == NULLCHAR)
571 && argv[i][0] != '{' )
572 snprintf(buf, sizeof(buf)/sizeof(buf[0]), "{%s} ", argv[i]);
574 snprintf(buf, sizeof(buf)/sizeof(buf[0]), "%s ", argv[i]);
575 strncat(line, buf, 128*1024 - strlen(line) - 1 );
578 line[strlen(line)-1] = NULLCHAR;
582 //--------------------------------------------------------------------------------------------
587 ResizeBoardWindow (int w, int h, int inhibit)
590 if(clockKludge) return; // ignore as long as clock does not have final height
591 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
592 w += marginW + 1; // [HGM] not sure why the +1 is (sometimes) needed...
593 h += marginH + a.height + 1;
594 gtk_window_resize(GTK_WINDOW(shellWidget), w, h);
599 { // dummy, as the GTK code does not make colors in advance
604 InitializeFonts (int clockFontPxlSize, int coordFontPxlSize, int fontPxlSize)
605 { // determine what fonts to use, and create them
610 if(!fontIsSet[CLOCK_FONT] && fontValid[CLOCK_FONT][squareSize])
611 appData.clockFont = fontTable[CLOCK_FONT][squareSize];
612 if(!fontIsSet[MESSAGE_FONT] && fontValid[MESSAGE_FONT][squareSize])
613 appData.font = fontTable[MESSAGE_FONT][squareSize];
614 if(!fontIsSet[COORD_FONT] && fontValid[COORD_FONT][squareSize])
615 appData.coordFont = fontTable[COORD_FONT][squareSize];
618 appData.font = InsertPxlSize(appData.font, fontPxlSize);
619 appData.clockFont = InsertPxlSize(appData.clockFont, clockFontPxlSize);
620 appData.coordFont = InsertPxlSize(appData.coordFont, coordFontPxlSize);
621 fontSet = CreateFontSet(appData.font);
622 clockFontSet = CreateFontSet(appData.clockFont);
624 /* For the coordFont, use the 0th font of the fontset. */
625 XFontSet coordFontSet = CreateFontSet(appData.coordFont);
626 XFontStruct **font_struct_list;
627 XFontSetExtents *fontSize;
628 char **font_name_list;
629 XFontsOfFontSet(coordFontSet, &font_struct_list, &font_name_list);
630 coordFontID = XLoadFont(xDisplay, font_name_list[0]);
631 coordFontStruct = XQueryFont(xDisplay, coordFontID);
632 fontSize = XExtentsOfFontSet(fontSet); // [HGM] figure out how much vertical space font takes
633 textHeight = fontSize->max_logical_extent.height + 5; // add borderWidth
636 appData.font = FindFont(appData.font, fontPxlSize);
637 appData.clockFont = FindFont(appData.clockFont, clockFontPxlSize);
638 appData.coordFont = FindFont(appData.coordFont, coordFontPxlSize);
639 clockFontID = XLoadFont(xDisplay, appData.clockFont);
640 clockFontStruct = XQueryFont(xDisplay, clockFontID);
641 coordFontID = XLoadFont(xDisplay, appData.coordFont);
642 coordFontStruct = XQueryFont(xDisplay, coordFontID);
643 // textHeight in !NLS mode!
645 countFontID = coordFontID; // [HGM] holdings
646 countFontStruct = coordFontStruct;
648 xdb = XtDatabase(xDisplay);
650 XrmPutLineResource(&xdb, "*international: True");
651 vTo.size = sizeof(XFontSet);
652 vTo.addr = (XtPointer) &fontSet;
653 XrmPutResource(&xdb, "*fontSet", XtRFontSet, &vTo);
655 XrmPutStringResource(&xdb, "*font", appData.font);
666 case ArgInt: p = " N"; break;
667 case ArgString: p = " STR"; break;
668 case ArgBoolean: p = " TF"; break;
669 case ArgSettingsFilename:
670 case ArgBackupSettingsFile:
671 case ArgFilename: p = " FILE"; break;
672 case ArgX: p = " Nx"; break;
673 case ArgY: p = " Ny"; break;
674 case ArgAttribs: p = " TEXTCOL"; break;
675 case ArgColor: p = " COL"; break;
676 case ArgFont: p = " FONT"; break;
677 case ArgBoardSize: p = " SIZE"; break;
678 case ArgFloat: p = " FLOAT"; break;
683 case ArgCommSettings:
695 ArgDescriptor *q, *p = argDescriptors+5;
696 printf("\nXBoard accepts the following options:\n"
697 "(N = integer, TF = true or false, STR = text string, FILE = filename,\n"
698 " Nx, Ny = relative coordinates, COL = color, FONT = X-font spec,\n"
699 " SIZE = board-size spec(s)\n"
700 " Within parentheses are short forms, or options to set to true or false.\n"
701 " Persistent options (saved in the settings file) are marked with *)\n\n");
703 if(p->argType == ArgCommSettings) { p++; continue; } // XBoard has no comm port
704 snprintf(buf+len, MSG_SIZ, "-%s%s", p->argName, PrintArg(p->argType));
705 if(p->save) strcat(buf+len, "*");
706 for(q=p+1; q->argLoc == p->argLoc; q++) {
707 if(q->argName[0] == '-') continue;
708 strcat(buf+len, q == p+1 ? " (" : " ");
709 sprintf(buf+strlen(buf), "-%s%s", q->argName, PrintArg(q->argType));
711 if(q != p+1) strcat(buf+len, ")");
713 if(len > 39) len = 0, printf("%s\n", buf); else while(len < 39) buf[len++] = ' ';
716 if(len) buf[len] = NULLCHAR, printf("%s\n", buf);
720 SlaveResize (Option *opt)
722 static int slaveW, slaveH, w, h;
725 gtk_widget_get_allocation(shells[DummyDlg], &a);
726 w = a.width; h = a.height;
727 gtk_widget_get_allocation(opt->handle, &a);
728 slaveW = w - opt->max; // [HGM] needed to set new shellWidget size when we resize board
729 slaveH = h - a.height + 13;
731 gtk_window_resize(GTK_WINDOW(shells[DummyDlg]), slaveW + opt->max, slaveH + opt->value);
735 static char clickedFile[MSG_SIZ];
739 StartNewXBoard(GtkosxApplication *app, gchar *path, gpointer user_data)
740 { // handler of OSX OpenFile signal, which sends us the filename of clicked file or first argument
741 if(suppress) { // we just started XBoard without arguments
742 strncpy(clickedFile, path, MSG_SIZ); // remember file name, but otherwise ignore
743 } else { // we are running something presumably useful
745 snprintf(buf, MSG_SIZ, "open -n -a \"xboard\" --args \"%s\"", path);
746 system(buf); // start new instance on this file
753 main (int argc, char **argv)
755 int i, clockFontPxlSize, coordFontPxlSize, fontPxlSize;
756 int boardWidth, w, h; //, boardHeight;
758 int forceMono = False;
760 srandom(time(0)); // [HGM] book: make random truly random
762 setbuf(stdout, NULL);
763 setbuf(stderr, NULL);
766 if(argc > 1 && (!strcmp(argv[1], "-v" ) || !strcmp(argv[1], "--version" ))) {
767 printf("%s version %s\n\n configure options: %s\n", PACKAGE_NAME, PACKAGE_VERSION, CONFIGURE_OPTIONS);
771 if(argc > 1 && !strcmp(argv[1], "--help" )) {
777 gtk_init (&argc, &argv);
779 { // prepare to catch OX OpenFile signal, which will tell us the clicked file
780 GtkosxApplication *theApp = g_object_new(GTKOSX_TYPE_APPLICATION, NULL);
781 char *path = gtkosx_application_get_bundle_path();
782 strncpy(dataDir, path, MSG_SIZ);
783 snprintf(masterSettings, MSG_SIZ, "%s/Contents/Resources/etc/xboard.conf", path);
784 g_signal_connect(theApp, "NSApplicationOpenFile", G_CALLBACK(StartNewXBoard), NULL);
785 // we must call application ready before we can get the signal,
786 // and supply a (dummy) menu bar before that, to avoid problems with dual apples in it
787 gtkosx_application_set_menu_bar(theApp, GTK_MENU_SHELL(gtk_menu_bar_new()));
788 gtkosx_application_ready(theApp);
789 suppress = (argc == 1 || argc > 1 && argv[1][00] != '-'); // OSX sends signal even if name was already argv[1]!
790 if(argc == 1) { // called without args: OSX open-file signal might follow
791 static char *fakeArgv[3] = {NULL, clickedFile, NULL};
792 usleep(10000); // wait 10 msec (and hope this is long enough).
793 while(gtk_events_pending())
794 gtk_main_iteration(); // process all events that came in upto now
795 suppress = 0; // future open-file signals should start new instance
796 if(clickedFile[0]) { // we were sent an open-file signal with filename!
797 fakeArgv[0] = argv[0];
798 argc = 2; argv = fakeArgv; // fake that we were called as "xboard filename"
804 if(argc > 1 && !strcmp(argv[1], "--show-config")) { // [HGM] install: called to print config info
805 typedef struct {char *name, *value; } Config;
806 static Config configList[] = {
807 { "Datadir", DATADIR },
808 { "Sysconfdir", SYSCONFDIR },
813 for(i=0; configList[i].name; i++) {
814 if(argc > 2 && strcmp(argv[2], configList[i].name)) continue;
815 if(argc > 2) printf("%s", configList[i].value);
816 else printf("%-12s: %s\n", configList[i].name, configList[i].value);
821 /* set up keyboard accelerators group */
822 GtkAccelerators = gtk_accel_group_new();
824 programName = strrchr(argv[0], '/');
825 if (programName == NULL)
826 programName = argv[0];
831 // if (appData.debugMode) {
832 // fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
835 bindtextdomain(PACKAGE, LOCALEDIR);
836 bind_textdomain_codeset(PACKAGE, "UTF-8"); // needed when creating markup for the clocks
840 appData.boardSize = "";
841 InitAppData(ConvertToLine(argc, argv));
843 if (p == NULL) p = "/tmp";
844 i = strlen(p) + strlen("/.xboardXXXXXx.pgn") + 1;
845 gameCopyFilename = (char*) malloc(i);
846 gamePasteFilename = (char*) malloc(i);
847 snprintf(gameCopyFilename,i, "%s/.xboard%05uc.pgn", p, getpid());
848 snprintf(gamePasteFilename,i, "%s/.xboard%05up.pgn", p, getpid());
850 { // [HGM] initstring: kludge to fix bad bug. expand '\n' characters in init string and computer string.
851 static char buf[MSG_SIZ];
852 snprintf(buf, MSG_SIZ, appData.sysOpen, DATADIR);
853 ASSIGN(appData.sysOpen, buf); // expand %s in -openCommand to DATADIR (usefull for OS X configuring)
854 EscapeExpand(buf, appData.firstInitString);
855 appData.firstInitString = strdup(buf);
856 EscapeExpand(buf, appData.secondInitString);
857 appData.secondInitString = strdup(buf);
858 EscapeExpand(buf, appData.firstComputerString);
859 appData.firstComputerString = strdup(buf);
860 EscapeExpand(buf, appData.secondComputerString);
861 appData.secondComputerString = strdup(buf);
864 if ((chessDir = (char *) getenv("CHESSDIR")) == NULL) {
867 if (chdir(chessDir) != 0) {
868 fprintf(stderr, _("%s: can't cd to CHESSDIR: "), programName);
874 if (appData.debugMode && appData.nameOfDebugFile && strcmp(appData.nameOfDebugFile, "stderr")) {
875 /* [DM] debug info to file [HGM] make the filename a command-line option, and allow it to remain stderr */
876 if ((debugFP = fopen(appData.nameOfDebugFile, "w")) == NULL) {
877 printf(_("Failed to open file '%s'\n"), appData.nameOfDebugFile);
880 setbuf(debugFP, NULL);
884 if (appData.debugMode) {
885 fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
889 /* [HGM,HR] make sure board size is acceptable */
890 if(appData.NrFiles > BOARD_FILES ||
891 appData.NrRanks > BOARD_RANKS )
892 DisplayFatalError(_("Recompile with larger BOARD_RANKS or BOARD_FILES to support this size"), 0, 2);
895 /* This feature does not work; animation needs a rewrite */
896 appData.highlightDragging = FALSE;
900 gameInfo.variant = StringToVariant(appData.variant);
904 * determine size, based on supplied or remembered -size, or screen size
906 if (isdigit(appData.boardSize[0])) {
907 i = sscanf(appData.boardSize, "%d,%d,%d,%d,%d,%d,%d", &squareSize,
908 &lineGap, &clockFontPxlSize, &coordFontPxlSize,
909 &fontPxlSize, &smallLayout, &tinyLayout);
911 fprintf(stderr, _("%s: bad boardSize syntax %s\n"),
912 programName, appData.boardSize);
916 /* Find some defaults; use the nearest known size */
917 SizeDefaults *szd, *nearest;
918 int distance = 99999;
919 nearest = szd = sizeDefaults;
920 while (szd->name != NULL) {
921 if (abs(szd->squareSize - squareSize) < distance) {
923 distance = abs(szd->squareSize - squareSize);
924 if (distance == 0) break;
928 if (i < 2) lineGap = nearest->lineGap;
929 if (i < 3) clockFontPxlSize = nearest->clockFontPxlSize;
930 if (i < 4) coordFontPxlSize = nearest->coordFontPxlSize;
931 if (i < 5) fontPxlSize = nearest->fontPxlSize;
932 if (i < 6) smallLayout = nearest->smallLayout;
933 if (i < 7) tinyLayout = nearest->tinyLayout;
936 SizeDefaults *szd = sizeDefaults;
937 if (*appData.boardSize == NULLCHAR) {
938 GdkScreen *screen = gtk_window_get_screen(GTK_WINDOW(mainwindow));
939 guint screenwidth = gdk_screen_get_width(screen);
940 guint screenheight = gdk_screen_get_height(screen);
941 while (screenwidth < szd->minScreenSize ||
942 screenheight < szd->minScreenSize) {
945 if (szd->name == NULL) szd--;
946 appData.boardSize = strdup(szd->name); // [HGM] settings: remember name for saving settings
948 while (szd->name != NULL &&
949 StrCaseCmp(szd->name, appData.boardSize) != 0) szd++;
950 if (szd->name == NULL) {
951 fprintf(stderr, _("%s: unrecognized boardSize name %s\n"),
952 programName, appData.boardSize);
956 squareSize = szd->squareSize;
957 lineGap = szd->lineGap;
958 clockFontPxlSize = szd->clockFontPxlSize;
959 coordFontPxlSize = szd->coordFontPxlSize;
960 fontPxlSize = szd->fontPxlSize;
961 smallLayout = szd->smallLayout;
962 tinyLayout = szd->tinyLayout;
963 // [HGM] font: use defaults from settings file if available and not overruled
966 defaultLineGap = lineGap;
967 if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
969 /* [HR] height treated separately (hacked) */
970 boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
971 // boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
974 * Determine what fonts to use.
977 InitializeFonts(clockFontPxlSize, coordFontPxlSize, fontPxlSize);
981 * Detect if there are not enough colors available and adapt.
984 if (DefaultDepth(xDisplay, xScreen) <= 2) {
985 appData.monoMode = True;
989 forceMono = MakeColors();
992 fprintf(stderr, _("%s: too few colors available; trying monochrome mode\n"),
994 appData.monoMode = True;
997 ParseIcsTextColors();
1003 layoutName = "tinyLayout";
1004 } else if (smallLayout) {
1005 layoutName = "smallLayout";
1007 layoutName = "normalLayout";
1010 wpMain.width = -1; // prevent popup sizes window
1011 optList = BoardPopUp(squareSize, lineGap, (void*)
1021 InitDrawingHandle(optList + W_BOARD);
1022 shellWidget = shells[BoardWindow];
1023 currBoard = &optList[W_BOARD];
1024 boardWidget = optList[W_BOARD].handle;
1025 menuBarWidget = optList[W_MENU].handle;
1026 dropMenu = optList[W_DROP].handle;
1027 titleWidget = optList[optList[W_TITLE].type != -1 ? W_TITLE : W_SMALL].handle;
1029 formWidget = XtParent(boardWidget);
1030 XtSetArg(args[0], XtNbackground, &timerBackgroundPixel);
1031 XtSetArg(args[1], XtNforeground, &timerForegroundPixel);
1032 XtGetValues(optList[W_WHITE].handle, args, 2);
1033 if (appData.showButtonBar) { // can't we use timer pixels for this? (Or better yet, just black & white?)
1034 XtSetArg(args[0], XtNbackground, &buttonBackgroundPixel);
1035 XtSetArg(args[1], XtNforeground, &buttonForegroundPixel);
1036 XtGetValues(optList[W_PAUSE].handle, args, 2);
1040 // [HGM] it seems the layout code ends here, but perhaps the color stuff is size independent and would
1041 // not need to go into InitDrawingSizes().
1045 // add accelerators to main shell
1046 gtk_window_add_accel_group(GTK_WINDOW(shellWidget), GtkAccelerators);
1049 * Create an icon. (Use two icons, to indicate whther it is white's or black's turn.)
1051 WhiteIcon = gdk_pixbuf_new_from_file(SVGDIR "/icon_white.svg", NULL);
1052 BlackIcon = gdk_pixbuf_new_from_file(SVGDIR "/icon_black.svg", NULL);
1053 mainwindowIcon = WhiteIcon;
1054 gtk_window_set_icon(GTK_WINDOW(shellWidget), mainwindowIcon);
1058 * Create a cursor for the board widget.
1061 window_attributes.cursor = XCreateFontCursor(xDisplay, XC_hand2);
1062 XChangeWindowAttributes(xDisplay, xBoardWindow,
1063 CWCursor, &window_attributes);
1067 * Inhibit shell resizing.
1070 shellArgs[0].value = (XtArgVal) &w;
1071 shellArgs[1].value = (XtArgVal) &h;
1072 XtGetValues(shellWidget, shellArgs, 2);
1073 shellArgs[4].value = shellArgs[2].value = w;
1074 shellArgs[5].value = shellArgs[3].value = h;
1075 // XtSetValues(shellWidget, &shellArgs[2], 4);
1078 // Note: We cannot do sensible sizing here, because the height of the clock widget is not yet known
1079 // It wil only become known asynchronously, when we first write a string into it.
1080 // This will then change the clock widget height, which triggers resizing the top-level window
1081 // and a configure event. Only then can we know the total height of the top-level window,
1082 // and calculate the height we need. The clockKludge flag suppresses all resizing until
1083 // that moment comes, after which the configure event-handler handles it through a (delayed) DragProg.
1086 gtk_widget_get_allocation(shells[BoardWindow], &a);
1087 w = a.width; h = a.height;
1088 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1089 clockKludge = hc = a.height;
1090 gtk_widget_get_allocation(boardWidget, &a);
1091 marginW = w - boardWidth; // [HGM] needed to set new shellWidget size when we resize board
1092 marginH = h - a.height - hc; // subtract current clock height, so it can be added back dynamically
1098 if(appData.logoSize)
1099 { // locate and read user logo
1101 snprintf(buf, MSG_SIZ, "%s/%s.png", appData.logoDir, UserName());
1102 ASSIGN(userLogo, buf);
1105 if (appData.animate || appData.animateDragging)
1108 g_signal_connect(shells[BoardWindow], "key-press-event", G_CALLBACK(KeyPressProc), NULL);
1109 g_signal_connect(shells[BoardWindow], "configure-event", G_CALLBACK(EventProc), NULL);
1111 /* [AS] Restore layout */
1112 if( wpMoveHistory.visible ) {
1116 if( wpEvalGraph.visible )
1121 if( wpEngineOutput.visible ) {
1122 EngineOutputPopUp();
1125 if( wpConsole.visible && appData.icsActive ) {
1132 if (errorExitStatus == -1) {
1133 if (appData.icsActive) {
1134 /* We now wait until we see "login:" from the ICS before
1135 sending the logon script (problems with timestamp otherwise) */
1136 /*ICSInitScript();*/
1137 if (appData.icsInputBox) ICSInputBoxPopUp();
1141 signal(SIGWINCH, TermSizeSigHandler);
1143 signal(SIGINT, IntSigHandler);
1144 signal(SIGTERM, IntSigHandler);
1145 if (*appData.cmailGameName != NULLCHAR) {
1146 signal(SIGUSR1, CmailSigHandler);
1150 gameInfo.boardWidth = 0; // [HGM] pieces: kludge to ensure InitPosition() calls InitDrawingSizes()
1153 // XtSetKeyboardFocus(shellWidget, formWidget);
1155 XSetInputFocus(xDisplay, XtWindow(formWidget), RevertToPointerRoot, CurrentTime);
1158 /* check for GTK events and process them */
1161 gtk_main_iteration();
1164 if (appData.debugMode) fclose(debugFP); // [DM] debug
1169 TermSizeSigHandler (int sig)
1175 IntSigHandler (int sig)
1181 CmailSigHandler (int sig)
1186 signal(SIGUSR1, SIG_IGN); /* suspend handler */
1188 /* Activate call-back function CmailSigHandlerCallBack() */
1189 OutputToProcess(cmailPR, (char *)(&dummy), sizeof(int), &error);
1191 signal(SIGUSR1, CmailSigHandler); /* re-activate handler */
1195 CmailSigHandlerCallBack (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1198 ReloadCmailMsgEvent(TRUE); /* Reload cmail msg */
1200 /**** end signal code ****/
1203 #define Abs(n) ((n)<0 ? -(n) : (n))
1207 InsertPxlSize (char *pattern, int targetPxlSize)
1209 char *base_fnt_lst, strInt[12], *p, *q;
1210 int alternatives, i, len, strIntLen;
1213 * Replace the "*" (if present) in the pixel-size slot of each
1214 * alternative with the targetPxlSize.
1218 while ((p = strchr(p, ',')) != NULL) {
1222 snprintf(strInt, sizeof(strInt), "%d", targetPxlSize);
1223 strIntLen = strlen(strInt);
1224 base_fnt_lst = calloc(1, strlen(pattern) + strIntLen * alternatives + 1);
1228 while (alternatives--) {
1229 char *comma = strchr(p, ',');
1230 for (i=0; i<14; i++) {
1231 char *hyphen = strchr(p, '-');
1233 if (comma && hyphen > comma) break;
1234 len = hyphen + 1 - p;
1235 if (i == 7 && *p == '*' && len == 2) {
1237 memcpy(q, strInt, strIntLen);
1247 len = comma + 1 - p;
1254 return base_fnt_lst;
1259 CreateFontSet (char *base_fnt_lst)
1262 char **missing_list;
1266 fntSet = XCreateFontSet(xDisplay, base_fnt_lst,
1267 &missing_list, &missing_count, &def_string);
1268 if (appData.debugMode) {
1270 XFontStruct **font_struct_list;
1271 char **font_name_list;
1272 fprintf(debugFP, "Requested font set for list %s\n", base_fnt_lst);
1274 fprintf(debugFP, " got list %s, locale %s\n",
1275 XBaseFontNameListOfFontSet(fntSet),
1276 XLocaleOfFontSet(fntSet));
1277 count = XFontsOfFontSet(fntSet, &font_struct_list, &font_name_list);
1278 for (i = 0; i < count; i++) {
1279 fprintf(debugFP, " got charset %s\n", font_name_list[i]);
1282 for (i = 0; i < missing_count; i++) {
1283 fprintf(debugFP, " missing charset %s\n", missing_list[i]);
1286 if (fntSet == NULL) {
1287 fprintf(stderr, _("Unable to create font set for %s.\n"), base_fnt_lst);
1293 #else // not ENABLE_NLS
1295 * Find a font that matches "pattern" that is as close as
1296 * possible to the targetPxlSize. Prefer fonts that are k
1297 * pixels smaller to fonts that are k pixels larger. The
1298 * pattern must be in the X Consortium standard format,
1299 * e.g. "-*-helvetica-bold-r-normal--*-*-*-*-*-*-*-*".
1300 * The return value should be freed with XtFree when no
1304 FindFont (char *pattern, int targetPxlSize)
1306 char **fonts, *p, *best, *scalable, *scalableTail;
1307 int i, j, nfonts, minerr, err, pxlSize;
1310 fonts = XListFonts(xDisplay, pattern, 999999, &nfonts);
1312 fprintf(stderr, _("%s: no fonts match pattern %s\n"),
1313 programName, pattern);
1320 for (i=0; i<nfonts; i++) {
1323 if (*p != '-') continue;
1325 if (*p == NULLCHAR) break;
1326 if (*p++ == '-') j++;
1328 if (j < 7) continue;
1331 scalable = fonts[i];
1334 err = pxlSize - targetPxlSize;
1335 if (Abs(err) < Abs(minerr) ||
1336 (minerr > 0 && err < 0 && -err == minerr)) {
1342 if (scalable && Abs(minerr) > appData.fontSizeTolerance) {
1343 /* If the error is too big and there is a scalable font,
1344 use the scalable font. */
1345 int headlen = scalableTail - scalable;
1346 p = (char *) XtMalloc(strlen(scalable) + 10);
1347 while (isdigit(*scalableTail)) scalableTail++;
1348 sprintf(p, "%.*s%d%s", headlen, scalable, targetPxlSize, scalableTail);
1350 p = (char *) XtMalloc(strlen(best) + 2);
1351 safeStrCpy(p, best, strlen(best)+1 );
1353 if (appData.debugMode) {
1354 fprintf(debugFP, "resolved %s at pixel size %d\n to %s\n",
1355 pattern, targetPxlSize, p);
1357 XFreeFontNames(fonts);
1364 EnableNamedMenuItem (char *menuRef, int state)
1366 MenuItem *item = MenuNameToItem(menuRef);
1368 if(item && item->handle) gtk_widget_set_sensitive(item->handle, state);
1372 EnableButtonBar (int state)
1375 XtSetSensitive(optList[W_BUTTON].handle, state);
1381 SetMenuEnables (Enables *enab)
1383 while (enab->name != NULL) {
1384 EnableNamedMenuItem(enab->name, enab->value);
1389 gboolean KeyPressProc(window, eventkey, data)
1391 GdkEventKey *eventkey;
1395 MoveTypeInProc(eventkey); // pop up for typed in moves
1398 /* check for other key values */
1399 switch(eventkey->keyval) {
1411 KeyBindingProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1412 { // [HGM] new method of key binding: specify MenuItem(FlipView) in stead of FlipViewProc in translation string
1414 if(*nprms == 0) return;
1415 item = MenuNameToItem(prms[0]);
1416 if(item) ((MenuProc *) item->proc) ();
1430 for (i=0; i<sizeof(dmEnables)/sizeof(DropMenuEnables); i++) {
1431 entry = XtNameToWidget(dropMenu, dmEnables[i].widget);
1432 p = strchr(gameMode == IcsPlayingWhite ? white_holding : black_holding,
1433 dmEnables[i].piece);
1434 XtSetSensitive(entry, p != NULL || !appData.testLegality
1435 /*!!temp:*/ || (gameInfo.variant == VariantCrazyhouse
1436 && !appData.icsActive));
1438 while (p && *p++ == dmEnables[i].piece) count++;
1439 snprintf(label, sizeof(label), "%s %d", dmEnables[i].widget, count);
1441 XtSetArg(args[j], XtNlabel, label); j++;
1442 XtSetValues(entry, args, j);
1448 do_flash_delay (unsigned long msec)
1454 FlashDelay (int flash_delay)
1456 if(flash_delay) do_flash_delay(flash_delay);
1460 Fraction (int x, int start, int stop)
1462 double f = ((double) x - start)/(stop - start);
1463 if(f > 1.) f = 1.; else if(f < 0.) f = 0.;
1467 static WindowPlacement wpNew;
1470 CoDrag (GtkWidget *sh, WindowPlacement *wp)
1472 int touch=0, fudge = 2, f = 2;
1473 GetActualPlacement(sh, wp);
1474 if(abs(wpMain.x + wpMain.width + 2*frameX - f - wp->x) < fudge) touch = 1; else // right touch
1475 if(abs(wp->x + wp->width + 2*frameX + f - wpMain.x) < fudge) touch = 2; else // left touch
1476 if(abs(wpMain.y + wpMain.height + frameX - f + frameY - wp->y) < fudge) touch = 3; else // bottom touch
1477 if(abs(wp->y + wp->height + frameX + frameY + f - wpMain.y) < fudge) touch = 4; // top touch
1478 //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);
1479 if(!touch ) return; // only windows that touch co-move
1480 if(touch < 3 && wpNew.height != wpMain.height) { // left or right and height changed
1481 int heightInc = wpNew.height - wpMain.height;
1482 double fracTop = Fraction(wp->y, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1483 double fracBot = Fraction(wp->y + wp->height + frameX + frameY + 1, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1484 wp->y += fracTop * heightInc;
1485 heightInc = (int) (fracBot * heightInc) - (int) (fracTop * heightInc);
1487 if(heightInc) XtSetArg(args[j], XtNheight, wp->height + heightInc), j++;
1489 wp->height += heightInc;
1490 } else if(touch > 2 && wpNew.width != wpMain.width) { // top or bottom and width changed
1491 int widthInc = wpNew.width - wpMain.width;
1492 double fracLeft = Fraction(wp->x, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1493 double fracRght = Fraction(wp->x + wp->width + 2*frameX + 1, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1494 wp->y += fracLeft * widthInc;
1495 widthInc = (int) (fracRght * widthInc) - (int) (fracLeft * widthInc);
1497 if(widthInc) XtSetArg(args[j], XtNwidth, wp->width + widthInc), j++;
1499 wp->width += widthInc;
1501 wp->x += wpNew.x - wpMain.x;
1502 wp->y += wpNew.y - wpMain.y;
1503 if(touch == 1) wp->x += wpNew.width - wpMain.width; else
1504 if(touch == 3) wp->y += wpNew.height - wpMain.height;
1506 XtSetArg(args[j], XtNx, wp->x); j++;
1507 XtSetArg(args[j], XtNy, wp->y); j++;
1508 XtSetValues(sh, args, j);
1510 gtk_window_move(GTK_WINDOW(sh), wp->x, wp->y);
1511 //printf("moved to (%d,%d)\n", wp->x, wp->y);
1512 gtk_window_resize(GTK_WINDOW(sh), wp->width, wp->height);
1516 ReSize (WindowPlacement *wp)
1519 int sqx, sqy, w, h, hc, lg = lineGap;
1520 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1521 hc = a.height; // clock height can depend on single / double line clock text!
1522 if(clockKludge == a.height) return; // wait for clock to get final size at startup
1523 if(clockKludge) { // clock height OK now; calculate desired initial board height
1525 wp->height = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + marginH + hc;
1527 if(wp->width == wpMain.width && wp->height == wpMain.height) return; // not sized
1528 sqx = (wp->width - lg - marginW) / BOARD_WIDTH - lg;
1529 sqy = (wp->height - lg - marginH - hc) / BOARD_HEIGHT - lg;
1530 if(sqy < sqx) sqx = sqy;
1531 if(appData.overrideLineGap < 0) { // do second iteration with adjusted lineGap
1532 lg = lineGap = sqx < 37 ? 1 : sqx < 59 ? 2 : sqx < 116 ? 3 : 4;
1533 sqx = (wp->width - lg - marginW) / BOARD_WIDTH - lg;
1534 sqy = (wp->height - lg - marginH - hc) / BOARD_HEIGHT - lg;
1535 if(sqy < sqx) sqx = sqy;
1537 if(sqx != squareSize) {
1538 //printf("new sq size %d (%dx%d)\n", sqx, wp->width, wp->height);
1539 squareSize = sqx; // adopt new square size
1540 CreatePNGPieces(); // make newly scaled pieces
1541 InitDrawingSizes(0, 0); // creates grid etc.
1542 } else ResizeBoardWindow(BOARD_WIDTH * (squareSize + lineGap) + lineGap, BOARD_HEIGHT * (squareSize + lineGap) + lineGap, 0);
1543 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
1544 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
1545 if(optList[W_BOARD].max > w) optList[W_BOARD].max = w;
1546 if(optList[W_BOARD].value > h) optList[W_BOARD].value = h;
1549 static guint delayedDragTag = 0;
1558 // GetActualPlacement(shellWidget, &wpNew);
1559 if(wpNew.x == wpMain.x && wpNew.y == wpMain.y && // not moved
1560 wpNew.width == wpMain.width && wpNew.height == wpMain.height) { // not sized
1561 busy = 0; return; // false alarm
1564 if(appData.useStickyWindows) {
1565 if(shellUp[EngOutDlg]) CoDrag(shells[EngOutDlg], &wpEngineOutput);
1566 if(shellUp[HistoryDlg]) CoDrag(shells[HistoryDlg], &wpMoveHistory);
1567 if(shellUp[EvalGraphDlg]) CoDrag(shells[EvalGraphDlg], &wpEvalGraph);
1568 if(shellUp[GameListDlg]) CoDrag(shells[GameListDlg], &wpGameList);
1569 if(shellUp[ChatDlg]) CoDrag(shells[ChatDlg], &wpConsole);
1572 DrawPosition(True, NULL);
1573 if(delayedDragTag) g_source_remove(delayedDragTag);
1574 delayedDragTag = 0; // now drag executed, make sure next DelayedDrag will not cancel timer event (which could now be used by other)
1581 //printf("old timr = %d\n", delayedDragTag);
1582 if(delayedDragTag) g_source_remove(delayedDragTag);
1583 delayedDragTag = g_timeout_add( 200, (GSourceFunc) DragProc, NULL);
1584 //printf("new timr = %d\n", delayedDragTag);
1588 EventProc (GtkWidget *widget, GdkEvent *event, gpointer g)
1590 //printf("event proc (%d,%d) %dx%d\n", event->configure.x, event->configure.y, event->configure.width, event->configure.height);
1592 wpNew.x = event->configure.x;
1593 wpNew.y = event->configure.y;
1594 wpNew.width = event->configure.width;
1595 wpNew.height = event->configure.height;
1596 DelayedDrag(); // as long as events keep coming in faster than 50 msec, they destroy each other
1602 /* Disable all user input other than deleting the window */
1603 static int frozen = 0;
1609 /* Grab by a widget that doesn't accept input */
1610 gtk_grab_add(optList[W_MESSG].handle);
1614 /* Undo a FreezeUI */
1618 if (!frozen) return;
1619 gtk_grab_remove(optList[W_MESSG].handle);
1626 static int oldPausing = FALSE;
1627 static GameMode oldmode = (GameMode) -1;
1629 if (!boardWidget) return;
1631 if (pausing != oldPausing) {
1632 oldPausing = pausing;
1633 MarkMenuItem("Mode.Pause", pausing);
1635 if (appData.showButtonBar) {
1636 /* Always toggle, don't set. Previous code messes up when
1637 invoked while the button is pressed, as releasing it
1638 toggles the state again. */
1640 gdk_color_parse( pausing ? "#808080" : "#F0F0F0", &color );
1641 gtk_widget_modify_bg ( GTK_WIDGET(optList[W_PAUSE].handle), GTK_STATE_NORMAL, &color );
1645 wname = ModeToWidgetName(oldmode);
1646 if (wname != NULL) {
1647 MarkMenuItem(wname, False);
1649 wname = ModeToWidgetName(gameMode);
1650 if (wname != NULL) {
1651 MarkMenuItem(wname, True);
1654 MarkMenuItem("Mode.MachineMatch", matchMode && matchGame < appData.matchGames);
1656 /* Maybe all the enables should be handled here, not just this one */
1657 EnableNamedMenuItem("Mode.Training", gameMode == Training || gameMode == PlayFromGameFile);
1659 DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
1664 * Button/menu procedures
1667 void CopyFileToClipboard(gchar *filename)
1669 gchar *selection_tmp;
1673 FILE* f = fopen(filename, "r");
1676 if (f == NULL) return;
1680 selection_tmp = g_try_malloc(len + 1);
1681 if (selection_tmp == NULL) {
1682 printf("Malloc failed in CopyFileToClipboard\n");
1685 count = fread(selection_tmp, 1, len, f);
1688 g_free(selection_tmp);
1691 selection_tmp[len] = NULLCHAR; // file is now in selection_tmp
1693 // copy selection_tmp to clipboard
1694 GdkDisplay *gdisp = gdk_display_get_default();
1696 g_free(selection_tmp);
1699 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1700 gtk_clipboard_set_text(cb, selection_tmp, -1);
1701 g_free(selection_tmp);
1705 CopySomething (char *src)
1707 GdkDisplay *gdisp = gdk_display_get_default();
1709 if(!src) { CopyFileToClipboard(gameCopyFilename); return; }
1710 if (gdisp == NULL) return;
1711 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1712 gtk_clipboard_set_text(cb, src, -1);
1716 PastePositionProc ()
1718 GdkDisplay *gdisp = gdk_display_get_default();
1722 if (gdisp == NULL) return;
1723 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1724 fenstr = gtk_clipboard_wait_for_text(cb);
1725 if (fenstr==NULL) return; // nothing had been selected to copy
1726 EditPositionPasteFEN(fenstr);
1738 // get game from clipboard
1739 GdkDisplay *gdisp = gdk_display_get_default();
1740 if (gdisp == NULL) return;
1741 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1742 text = gtk_clipboard_wait_for_text(cb);
1743 if (text == NULL) return; // nothing to paste
1746 // write to temp file
1747 if (text == NULL || len == 0) {
1748 return; //nothing to paste
1750 f = fopen(gamePasteFilename, "w");
1752 DisplayError(_("Can't open temp file"), errno);
1755 fwrite(text, 1, len, f);
1759 LoadGameFromFile(gamePasteFilename, 0, gamePasteFilename, TRUE);
1766 QuitWrapper (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1772 void MoveTypeInProc(eventkey)
1773 GdkEventKey *eventkey;
1777 // ingnore if ctrl, alt, or meta is pressed
1778 if (eventkey->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_META_MASK)) {
1782 buf[0]=eventkey->keyval;
1784 if (eventkey->keyval > 32 && eventkey->keyval < 256)
1790 TempBackwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1792 if (!TempBackwardActive) {
1793 TempBackwardActive = True;
1799 TempForwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1801 /* Check to see if triggered by a key release event for a repeating key.
1802 * If so the next queued event will be a key press of the same key at the same time */
1803 if (XEventsQueued(xDisplay, QueuedAfterReading)) {
1805 XPeekEvent(xDisplay, &next);
1806 if (next.type == KeyPress && next.xkey.time == event->xkey.time &&
1807 next.xkey.keycode == event->xkey.keycode)
1811 TempBackwardActive = False;
1817 { // called from menu
1820 snprintf(buf, MSG_SIZ, "%s ./man.command", appData.sysOpen);
1823 system("xterm -e man xboard &");
1828 SetWindowTitle (char *text, char *title, char *icon)
1833 if (appData.titleInWindow) {
1835 XtSetArg(args[i], XtNlabel, text); i++;
1836 XtSetValues(titleWidget, args, i);
1839 XtSetArg(args[i], XtNiconName, (XtArgVal) icon); i++;
1840 XtSetArg(args[i], XtNtitle, (XtArgVal) title); i++;
1841 XtSetValues(shellWidget, args, i);
1842 XSync(xDisplay, False);
1844 if (appData.titleInWindow) {
1845 SetWidgetLabel(titleWidget, text);
1847 gtk_window_set_title (GTK_WINDOW(shells[BoardWindow]), title);
1852 DisplayIcsInteractionTitle (String message)
1855 if (oldICSInteractionTitle == NULL) {
1856 /* Magic to find the old window title, adapted from vim */
1857 char *wina = getenv("WINDOWID");
1859 Window win = (Window) atoi(wina);
1860 Window root, parent, *children;
1861 unsigned int nchildren;
1862 int (*oldHandler)() = XSetErrorHandler(NullXErrorCheck);
1864 if (XFetchName(xDisplay, win, &oldICSInteractionTitle)) break;
1865 if (!XQueryTree(xDisplay, win, &root, &parent,
1866 &children, &nchildren)) break;
1867 if (children) XFree((void *)children);
1868 if (parent == root || parent == 0) break;
1871 XSetErrorHandler(oldHandler);
1873 if (oldICSInteractionTitle == NULL) {
1874 oldICSInteractionTitle = "xterm";
1877 printf("\033]0;%s\007", message);
1884 DisplayTimerLabel (Option *opt, char *color, long timer, int highlight)
1886 GtkWidget *w = (GtkWidget *) opt->handle;
1893 strcpy(bgcolor, "black");
1894 strcpy(fgcolor, "white");
1896 strcpy(bgcolor, "white");
1897 strcpy(fgcolor, "black");
1900 appData.lowTimeWarning &&
1901 (timer / 1000) < appData.icsAlarmTime) {
1902 strcpy(fgcolor, appData.lowTimeWarningColor);
1905 gdk_color_parse( bgcolor, &col );
1906 gtk_widget_modify_bg(gtk_widget_get_parent(opt->handle), GTK_STATE_NORMAL, &col);
1908 if (appData.clockMode) {
1909 markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s:%s%s</span>",
1910 bgcolor, fgcolor, color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
1912 markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s </span>",
1913 bgcolor, fgcolor, color);
1915 gtk_label_set_markup(GTK_LABEL(w), markup);
1919 static GdkPixbuf **clockIcons[] = { &WhiteIcon, &BlackIcon };
1922 SetClockIcon (int color)
1924 GdkPixbuf *pm = *clockIcons[color];
1925 if (mainwindowIcon != pm) {
1926 mainwindowIcon = pm;
1927 gtk_window_set_icon(GTK_WINDOW(shellWidget), mainwindowIcon);
1931 #define INPUT_SOURCE_BUF_SIZE 8192
1940 char buf[INPUT_SOURCE_BUF_SIZE];
1945 DoInputCallback(io, cond, data)
1950 /* read input from one of the input source (for example a chess program, ICS, etc).
1951 * and call a function that will handle the input
1958 /* All information (callback function, file descriptor, etc) is
1959 * saved in an InputSource structure
1961 InputSource *is = (InputSource *) data;
1963 if (is->lineByLine) {
1964 count = read(is->fd, is->unused,
1965 INPUT_SOURCE_BUF_SIZE - (is->unused - is->buf));
1967 (is->func)(is, is->closure, is->buf, count, count ? errno : 0);
1970 is->unused += count;
1972 /* break input into lines and call the callback function on each
1975 while (p < is->unused) {
1976 q = memchr(p, '\n', is->unused - p);
1977 if (q == NULL) break;
1979 (is->func)(is, is->closure, p, q - p, 0);
1982 /* remember not yet used part of the buffer */
1984 while (p < is->unused) {
1989 /* read maximum length of input buffer and send the whole buffer
1990 * to the callback function
1992 count = read(is->fd, is->buf, INPUT_SOURCE_BUF_SIZE);
1997 (is->func)(is, is->closure, is->buf, count, error);
1999 return True; // Must return true or the watch will be removed
2002 InputSourceRef AddInputSource(pr, lineByLine, func, closure)
2009 GIOChannel *channel;
2010 ChildProc *cp = (ChildProc *) pr;
2012 is = (InputSource *) calloc(1, sizeof(InputSource));
2013 is->lineByLine = lineByLine;
2017 is->fd = fileno(stdin);
2019 is->kind = cp->kind;
2020 is->fd = cp->fdFrom;
2023 is->unused = is->buf;
2027 /* GTK-TODO: will this work on windows?*/
2029 channel = g_io_channel_unix_new(is->fd);
2030 g_io_channel_set_close_on_unref (channel, TRUE);
2031 is->sid = g_io_add_watch(channel, G_IO_IN,(GIOFunc) DoInputCallback, is);
2033 is->closure = closure;
2034 return (InputSourceRef) is;
2039 RemoveInputSource(isr)
2042 InputSource *is = (InputSource *) isr;
2044 if (is->sid == 0) return;
2045 g_source_remove(is->sid);
2052 static Boolean frameWaiting;
2055 FrameAlarm (int sig)
2057 frameWaiting = False;
2058 /* In case System-V style signals. Needed?? */
2059 signal(SIGALRM, FrameAlarm);
2063 FrameDelay (int time)
2065 struct itimerval delay;
2068 frameWaiting = True;
2069 signal(SIGALRM, FrameAlarm);
2070 delay.it_interval.tv_sec =
2071 delay.it_value.tv_sec = time / 1000;
2072 delay.it_interval.tv_usec =
2073 delay.it_value.tv_usec = (time % 1000) * 1000;
2074 setitimer(ITIMER_REAL, &delay, NULL);
2075 while (frameWaiting) pause();
2076 delay.it_interval.tv_sec = delay.it_value.tv_sec = 0;
2077 delay.it_interval.tv_usec = delay.it_value.tv_usec = 0;
2078 setitimer(ITIMER_REAL, &delay, NULL);
2085 FrameDelay (int time)
2088 XSync(xDisplay, False);
2090 // gtk_main_iteration_do(False);
2093 usleep(time * 1000);
2099 LoadLogo (ChessProgramState *cps, int n, Boolean ics)
2101 char buf[MSG_SIZ], *logoName = buf;
2102 if(appData.logo[n][0]) {
2103 logoName = appData.logo[n];
2104 } else if(appData.autoLogo) {
2105 if(ics) { // [HGM] logo: in ICS mode second can be used for ICS
2106 sprintf(buf, "%s/%s.png", appData.logoDir, appData.icsHost);
2107 } else if(appData.directory[n] && appData.directory[n][0]) {
2108 sprintf(buf, "%s/%s.png", appData.logoDir, cps->tidy);
2112 { ASSIGN(cps->programLogo, logoName); }
2116 UpdateLogos (int displ)
2118 if(optList[W_WHITE-1].handle == NULL) return;
2119 LoadLogo(&first, 0, 0);
2120 LoadLogo(&second, 1, appData.icsActive);
2121 if(displ) DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
2125 void FileNamePopUpWrapper(label, def, filter, proc, pathFlag, openMode, name, fp)
2136 GtkFileFilter *gtkfilter;
2137 GtkFileFilter *gtkfilter_all;
2139 char fileext[10] = "";
2140 char *result = NULL;
2143 /* make a copy of the filter string, so that strtok can work with it*/
2144 cp = strdup(filter);
2146 /* add filters for file extensions */
2147 gtkfilter = gtk_file_filter_new();
2148 gtkfilter_all = gtk_file_filter_new();
2150 /* one filter to show everything */
2151 gtk_file_filter_add_pattern(gtkfilter_all, "*.*");
2152 gtk_file_filter_set_name (gtkfilter_all, "All Files");
2154 /* add filter if present */
2155 result = strtok(cp, space);
2156 while( result != NULL ) {
2157 snprintf(fileext,10,"*%s",result);
2158 result = strtok( NULL, space );
2159 gtk_file_filter_add_pattern(gtkfilter, fileext);
2162 /* second filter to only show what's useful */
2163 gtk_file_filter_set_name (gtkfilter,filter);
2165 if (openMode[0] == 'r')
2167 dialog = gtk_file_chooser_dialog_new (label,
2169 GTK_FILE_CHOOSER_ACTION_OPEN,
2170 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2171 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
2176 dialog = gtk_file_chooser_dialog_new (label,
2178 GTK_FILE_CHOOSER_ACTION_SAVE,
2179 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2180 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
2182 /* add filename suggestions */
2183 if (strlen(def) > 0 )
2184 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), def);
2186 //gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER (dialog),TRUE);
2190 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter_all);
2191 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2192 /* activate filter */
2193 gtk_file_chooser_set_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2195 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
2200 filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
2203 f = fopen(filename, openMode);
2206 DisplayError(_("Failed to open file"), errno);
2210 /* TODO add indec */
2212 ASSIGN(*name, filename);
2213 ScheduleDelayedEvent(DelayedLoad, 50);
2218 gtk_widget_destroy (dialog);