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
179 # define ICS_LOGON "Library/Preferences/XboardICS.conf"
180 # define SYSCONFDIR "../etc"
181 # define DATADIR dataDir
182 char *dataDir; // for expanding ~~
185 # define DATADIR "~~"
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);
539 PrintCommPortSettings (FILE *f, char *name)
540 { // This option does not exist in XBoard
544 EnsureOnScreen (int *x, int *y, int minX, int minY)
551 { // [HGM] args: allows testing if main window is realized from back-end
552 return DialogExists(BoardWindow);
556 PopUpStartupDialog ()
557 { // start menu not implemented in XBoard
561 ConvertToLine (int argc, char **argv)
563 static char line[128*1024], buf[1024];
567 for(i=1; i<argc; i++)
569 if( (strchr(argv[i], ' ') || strchr(argv[i], '\n') ||strchr(argv[i], '\t') || argv[i][0] == NULLCHAR)
570 && argv[i][0] != '{' )
571 snprintf(buf, sizeof(buf)/sizeof(buf[0]), "{%s} ", argv[i]);
573 snprintf(buf, sizeof(buf)/sizeof(buf[0]), "%s ", argv[i]);
574 strncat(line, buf, 128*1024 - strlen(line) - 1 );
577 line[strlen(line)-1] = NULLCHAR;
581 //--------------------------------------------------------------------------------------------
586 ResizeBoardWindow (int w, int h, int inhibit)
589 if(clockKludge) return; // ignore as long as clock does not have final height
590 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
591 w += marginW + 1; // [HGM] not sure why the +1 is (sometimes) needed...
592 h += marginH + a.height + 1;
593 gtk_window_resize(GTK_WINDOW(shellWidget), w, h);
598 { // dummy, as the GTK code does not make colors in advance
603 InitializeFonts (int clockFontPxlSize, int coordFontPxlSize, int fontPxlSize)
604 { // determine what fonts to use, and create them
609 if(!fontIsSet[CLOCK_FONT] && fontValid[CLOCK_FONT][squareSize])
610 appData.clockFont = fontTable[CLOCK_FONT][squareSize];
611 if(!fontIsSet[MESSAGE_FONT] && fontValid[MESSAGE_FONT][squareSize])
612 appData.font = fontTable[MESSAGE_FONT][squareSize];
613 if(!fontIsSet[COORD_FONT] && fontValid[COORD_FONT][squareSize])
614 appData.coordFont = fontTable[COORD_FONT][squareSize];
617 appData.font = InsertPxlSize(appData.font, fontPxlSize);
618 appData.clockFont = InsertPxlSize(appData.clockFont, clockFontPxlSize);
619 appData.coordFont = InsertPxlSize(appData.coordFont, coordFontPxlSize);
620 fontSet = CreateFontSet(appData.font);
621 clockFontSet = CreateFontSet(appData.clockFont);
623 /* For the coordFont, use the 0th font of the fontset. */
624 XFontSet coordFontSet = CreateFontSet(appData.coordFont);
625 XFontStruct **font_struct_list;
626 XFontSetExtents *fontSize;
627 char **font_name_list;
628 XFontsOfFontSet(coordFontSet, &font_struct_list, &font_name_list);
629 coordFontID = XLoadFont(xDisplay, font_name_list[0]);
630 coordFontStruct = XQueryFont(xDisplay, coordFontID);
631 fontSize = XExtentsOfFontSet(fontSet); // [HGM] figure out how much vertical space font takes
632 textHeight = fontSize->max_logical_extent.height + 5; // add borderWidth
635 appData.font = FindFont(appData.font, fontPxlSize);
636 appData.clockFont = FindFont(appData.clockFont, clockFontPxlSize);
637 appData.coordFont = FindFont(appData.coordFont, coordFontPxlSize);
638 clockFontID = XLoadFont(xDisplay, appData.clockFont);
639 clockFontStruct = XQueryFont(xDisplay, clockFontID);
640 coordFontID = XLoadFont(xDisplay, appData.coordFont);
641 coordFontStruct = XQueryFont(xDisplay, coordFontID);
642 // textHeight in !NLS mode!
644 countFontID = coordFontID; // [HGM] holdings
645 countFontStruct = coordFontStruct;
647 xdb = XtDatabase(xDisplay);
649 XrmPutLineResource(&xdb, "*international: True");
650 vTo.size = sizeof(XFontSet);
651 vTo.addr = (XtPointer) &fontSet;
652 XrmPutResource(&xdb, "*fontSet", XtRFontSet, &vTo);
654 XrmPutStringResource(&xdb, "*font", appData.font);
665 case ArgInt: p = " N"; break;
666 case ArgString: p = " STR"; break;
667 case ArgBoolean: p = " TF"; break;
668 case ArgSettingsFilename:
669 case ArgBackupSettingsFile:
670 case ArgFilename: p = " FILE"; break;
671 case ArgX: p = " Nx"; break;
672 case ArgY: p = " Ny"; break;
673 case ArgAttribs: p = " TEXTCOL"; break;
674 case ArgColor: p = " COL"; break;
675 case ArgFont: p = " FONT"; break;
676 case ArgBoardSize: p = " SIZE"; break;
677 case ArgFloat: p = " FLOAT"; break;
682 case ArgCommSettings:
694 ArgDescriptor *q, *p = argDescriptors+5;
695 printf("\nXBoard accepts the following options:\n"
696 "(N = integer, TF = true or false, STR = text string, FILE = filename,\n"
697 " Nx, Ny = relative coordinates, COL = color, FONT = X-font spec,\n"
698 " SIZE = board-size spec(s)\n"
699 " Within parentheses are short forms, or options to set to true or false.\n"
700 " Persistent options (saved in the settings file) are marked with *)\n\n");
702 if(p->argType == ArgCommSettings) { p++; continue; } // XBoard has no comm port
703 snprintf(buf+len, MSG_SIZ, "-%s%s", p->argName, PrintArg(p->argType));
704 if(p->save) strcat(buf+len, "*");
705 for(q=p+1; q->argLoc == p->argLoc; q++) {
706 if(q->argName[0] == '-') continue;
707 strcat(buf+len, q == p+1 ? " (" : " ");
708 sprintf(buf+strlen(buf), "-%s%s", q->argName, PrintArg(q->argType));
710 if(q != p+1) strcat(buf+len, ")");
712 if(len > 39) len = 0, printf("%s\n", buf); else while(len < 39) buf[len++] = ' ';
715 if(len) buf[len] = NULLCHAR, printf("%s\n", buf);
719 SlaveResize (Option *opt)
721 static int slaveW, slaveH, w, h;
724 gtk_widget_get_allocation(shells[DummyDlg], &a);
725 w = a.width; h = a.height;
726 gtk_widget_get_allocation(opt->handle, &a);
727 slaveW = w - opt->max; // [HGM] needed to set new shellWidget size when we resize board
728 slaveH = h - a.height + 13;
730 gtk_window_resize(GTK_WINDOW(shells[DummyDlg]), slaveW + opt->max, slaveH + opt->value);
734 static char clickedFile[MSG_SIZ];
738 StartNewXBoard(GtkosxApplication *app, gchar *path, gpointer user_data)
739 { // handler of OSX OpenFile signal, which sends us the filename of clicked file or first argument
740 if(suppress) { // we just started XBoard without arguments
741 strncpy(clickedFile, path, MSG_SIZ); // remember file name, but otherwise ignore
742 } else { // we are running something presumably useful
744 snprintf(buf, MSG_SIZ, "open -n -a \"xboard\" --args \"%s\"", path);
745 system(buf); // start new instance on this file
752 main (int argc, char **argv)
754 int i, clockFontPxlSize, coordFontPxlSize, fontPxlSize;
755 int boardWidth, boardHeight, w, h;
757 int forceMono = False;
759 srandom(time(0)); // [HGM] book: make random truly random
761 setbuf(stdout, NULL);
762 setbuf(stderr, NULL);
765 if(argc > 1 && (!strcmp(argv[1], "-v" ) || !strcmp(argv[1], "--version" ))) {
766 printf("%s version %s\n\n configure options: %s\n", PACKAGE_NAME, PACKAGE_VERSION, CONFIGURE_OPTIONS);
770 if(argc > 1 && !strcmp(argv[1], "--help" )) {
776 gtk_init (&argc, &argv);
778 { // prepare to catch OX OpenFile signal, which will tell us the clicked file
779 GtkosxApplication *theApp = g_object_new(GTKOSX_TYPE_APPLICATION, NULL);
780 dataDir = gtkosx_application_get_bundle_path();
781 g_signal_connect(theApp, "NSApplicationOpenFile", G_CALLBACK(StartNewXBoard), NULL);
782 // we must call application ready before we can get the signal,
783 // and supply a (dummy) menu bar before that, to avoid problems with dual apples in it
784 gtkosx_application_set_menu_bar(theApp, GTK_MENU_SHELL(gtk_menu_bar_new()));
785 gtkosx_application_ready(theApp);
786 suppress = (argc == 1 || argc > 1 && argv[1][00] != '-'); // OSX sends signal even if name was already argv[1]!
787 if(argc == 1) { // called without args: OSX open-file signal might follow
788 static char *fakeArgv[3] = {NULL, clickedFile, NULL};
789 usleep(10000); // wait 10 msec (and hope this is long enough).
790 while(gtk_events_pending())
791 gtk_main_iteration(); // process all events that came in upto now
792 suppress = 0; // future open-file signals should start new instance
793 if(clickedFile[0]) { // we were sent an open-file signal with filename!
794 fakeArgv[0] = argv[0];
795 argc = 2; argv = fakeArgv; // fake that we were called as "xboard filename"
801 /* set up keyboard accelerators group */
802 GtkAccelerators = gtk_accel_group_new();
804 programName = strrchr(argv[0], '/');
805 if (programName == NULL)
806 programName = argv[0];
811 // if (appData.debugMode) {
812 // fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
815 bindtextdomain(PACKAGE, LOCALEDIR);
816 bind_textdomain_codeset(PACKAGE, "UTF-8"); // needed when creating markup for the clocks
820 appData.boardSize = "";
821 InitAppData(ConvertToLine(argc, argv));
823 if (p == NULL) p = "/tmp";
824 i = strlen(p) + strlen("/.xboardXXXXXx.pgn") + 1;
825 gameCopyFilename = (char*) malloc(i);
826 gamePasteFilename = (char*) malloc(i);
827 snprintf(gameCopyFilename,i, "%s/.xboard%05uc.pgn", p, getpid());
828 snprintf(gamePasteFilename,i, "%s/.xboard%05up.pgn", p, getpid());
830 { // [HGM] initstring: kludge to fix bad bug. expand '\n' characters in init string and computer string.
831 static char buf[MSG_SIZ];
832 EscapeExpand(buf, appData.firstInitString);
833 appData.firstInitString = strdup(buf);
834 EscapeExpand(buf, appData.secondInitString);
835 appData.secondInitString = strdup(buf);
836 EscapeExpand(buf, appData.firstComputerString);
837 appData.firstComputerString = strdup(buf);
838 EscapeExpand(buf, appData.secondComputerString);
839 appData.secondComputerString = strdup(buf);
842 if ((chessDir = (char *) getenv("CHESSDIR")) == NULL) {
845 if (chdir(chessDir) != 0) {
846 fprintf(stderr, _("%s: can't cd to CHESSDIR: "), programName);
852 if (appData.debugMode && appData.nameOfDebugFile && strcmp(appData.nameOfDebugFile, "stderr")) {
853 /* [DM] debug info to file [HGM] make the filename a command-line option, and allow it to remain stderr */
854 if ((debugFP = fopen(appData.nameOfDebugFile, "w")) == NULL) {
855 printf(_("Failed to open file '%s'\n"), appData.nameOfDebugFile);
858 setbuf(debugFP, NULL);
862 if (appData.debugMode) {
863 fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
867 /* [HGM,HR] make sure board size is acceptable */
868 if(appData.NrFiles > BOARD_FILES ||
869 appData.NrRanks > BOARD_RANKS )
870 DisplayFatalError(_("Recompile with larger BOARD_RANKS or BOARD_FILES to support this size"), 0, 2);
873 /* This feature does not work; animation needs a rewrite */
874 appData.highlightDragging = FALSE;
878 gameInfo.variant = StringToVariant(appData.variant);
882 * determine size, based on supplied or remembered -size, or screen size
884 if (isdigit(appData.boardSize[0])) {
885 i = sscanf(appData.boardSize, "%d,%d,%d,%d,%d,%d,%d", &squareSize,
886 &lineGap, &clockFontPxlSize, &coordFontPxlSize,
887 &fontPxlSize, &smallLayout, &tinyLayout);
889 fprintf(stderr, _("%s: bad boardSize syntax %s\n"),
890 programName, appData.boardSize);
894 /* Find some defaults; use the nearest known size */
895 SizeDefaults *szd, *nearest;
896 int distance = 99999;
897 nearest = szd = sizeDefaults;
898 while (szd->name != NULL) {
899 if (abs(szd->squareSize - squareSize) < distance) {
901 distance = abs(szd->squareSize - squareSize);
902 if (distance == 0) break;
906 if (i < 2) lineGap = nearest->lineGap;
907 if (i < 3) clockFontPxlSize = nearest->clockFontPxlSize;
908 if (i < 4) coordFontPxlSize = nearest->coordFontPxlSize;
909 if (i < 5) fontPxlSize = nearest->fontPxlSize;
910 if (i < 6) smallLayout = nearest->smallLayout;
911 if (i < 7) tinyLayout = nearest->tinyLayout;
914 SizeDefaults *szd = sizeDefaults;
915 if (*appData.boardSize == NULLCHAR) {
916 GdkScreen *screen = gtk_window_get_screen(GTK_WINDOW(mainwindow));
917 guint screenwidth = gdk_screen_get_width(screen);
918 guint screenheight = gdk_screen_get_height(screen);
919 while (screenwidth < szd->minScreenSize ||
920 screenheight < szd->minScreenSize) {
923 if (szd->name == NULL) szd--;
924 appData.boardSize = strdup(szd->name); // [HGM] settings: remember name for saving settings
926 while (szd->name != NULL &&
927 StrCaseCmp(szd->name, appData.boardSize) != 0) szd++;
928 if (szd->name == NULL) {
929 fprintf(stderr, _("%s: unrecognized boardSize name %s\n"),
930 programName, appData.boardSize);
934 squareSize = szd->squareSize;
935 lineGap = szd->lineGap;
936 clockFontPxlSize = szd->clockFontPxlSize;
937 coordFontPxlSize = szd->coordFontPxlSize;
938 fontPxlSize = szd->fontPxlSize;
939 smallLayout = szd->smallLayout;
940 tinyLayout = szd->tinyLayout;
941 // [HGM] font: use defaults from settings file if available and not overruled
944 defaultLineGap = lineGap;
945 if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
947 /* [HR] height treated separately (hacked) */
948 boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
949 boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
952 * Determine what fonts to use.
955 InitializeFonts(clockFontPxlSize, coordFontPxlSize, fontPxlSize);
959 * Detect if there are not enough colors available and adapt.
962 if (DefaultDepth(xDisplay, xScreen) <= 2) {
963 appData.monoMode = True;
967 forceMono = MakeColors();
970 fprintf(stderr, _("%s: too few colors available; trying monochrome mode\n"),
972 appData.monoMode = True;
975 ParseIcsTextColors();
981 layoutName = "tinyLayout";
982 } else if (smallLayout) {
983 layoutName = "smallLayout";
985 layoutName = "normalLayout";
988 wpMain.width = -1; // prevent popup sizes window
989 optList = BoardPopUp(squareSize, lineGap, (void*)
999 InitDrawingHandle(optList + W_BOARD);
1000 shellWidget = shells[BoardWindow];
1001 currBoard = &optList[W_BOARD];
1002 boardWidget = optList[W_BOARD].handle;
1003 menuBarWidget = optList[W_MENU].handle;
1004 dropMenu = optList[W_DROP].handle;
1005 titleWidget = optList[optList[W_TITLE].type != -1 ? W_TITLE : W_SMALL].handle;
1007 formWidget = XtParent(boardWidget);
1008 XtSetArg(args[0], XtNbackground, &timerBackgroundPixel);
1009 XtSetArg(args[1], XtNforeground, &timerForegroundPixel);
1010 XtGetValues(optList[W_WHITE].handle, args, 2);
1011 if (appData.showButtonBar) { // can't we use timer pixels for this? (Or better yet, just black & white?)
1012 XtSetArg(args[0], XtNbackground, &buttonBackgroundPixel);
1013 XtSetArg(args[1], XtNforeground, &buttonForegroundPixel);
1014 XtGetValues(optList[W_PAUSE].handle, args, 2);
1018 // [HGM] it seems the layout code ends here, but perhaps the color stuff is size independent and would
1019 // not need to go into InitDrawingSizes().
1023 // add accelerators to main shell
1024 gtk_window_add_accel_group(GTK_WINDOW(shellWidget), GtkAccelerators);
1027 * Create an icon. (Use two icons, to indicate whther it is white's or black's turn.)
1029 WhiteIcon = gdk_pixbuf_new_from_file(SVGDIR "/icon_white.svg", NULL);
1030 BlackIcon = gdk_pixbuf_new_from_file(SVGDIR "/icon_black.svg", NULL);
1031 mainwindowIcon = WhiteIcon;
1032 gtk_window_set_icon(GTK_WINDOW(shellWidget), mainwindowIcon);
1036 * Create a cursor for the board widget.
1039 window_attributes.cursor = XCreateFontCursor(xDisplay, XC_hand2);
1040 XChangeWindowAttributes(xDisplay, xBoardWindow,
1041 CWCursor, &window_attributes);
1045 * Inhibit shell resizing.
1048 shellArgs[0].value = (XtArgVal) &w;
1049 shellArgs[1].value = (XtArgVal) &h;
1050 XtGetValues(shellWidget, shellArgs, 2);
1051 shellArgs[4].value = shellArgs[2].value = w;
1052 shellArgs[5].value = shellArgs[3].value = h;
1053 // XtSetValues(shellWidget, &shellArgs[2], 4);
1056 // Note: We cannot do sensible sizing here, because the height of the clock widget is not yet known
1057 // It wil only become known asynchronously, when we first write a string into it.
1058 // This will then change the clock widget height, which triggers resizing the top-level window
1059 // and a configure event. Only then can we know the total height of the top-level window,
1060 // and calculate the height we need. The clockKludge flag suppresses all resizing until
1061 // that moment comes, after which the configure event-handler handles it through a (delayed) DragProg.
1064 gtk_widget_get_allocation(shells[BoardWindow], &a);
1065 w = a.width; h = a.height;
1066 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1067 clockKludge = hc = a.height;
1068 gtk_widget_get_allocation(boardWidget, &a);
1069 marginW = w - boardWidth; // [HGM] needed to set new shellWidget size when we resize board
1070 marginH = h - a.height - hc; // subtract current clock height, so it can be added back dynamically
1076 if(appData.logoSize)
1077 { // locate and read user logo
1079 snprintf(buf, MSG_SIZ, "%s/%s.png", appData.logoDir, UserName());
1080 ASSIGN(userLogo, buf);
1083 if (appData.animate || appData.animateDragging)
1086 g_signal_connect(shells[BoardWindow], "key-press-event", G_CALLBACK(KeyPressProc), NULL);
1087 g_signal_connect(shells[BoardWindow], "configure-event", G_CALLBACK(EventProc), NULL);
1089 /* [AS] Restore layout */
1090 if( wpMoveHistory.visible ) {
1094 if( wpEvalGraph.visible )
1099 if( wpEngineOutput.visible ) {
1100 EngineOutputPopUp();
1105 if (errorExitStatus == -1) {
1106 if (appData.icsActive) {
1107 /* We now wait until we see "login:" from the ICS before
1108 sending the logon script (problems with timestamp otherwise) */
1109 /*ICSInitScript();*/
1110 if (appData.icsInputBox) ICSInputBoxPopUp();
1114 signal(SIGWINCH, TermSizeSigHandler);
1116 signal(SIGINT, IntSigHandler);
1117 signal(SIGTERM, IntSigHandler);
1118 if (*appData.cmailGameName != NULLCHAR) {
1119 signal(SIGUSR1, CmailSigHandler);
1123 gameInfo.boardWidth = 0; // [HGM] pieces: kludge to ensure InitPosition() calls InitDrawingSizes()
1126 // XtSetKeyboardFocus(shellWidget, formWidget);
1128 XSetInputFocus(xDisplay, XtWindow(formWidget), RevertToPointerRoot, CurrentTime);
1131 /* check for GTK events and process them */
1134 gtk_main_iteration();
1137 if (appData.debugMode) fclose(debugFP); // [DM] debug
1142 TermSizeSigHandler (int sig)
1148 IntSigHandler (int sig)
1154 CmailSigHandler (int sig)
1159 signal(SIGUSR1, SIG_IGN); /* suspend handler */
1161 /* Activate call-back function CmailSigHandlerCallBack() */
1162 OutputToProcess(cmailPR, (char *)(&dummy), sizeof(int), &error);
1164 signal(SIGUSR1, CmailSigHandler); /* re-activate handler */
1168 CmailSigHandlerCallBack (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1171 ReloadCmailMsgEvent(TRUE); /* Reload cmail msg */
1173 /**** end signal code ****/
1176 #define Abs(n) ((n)<0 ? -(n) : (n))
1180 InsertPxlSize (char *pattern, int targetPxlSize)
1182 char *base_fnt_lst, strInt[12], *p, *q;
1183 int alternatives, i, len, strIntLen;
1186 * Replace the "*" (if present) in the pixel-size slot of each
1187 * alternative with the targetPxlSize.
1191 while ((p = strchr(p, ',')) != NULL) {
1195 snprintf(strInt, sizeof(strInt), "%d", targetPxlSize);
1196 strIntLen = strlen(strInt);
1197 base_fnt_lst = calloc(1, strlen(pattern) + strIntLen * alternatives + 1);
1201 while (alternatives--) {
1202 char *comma = strchr(p, ',');
1203 for (i=0; i<14; i++) {
1204 char *hyphen = strchr(p, '-');
1206 if (comma && hyphen > comma) break;
1207 len = hyphen + 1 - p;
1208 if (i == 7 && *p == '*' && len == 2) {
1210 memcpy(q, strInt, strIntLen);
1220 len = comma + 1 - p;
1227 return base_fnt_lst;
1232 CreateFontSet (char *base_fnt_lst)
1235 char **missing_list;
1239 fntSet = XCreateFontSet(xDisplay, base_fnt_lst,
1240 &missing_list, &missing_count, &def_string);
1241 if (appData.debugMode) {
1243 XFontStruct **font_struct_list;
1244 char **font_name_list;
1245 fprintf(debugFP, "Requested font set for list %s\n", base_fnt_lst);
1247 fprintf(debugFP, " got list %s, locale %s\n",
1248 XBaseFontNameListOfFontSet(fntSet),
1249 XLocaleOfFontSet(fntSet));
1250 count = XFontsOfFontSet(fntSet, &font_struct_list, &font_name_list);
1251 for (i = 0; i < count; i++) {
1252 fprintf(debugFP, " got charset %s\n", font_name_list[i]);
1255 for (i = 0; i < missing_count; i++) {
1256 fprintf(debugFP, " missing charset %s\n", missing_list[i]);
1259 if (fntSet == NULL) {
1260 fprintf(stderr, _("Unable to create font set for %s.\n"), base_fnt_lst);
1266 #else // not ENABLE_NLS
1268 * Find a font that matches "pattern" that is as close as
1269 * possible to the targetPxlSize. Prefer fonts that are k
1270 * pixels smaller to fonts that are k pixels larger. The
1271 * pattern must be in the X Consortium standard format,
1272 * e.g. "-*-helvetica-bold-r-normal--*-*-*-*-*-*-*-*".
1273 * The return value should be freed with XtFree when no
1277 FindFont (char *pattern, int targetPxlSize)
1279 char **fonts, *p, *best, *scalable, *scalableTail;
1280 int i, j, nfonts, minerr, err, pxlSize;
1283 fonts = XListFonts(xDisplay, pattern, 999999, &nfonts);
1285 fprintf(stderr, _("%s: no fonts match pattern %s\n"),
1286 programName, pattern);
1293 for (i=0; i<nfonts; i++) {
1296 if (*p != '-') continue;
1298 if (*p == NULLCHAR) break;
1299 if (*p++ == '-') j++;
1301 if (j < 7) continue;
1304 scalable = fonts[i];
1307 err = pxlSize - targetPxlSize;
1308 if (Abs(err) < Abs(minerr) ||
1309 (minerr > 0 && err < 0 && -err == minerr)) {
1315 if (scalable && Abs(minerr) > appData.fontSizeTolerance) {
1316 /* If the error is too big and there is a scalable font,
1317 use the scalable font. */
1318 int headlen = scalableTail - scalable;
1319 p = (char *) XtMalloc(strlen(scalable) + 10);
1320 while (isdigit(*scalableTail)) scalableTail++;
1321 sprintf(p, "%.*s%d%s", headlen, scalable, targetPxlSize, scalableTail);
1323 p = (char *) XtMalloc(strlen(best) + 2);
1324 safeStrCpy(p, best, strlen(best)+1 );
1326 if (appData.debugMode) {
1327 fprintf(debugFP, "resolved %s at pixel size %d\n to %s\n",
1328 pattern, targetPxlSize, p);
1330 XFreeFontNames(fonts);
1337 EnableNamedMenuItem (char *menuRef, int state)
1339 MenuItem *item = MenuNameToItem(menuRef);
1341 if(item) gtk_widget_set_sensitive(item->handle, state);
1345 EnableButtonBar (int state)
1348 XtSetSensitive(optList[W_BUTTON].handle, state);
1354 SetMenuEnables (Enables *enab)
1356 while (enab->name != NULL) {
1357 EnableNamedMenuItem(enab->name, enab->value);
1362 gboolean KeyPressProc(window, eventkey, data)
1364 GdkEventKey *eventkey;
1368 MoveTypeInProc(eventkey); // pop up for typed in moves
1371 /* check for other key values */
1372 switch(eventkey->keyval) {
1384 KeyBindingProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1385 { // [HGM] new method of key binding: specify MenuItem(FlipView) in stead of FlipViewProc in translation string
1387 if(*nprms == 0) return;
1388 item = MenuNameToItem(prms[0]);
1389 if(item) ((MenuProc *) item->proc) ();
1403 for (i=0; i<sizeof(dmEnables)/sizeof(DropMenuEnables); i++) {
1404 entry = XtNameToWidget(dropMenu, dmEnables[i].widget);
1405 p = strchr(gameMode == IcsPlayingWhite ? white_holding : black_holding,
1406 dmEnables[i].piece);
1407 XtSetSensitive(entry, p != NULL || !appData.testLegality
1408 /*!!temp:*/ || (gameInfo.variant == VariantCrazyhouse
1409 && !appData.icsActive));
1411 while (p && *p++ == dmEnables[i].piece) count++;
1412 snprintf(label, sizeof(label), "%s %d", dmEnables[i].widget, count);
1414 XtSetArg(args[j], XtNlabel, label); j++;
1415 XtSetValues(entry, args, j);
1421 do_flash_delay (unsigned long msec)
1427 FlashDelay (int flash_delay)
1429 if(flash_delay) do_flash_delay(flash_delay);
1433 Fraction (int x, int start, int stop)
1435 double f = ((double) x - start)/(stop - start);
1436 if(f > 1.) f = 1.; else if(f < 0.) f = 0.;
1440 static WindowPlacement wpNew;
1443 CoDrag (GtkWidget *sh, WindowPlacement *wp)
1445 int touch=0, fudge = 2, f = 2;
1446 GetActualPlacement(sh, wp);
1447 if(abs(wpMain.x + wpMain.width + 2*frameX - f - wp->x) < fudge) touch = 1; else // right touch
1448 if(abs(wp->x + wp->width + 2*frameX + f - wpMain.x) < fudge) touch = 2; else // left touch
1449 if(abs(wpMain.y + wpMain.height + frameX - f + frameY - wp->y) < fudge) touch = 3; else // bottom touch
1450 if(abs(wp->y + wp->height + frameX + frameY + f - wpMain.y) < fudge) touch = 4; // top touch
1451 //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);
1452 if(!touch ) return; // only windows that touch co-move
1453 if(touch < 3 && wpNew.height != wpMain.height) { // left or right and height changed
1454 int heightInc = wpNew.height - wpMain.height;
1455 double fracTop = Fraction(wp->y, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1456 double fracBot = Fraction(wp->y + wp->height + frameX + frameY + 1, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1457 wp->y += fracTop * heightInc;
1458 heightInc = (int) (fracBot * heightInc) - (int) (fracTop * heightInc);
1460 if(heightInc) XtSetArg(args[j], XtNheight, wp->height + heightInc), j++;
1462 wp->height += heightInc;
1463 } else if(touch > 2 && wpNew.width != wpMain.width) { // top or bottom and width changed
1464 int widthInc = wpNew.width - wpMain.width;
1465 double fracLeft = Fraction(wp->x, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1466 double fracRght = Fraction(wp->x + wp->width + 2*frameX + 1, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1467 wp->y += fracLeft * widthInc;
1468 widthInc = (int) (fracRght * widthInc) - (int) (fracLeft * widthInc);
1470 if(widthInc) XtSetArg(args[j], XtNwidth, wp->width + widthInc), j++;
1472 wp->width += widthInc;
1474 wp->x += wpNew.x - wpMain.x;
1475 wp->y += wpNew.y - wpMain.y;
1476 if(touch == 1) wp->x += wpNew.width - wpMain.width; else
1477 if(touch == 3) wp->y += wpNew.height - wpMain.height;
1479 XtSetArg(args[j], XtNx, wp->x); j++;
1480 XtSetArg(args[j], XtNy, wp->y); j++;
1481 XtSetValues(sh, args, j);
1483 gtk_window_move(GTK_WINDOW(sh), wp->x, wp->y);
1484 //printf("moved to (%d,%d)\n", wp->x, wp->y);
1485 gtk_window_resize(GTK_WINDOW(sh), wp->width, wp->height);
1489 ReSize (WindowPlacement *wp)
1492 int sqx, sqy, w, h, hc, lg = lineGap;
1493 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1494 hc = a.height; // clock height can depend on single / double line clock text!
1495 if(clockKludge == a.height) return; // wait for clock to get final size at startup
1496 if(clockKludge) { // clock height OK now; calculate desired initial board height
1498 wp->height = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + marginH + hc;
1500 if(wp->width == wpMain.width && wp->height == wpMain.height) return; // not sized
1501 sqx = (wp->width - lg - marginW) / BOARD_WIDTH - lg;
1502 sqy = (wp->height - lg - marginH - hc) / BOARD_HEIGHT - lg;
1503 if(sqy < sqx) sqx = sqy;
1504 if(appData.overrideLineGap < 0) { // do second iteration with adjusted lineGap
1505 lg = lineGap = sqx < 37 ? 1 : sqx < 59 ? 2 : sqx < 116 ? 3 : 4;
1506 sqx = (wp->width - lg - marginW) / BOARD_WIDTH - lg;
1507 sqy = (wp->height - lg - marginH - hc) / BOARD_HEIGHT - lg;
1508 if(sqy < sqx) sqx = sqy;
1510 if(sqx != squareSize) {
1511 //printf("new sq size %d (%dx%d)\n", sqx, wp->width, wp->height);
1512 squareSize = sqx; // adopt new square size
1513 CreatePNGPieces(); // make newly scaled pieces
1514 InitDrawingSizes(0, 0); // creates grid etc.
1515 } else ResizeBoardWindow(BOARD_WIDTH * (squareSize + lineGap) + lineGap, BOARD_HEIGHT * (squareSize + lineGap) + lineGap, 0);
1516 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
1517 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
1518 if(optList[W_BOARD].max > w) optList[W_BOARD].max = w;
1519 if(optList[W_BOARD].value > h) optList[W_BOARD].value = h;
1522 static guint delayedDragTag = 0;
1531 // GetActualPlacement(shellWidget, &wpNew);
1532 if(wpNew.x == wpMain.x && wpNew.y == wpMain.y && // not moved
1533 wpNew.width == wpMain.width && wpNew.height == wpMain.height) { // not sized
1534 busy = 0; return; // false alarm
1537 if(appData.useStickyWindows) {
1538 if(shellUp[EngOutDlg]) CoDrag(shells[EngOutDlg], &wpEngineOutput);
1539 if(shellUp[HistoryDlg]) CoDrag(shells[HistoryDlg], &wpMoveHistory);
1540 if(shellUp[EvalGraphDlg]) CoDrag(shells[EvalGraphDlg], &wpEvalGraph);
1541 if(shellUp[GameListDlg]) CoDrag(shells[GameListDlg], &wpGameList);
1544 DrawPosition(True, NULL);
1545 if(delayedDragTag) g_source_remove(delayedDragTag);
1546 delayedDragTag = 0; // now drag executed, make sure next DelayedDrag will not cancel timer event (which could now be used by other)
1553 //printf("old timr = %d\n", delayedDragTag);
1554 if(delayedDragTag) g_source_remove(delayedDragTag);
1555 delayedDragTag = g_timeout_add( 200, (GSourceFunc) DragProc, NULL);
1556 //printf("new timr = %d\n", delayedDragTag);
1560 EventProc (GtkWidget *widget, GdkEvent *event, gpointer g)
1562 //printf("event proc (%d,%d) %dx%d\n", event->configure.x, event->configure.y, event->configure.width, event->configure.height);
1564 wpNew.x = event->configure.x;
1565 wpNew.y = event->configure.y;
1566 wpNew.width = event->configure.width;
1567 wpNew.height = event->configure.height;
1568 DelayedDrag(); // as long as events keep coming in faster than 50 msec, they destroy each other
1574 /* Disable all user input other than deleting the window */
1575 static int frozen = 0;
1581 /* Grab by a widget that doesn't accept input */
1582 gtk_grab_add(optList[W_MESSG].handle);
1586 /* Undo a FreezeUI */
1590 if (!frozen) return;
1591 gtk_grab_remove(optList[W_MESSG].handle);
1598 static int oldPausing = FALSE;
1599 static GameMode oldmode = (GameMode) -1;
1601 if (!boardWidget) return;
1603 if (pausing != oldPausing) {
1604 oldPausing = pausing;
1605 MarkMenuItem("Mode.Pause", pausing);
1607 if (appData.showButtonBar) {
1608 /* Always toggle, don't set. Previous code messes up when
1609 invoked while the button is pressed, as releasing it
1610 toggles the state again. */
1612 gdk_color_parse( pausing ? "#808080" : "#F0F0F0", &color );
1613 gtk_widget_modify_bg ( GTK_WIDGET(optList[W_PAUSE].handle), GTK_STATE_NORMAL, &color );
1617 wname = ModeToWidgetName(oldmode);
1618 if (wname != NULL) {
1619 MarkMenuItem(wname, False);
1621 wname = ModeToWidgetName(gameMode);
1622 if (wname != NULL) {
1623 MarkMenuItem(wname, True);
1626 MarkMenuItem("Mode.MachineMatch", matchMode && matchGame < appData.matchGames);
1628 /* Maybe all the enables should be handled here, not just this one */
1629 EnableNamedMenuItem("Mode.Training", gameMode == Training || gameMode == PlayFromGameFile);
1631 DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
1636 * Button/menu procedures
1639 void CopyFileToClipboard(gchar *filename)
1641 gchar *selection_tmp;
1645 FILE* f = fopen(filename, "r");
1648 if (f == NULL) return;
1652 selection_tmp = g_try_malloc(len + 1);
1653 if (selection_tmp == NULL) {
1654 printf("Malloc failed in CopyFileToClipboard\n");
1657 count = fread(selection_tmp, 1, len, f);
1660 g_free(selection_tmp);
1663 selection_tmp[len] = NULLCHAR; // file is now in selection_tmp
1665 // copy selection_tmp to clipboard
1666 GdkDisplay *gdisp = gdk_display_get_default();
1668 g_free(selection_tmp);
1671 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1672 gtk_clipboard_set_text(cb, selection_tmp, -1);
1673 g_free(selection_tmp);
1677 CopySomething (char *src)
1679 GdkDisplay *gdisp = gdk_display_get_default();
1681 if(!src) { CopyFileToClipboard(gameCopyFilename); return; }
1682 if (gdisp == NULL) return;
1683 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1684 gtk_clipboard_set_text(cb, src, -1);
1688 PastePositionProc ()
1690 GdkDisplay *gdisp = gdk_display_get_default();
1694 if (gdisp == NULL) return;
1695 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1696 fenstr = gtk_clipboard_wait_for_text(cb);
1697 if (fenstr==NULL) return; // nothing had been selected to copy
1698 EditPositionPasteFEN(fenstr);
1710 // get game from clipboard
1711 GdkDisplay *gdisp = gdk_display_get_default();
1712 if (gdisp == NULL) return;
1713 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1714 text = gtk_clipboard_wait_for_text(cb);
1715 if (text == NULL) return; // nothing to paste
1718 // write to temp file
1719 if (text == NULL || len == 0) {
1720 return; //nothing to paste
1722 f = fopen(gamePasteFilename, "w");
1724 DisplayError(_("Can't open temp file"), errno);
1727 fwrite(text, 1, len, f);
1731 LoadGameFromFile(gamePasteFilename, 0, gamePasteFilename, TRUE);
1738 QuitWrapper (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1744 void MoveTypeInProc(eventkey)
1745 GdkEventKey *eventkey;
1749 // ingnore if ctrl, alt, or meta is pressed
1750 if (eventkey->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_META_MASK)) {
1754 buf[0]=eventkey->keyval;
1756 if (eventkey->keyval > 32 && eventkey->keyval < 256)
1762 TempBackwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1764 if (!TempBackwardActive) {
1765 TempBackwardActive = True;
1771 TempForwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1773 /* Check to see if triggered by a key release event for a repeating key.
1774 * If so the next queued event will be a key press of the same key at the same time */
1775 if (XEventsQueued(xDisplay, QueuedAfterReading)) {
1777 XPeekEvent(xDisplay, &next);
1778 if (next.type == KeyPress && next.xkey.time == event->xkey.time &&
1779 next.xkey.keycode == event->xkey.keycode)
1783 TempBackwardActive = False;
1789 { // called from menu
1791 system("%s ./man.command", appData.sysOpen);
1793 system("xterm -e man xboard &");
1798 SetWindowTitle (char *text, char *title, char *icon)
1803 if (appData.titleInWindow) {
1805 XtSetArg(args[i], XtNlabel, text); i++;
1806 XtSetValues(titleWidget, args, i);
1809 XtSetArg(args[i], XtNiconName, (XtArgVal) icon); i++;
1810 XtSetArg(args[i], XtNtitle, (XtArgVal) title); i++;
1811 XtSetValues(shellWidget, args, i);
1812 XSync(xDisplay, False);
1814 if (appData.titleInWindow) {
1815 SetWidgetLabel(titleWidget, text);
1817 gtk_window_set_title (GTK_WINDOW(shells[BoardWindow]), title);
1822 DisplayIcsInteractionTitle (String message)
1825 if (oldICSInteractionTitle == NULL) {
1826 /* Magic to find the old window title, adapted from vim */
1827 char *wina = getenv("WINDOWID");
1829 Window win = (Window) atoi(wina);
1830 Window root, parent, *children;
1831 unsigned int nchildren;
1832 int (*oldHandler)() = XSetErrorHandler(NullXErrorCheck);
1834 if (XFetchName(xDisplay, win, &oldICSInteractionTitle)) break;
1835 if (!XQueryTree(xDisplay, win, &root, &parent,
1836 &children, &nchildren)) break;
1837 if (children) XFree((void *)children);
1838 if (parent == root || parent == 0) break;
1841 XSetErrorHandler(oldHandler);
1843 if (oldICSInteractionTitle == NULL) {
1844 oldICSInteractionTitle = "xterm";
1847 printf("\033]0;%s\007", message);
1854 DisplayTimerLabel (Option *opt, char *color, long timer, int highlight)
1856 GtkWidget *w = (GtkWidget *) opt->handle;
1863 strcpy(bgcolor, "black");
1864 strcpy(fgcolor, "white");
1866 strcpy(bgcolor, "white");
1867 strcpy(fgcolor, "black");
1870 appData.lowTimeWarning &&
1871 (timer / 1000) < appData.icsAlarmTime) {
1872 strcpy(fgcolor, appData.lowTimeWarningColor);
1875 gdk_color_parse( bgcolor, &col );
1876 gtk_widget_modify_bg(gtk_widget_get_parent(opt->handle), GTK_STATE_NORMAL, &col);
1878 if (appData.clockMode) {
1879 markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s:%s%s</span>",
1880 bgcolor, fgcolor, color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
1882 markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s </span>",
1883 bgcolor, fgcolor, color);
1885 gtk_label_set_markup(GTK_LABEL(w), markup);
1889 static GdkPixbuf **clockIcons[] = { &WhiteIcon, &BlackIcon };
1892 SetClockIcon (int color)
1894 GdkPixbuf *pm = *clockIcons[color];
1895 if (mainwindowIcon != pm) {
1896 mainwindowIcon = pm;
1897 gtk_window_set_icon(GTK_WINDOW(shellWidget), mainwindowIcon);
1901 #define INPUT_SOURCE_BUF_SIZE 8192
1910 char buf[INPUT_SOURCE_BUF_SIZE];
1915 DoInputCallback(io, cond, data)
1920 /* read input from one of the input source (for example a chess program, ICS, etc).
1921 * and call a function that will handle the input
1928 /* All information (callback function, file descriptor, etc) is
1929 * saved in an InputSource structure
1931 InputSource *is = (InputSource *) data;
1933 if (is->lineByLine) {
1934 count = read(is->fd, is->unused,
1935 INPUT_SOURCE_BUF_SIZE - (is->unused - is->buf));
1937 (is->func)(is, is->closure, is->buf, count, count ? errno : 0);
1940 is->unused += count;
1942 /* break input into lines and call the callback function on each
1945 while (p < is->unused) {
1946 q = memchr(p, '\n', is->unused - p);
1947 if (q == NULL) break;
1949 (is->func)(is, is->closure, p, q - p, 0);
1952 /* remember not yet used part of the buffer */
1954 while (p < is->unused) {
1959 /* read maximum length of input buffer and send the whole buffer
1960 * to the callback function
1962 count = read(is->fd, is->buf, INPUT_SOURCE_BUF_SIZE);
1967 (is->func)(is, is->closure, is->buf, count, error);
1969 return True; // Must return true or the watch will be removed
1972 InputSourceRef AddInputSource(pr, lineByLine, func, closure)
1979 GIOChannel *channel;
1980 ChildProc *cp = (ChildProc *) pr;
1982 is = (InputSource *) calloc(1, sizeof(InputSource));
1983 is->lineByLine = lineByLine;
1987 is->fd = fileno(stdin);
1989 is->kind = cp->kind;
1990 is->fd = cp->fdFrom;
1993 is->unused = is->buf;
1997 /* GTK-TODO: will this work on windows?*/
1999 channel = g_io_channel_unix_new(is->fd);
2000 g_io_channel_set_close_on_unref (channel, TRUE);
2001 is->sid = g_io_add_watch(channel, G_IO_IN,(GIOFunc) DoInputCallback, is);
2003 is->closure = closure;
2004 return (InputSourceRef) is;
2009 RemoveInputSource(isr)
2012 InputSource *is = (InputSource *) isr;
2014 if (is->sid == 0) return;
2015 g_source_remove(is->sid);
2022 static Boolean frameWaiting;
2025 FrameAlarm (int sig)
2027 frameWaiting = False;
2028 /* In case System-V style signals. Needed?? */
2029 signal(SIGALRM, FrameAlarm);
2033 FrameDelay (int time)
2035 struct itimerval delay;
2038 frameWaiting = True;
2039 signal(SIGALRM, FrameAlarm);
2040 delay.it_interval.tv_sec =
2041 delay.it_value.tv_sec = time / 1000;
2042 delay.it_interval.tv_usec =
2043 delay.it_value.tv_usec = (time % 1000) * 1000;
2044 setitimer(ITIMER_REAL, &delay, NULL);
2045 while (frameWaiting) pause();
2046 delay.it_interval.tv_sec = delay.it_value.tv_sec = 0;
2047 delay.it_interval.tv_usec = delay.it_value.tv_usec = 0;
2048 setitimer(ITIMER_REAL, &delay, NULL);
2055 FrameDelay (int time)
2058 XSync(xDisplay, False);
2060 // gtk_main_iteration_do(False);
2063 usleep(time * 1000);
2069 LoadLogo (ChessProgramState *cps, int n, Boolean ics)
2071 char buf[MSG_SIZ], *logoName = buf;
2072 if(appData.logo[n][0]) {
2073 logoName = appData.logo[n];
2074 } else if(appData.autoLogo) {
2075 if(ics) { // [HGM] logo: in ICS mode second can be used for ICS
2076 sprintf(buf, "%s/%s.png", appData.logoDir, appData.icsHost);
2077 } else if(appData.directory[n] && appData.directory[n][0]) {
2078 sprintf(buf, "%s/%s.png", appData.logoDir, cps->tidy);
2082 { ASSIGN(cps->programLogo, logoName); }
2086 UpdateLogos (int displ)
2088 if(optList[W_WHITE-1].handle == NULL) return;
2089 LoadLogo(&first, 0, 0);
2090 LoadLogo(&second, 1, appData.icsActive);
2091 if(displ) DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
2095 void FileNamePopUpWrapper(label, def, filter, proc, pathFlag, openMode, name, fp)
2106 GtkFileFilter *gtkfilter;
2107 GtkFileFilter *gtkfilter_all;
2109 char fileext[10] = "";
2110 char *result = NULL;
2113 /* make a copy of the filter string, so that strtok can work with it*/
2114 cp = strdup(filter);
2116 /* add filters for file extensions */
2117 gtkfilter = gtk_file_filter_new();
2118 gtkfilter_all = gtk_file_filter_new();
2120 /* one filter to show everything */
2121 gtk_file_filter_add_pattern(gtkfilter_all, "*.*");
2122 gtk_file_filter_set_name (gtkfilter_all, "All Files");
2124 /* add filter if present */
2125 result = strtok(cp, space);
2126 while( result != NULL ) {
2127 snprintf(fileext,10,"*%s",result);
2128 result = strtok( NULL, space );
2129 gtk_file_filter_add_pattern(gtkfilter, fileext);
2132 /* second filter to only show what's useful */
2133 gtk_file_filter_set_name (gtkfilter,filter);
2135 if (openMode[0] == 'r')
2137 dialog = gtk_file_chooser_dialog_new (label,
2139 GTK_FILE_CHOOSER_ACTION_OPEN,
2140 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2141 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
2146 dialog = gtk_file_chooser_dialog_new (label,
2148 GTK_FILE_CHOOSER_ACTION_SAVE,
2149 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2150 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
2152 /* add filename suggestions */
2153 if (strlen(def) > 0 )
2154 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), def);
2156 //gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER (dialog),TRUE);
2160 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter_all);
2161 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2162 /* activate filter */
2163 gtk_file_chooser_set_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2165 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
2170 filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
2173 f = fopen(filename, openMode);
2176 DisplayError(_("Failed to open file"), errno);
2180 /* TODO add indec */
2182 ASSIGN(*name, filename);
2183 ScheduleDelayedEvent(DelayedLoad, 50);
2188 gtk_widget_destroy (dialog);