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).
176 // redefine some defaults
180 # undef SETTINGS_FILE
181 # define ICS_LOGON "Library/Preferences/XboardICS.conf"
182 # define DATADIR dataDir
183 # define LOCALEDIR localeDir
184 # define SETTINGS_FILE masterSettings
185 # define SYNC_MENUBAR gtkosx_application_sync_menubar(theApp)
186 char dataDir[MSG_SIZ]; // for expanding ~~
187 char localeDir[MSG_SIZ];
188 char masterSettings[MSG_SIZ];
192 # define SYNC_MENUBAR
199 #define usleep(t) _sleep2(((t)+500)/1000)
203 # define _(s) gettext (s)
204 # define N_(s) gettext_noop (s)
210 int main P((int argc, char **argv));
211 RETSIGTYPE CmailSigHandler P((int sig));
212 RETSIGTYPE IntSigHandler P((int sig));
213 RETSIGTYPE TermSizeSigHandler P((int sig));
214 char *InsertPxlSize P((char *pattern, int targetPxlSize));
216 XFontSet CreateFontSet P((char *base_fnt_lst));
218 char *FindFont P((char *pattern, int targetPxlSize));
220 void DelayedDrag P((void));
221 void ICSInputBoxPopUp P((void));
222 void MoveTypeInProc P((GdkEventKey *eventkey));
223 gboolean KeyPressProc P((GtkWindow *window, GdkEventKey *eventkey, gpointer data));
224 Boolean TempBackwardActive = False;
225 void DisplayMove P((int moveNumber));
226 void update_ics_width P(());
227 int CopyMemoProc P(());
228 static gboolean EventProc P((GtkWidget *widget, GdkEvent *event, gpointer g));
232 XFontSet fontSet, clockFontSet;
235 XFontStruct *clockFontStruct;
237 Font coordFontID, countFontID;
238 XFontStruct *coordFontStruct, *countFontStruct;
240 void *shellWidget, *formWidget, *boardWidget, *titleWidget, *dropMenu, *menuBarWidget;
241 GtkWidget *mainwindow;
243 Option *optList; // contains all widgets of main window
246 char installDir[] = "."; // [HGM] UCI: needed for UCI; probably needs run-time initializtion
249 static GdkPixbuf *mainwindowIcon=NULL;
250 static GdkPixbuf *WhiteIcon=NULL;
251 static GdkPixbuf *BlackIcon=NULL;
253 /* key board accelerators */
254 GtkAccelGroup *GtkAccelerators;
256 typedef unsigned int BoardSize;
258 Boolean chessProgram;
260 int minX, minY; // [HGM] placement: volatile limits on upper-left corner
261 int smallLayout = 0, tinyLayout = 0,
262 marginW, marginH, // [HGM] for run-time resizing
263 fromX = -1, fromY = -1, toX, toY, commentUp = False,
264 errorExitStatus = -1, defaultLineGap;
266 Dimension textHeight;
268 char *chessDir, *programName, *programVersion;
269 Boolean alwaysOnTop = False;
270 char *icsTextMenuString;
272 char *firstChessProgramNames;
273 char *secondChessProgramNames;
275 WindowPlacement wpMain;
276 WindowPlacement wpConsole;
277 WindowPlacement wpComment;
278 WindowPlacement wpMoveHistory;
279 WindowPlacement wpEvalGraph;
280 WindowPlacement wpEngineOutput;
281 WindowPlacement wpGameList;
282 WindowPlacement wpTags;
283 WindowPlacement wpDualBoard;
285 /* This magic number is the number of intermediate frames used
286 in each half of the animation. For short moves it's reduced
287 by 1. The total number of frames will be factor * 2 + 1. */
290 SizeDefaults sizeDefaults[] = SIZE_DEFAULTS;
297 DropMenuEnables dmEnables[] = {
306 XtResource clientResources[] = {
307 { "flashCount", "flashCount", XtRInt, sizeof(int),
308 XtOffset(AppDataPtr, flashCount), XtRImmediate,
309 (XtPointer) FLASH_COUNT },
313 /* keyboard shortcuts not yet transistioned int menuitem @ menu.c */
314 char globalTranslations[] =
315 ":Ctrl<Key>Down: LoadSelectedProc(3) \n \
316 :Ctrl<Key>Up: LoadSelectedProc(-3) \n \
317 :<KeyDown>Return: TempBackwardProc() \n \
318 :<KeyUp>Return: TempForwardProc() \n";
320 char ICSInputTranslations[] =
321 "<Key>Up: UpKeyProc() \n "
322 "<Key>Down: DownKeyProc() \n "
323 "<Key>Return: EnterKeyProc() \n";
325 // [HGM] vari: another hideous kludge: call extend-end first so we can be sure select-start works,
326 // as the widget is destroyed before the up-click can call extend-end
327 char commentTranslations[] = "<Btn3Down>: extend-end() select-start() CommentClick() \n";
330 String xboardResources[] = {
331 "*Error*translations: #override\\n <Key>Return: ErrorPopDown()",
339 gtk_window_present(GTK_WINDOW(shells[BoardWindow]));
342 //---------------------------------------------------------------------------------------------------------
343 // some symbol definitions to provide the proper (= XBoard) context for the code in args.h
346 #define CW_USEDEFAULT (1<<31)
347 #define ICS_TEXT_MENU_SIZE 90
348 #define DEBUG_FILE "xboard.debug"
349 #define SetCurrentDirectory chdir
350 #define GetCurrentDirectory(SIZE, NAME) getcwd(NAME, SIZE)
354 // The option definition and parsing code common to XBoard and WinBoard is collected in this file
357 // front-end part of option handling
359 // [HGM] This platform-dependent table provides the location for storing the color info
360 extern char *crWhite, * crBlack;
364 &appData.whitePieceColor,
365 &appData.blackPieceColor,
366 &appData.lightSquareColor,
367 &appData.darkSquareColor,
368 &appData.highlightSquareColor,
369 &appData.premoveHighlightColor,
370 &appData.lowTimeWarningColor,
381 // [HGM] font: keep a font for each square size, even non-stndard ones
384 Boolean fontIsSet[NUM_FONTS], fontValid[NUM_FONTS][MAX_SIZE];
385 char *fontTable[NUM_FONTS][MAX_SIZE];
388 ParseFont (char *name, int number)
389 { // in XBoard, only 2 of the fonts are currently implemented, and we just copy their name
391 if(sscanf(name, "size%d:", &size)) {
392 // [HGM] font: font is meant for specific boardSize (likely from settings file);
393 // defer processing it until we know if it matches our board size
394 if(!strstr(name, "-*-") && // ignore X-fonts
395 size >= 0 && size<MAX_SIZE) { // for now, fixed limit
396 fontTable[number][size] = strdup(strchr(name, ':')+1);
397 fontValid[number][size] = True;
402 case 0: // CLOCK_FONT
403 appData.clockFont = strdup(name);
405 case 1: // MESSAGE_FONT
406 appData.font = strdup(name);
408 case 2: // COORD_FONT
409 appData.coordFont = strdup(name);
412 appData.icsFont = strdup(name);
415 appData.tagsFont = strdup(name);
418 appData.commentFont = strdup(name);
420 case MOVEHISTORY_FONT:
421 appData.historyFont = strdup(name);
424 appData.gameListFont = strdup(name);
429 fontIsSet[number] = True; // [HGM] font: indicate a font was specified (not from settings file)
434 { // only 2 fonts currently
435 appData.clockFont = strdup(CLOCK_FONT_NAME);
436 appData.coordFont = strdup(COORD_FONT_NAME);
437 appData.font = strdup(DEFAULT_FONT_NAME);
438 appData.icsFont = strdup(CONSOLE_FONT_NAME);
439 appData.tagsFont = strdup(TAGS_FONT_NAME);
440 appData.commentFont = strdup(COMMENT_FONT_NAME);
441 appData.historyFont = strdup(HISTORY_FONT_NAME);
442 appData.gameListFont = strdup(GAMELIST_FONT_NAME);
447 { // no-op, until we identify the code for this already in XBoard and move it here
451 ParseColor (int n, char *name)
452 { // in XBoard, just copy the color-name string
453 if(colorVariable[n] && *name == '#') *(char**)colorVariable[n] = strdup(name);
459 return *(char**)colorVariable[n];
463 ParseTextAttribs (ColorClass cc, char *s)
465 (&appData.colorShout)[cc] = strdup(s);
469 ParseBoardSize (void *addr, char *name)
471 appData.boardSize = strdup(name);
476 { // In XBoard the sound-playing program takes care of obtaining the actual sound
480 SetCommPortDefaults ()
481 { // for now, this is a no-op, as the corresponding option does not exist in XBoard
484 // [HGM] args: these three cases taken out to stay in front-end
486 SaveFontArg (FILE *f, ArgDescriptor *ad)
489 int i, n = (int)(intptr_t)ad->argLoc;
491 case 0: // CLOCK_FONT
492 name = appData.clockFont;
494 case 1: // MESSAGE_FONT
497 case 2: // COORD_FONT
498 name = appData.coordFont;
501 name = appData.icsFont;
504 name = appData.tagsFont;
507 name = appData.commentFont;
509 case MOVEHISTORY_FONT:
510 name = appData.historyFont;
513 name = appData.gameListFont;
518 for(i=0; i<NUM_SIZES; i++) // [HGM] font: current font becomes standard for current size
519 if(sizeDefaults[i].squareSize == squareSize) { // only for standard sizes!
520 fontTable[n][squareSize] = strdup(name);
521 fontValid[n][squareSize] = True;
524 for(i=0; i<MAX_SIZE; i++) if(fontValid[n][i]) // [HGM] font: store all standard fonts
525 fprintf(f, OPTCHAR "%s" SEPCHAR "\"size%d:%s\"\n", ad->argName, i, fontTable[n][i]);
530 { // nothing to do, as the sounds are at all times represented by their text-string names already
534 SaveAttribsArg (FILE *f, ArgDescriptor *ad)
535 { // here the "argLoc" defines a table index. It could have contained the 'ta' pointer itself, though
536 fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, (&appData.colorShout)[(int)(intptr_t)ad->argLoc]);
540 SaveColor (FILE *f, ArgDescriptor *ad)
541 { // in WinBoard the color is an int and has to be converted to text. In X it would be a string already?
542 if(colorVariable[(int)(intptr_t)ad->argLoc])
543 fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, *(char**)colorVariable[(int)(intptr_t)ad->argLoc]);
547 SaveBoardSize (FILE *f, char *name, void *addr)
548 { // wrapper to shield back-end from BoardSize & sizeInfo
549 fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", name, appData.boardSize);
553 ParseCommPortSettings (char *s)
554 { // no such option in XBoard (yet)
560 GetActualPlacement (GtkWidget *shell, WindowPlacement *wp)
564 gtk_widget_get_allocation(shell, &a);
565 gtk_window_get_position(GTK_WINDOW(shell), &a.x, &a.y);
569 wp->height = a.height;
570 //printf("placement: (%d,%d) %dx%d\n", a.x, a.y, a.width, a.height);
571 frameX = 3; frameY = 3; // remember to decide if windows touch
575 GetPlacement (DialogClass dlg, WindowPlacement *wp)
576 { // wrapper to shield back-end from widget type
577 if(shellUp[dlg]) GetActualPlacement(shells[dlg], wp);
582 { // wrapper to shield use of window handles from back-end (make addressible by number?)
583 // In XBoard this will have to wait until awareness of window parameters is implemented
584 GetActualPlacement(shellWidget, &wpMain);
585 if(shellUp[EngOutDlg]) GetActualPlacement(shells[EngOutDlg], &wpEngineOutput);
586 if(shellUp[HistoryDlg]) GetActualPlacement(shells[HistoryDlg], &wpMoveHistory);
587 if(shellUp[EvalGraphDlg]) GetActualPlacement(shells[EvalGraphDlg], &wpEvalGraph);
588 if(shellUp[GameListDlg]) GetActualPlacement(shells[GameListDlg], &wpGameList);
589 if(shellUp[CommentDlg]) GetActualPlacement(shells[CommentDlg], &wpComment);
590 if(shellUp[TagsDlg]) GetActualPlacement(shells[TagsDlg], &wpTags);
591 GetPlacement(ChatDlg, &wpConsole); if(appData.icsActive) wpConsole.visible = shellUp[ChatDlg];
595 PrintCommPortSettings (FILE *f, char *name)
596 { // This option does not exist in XBoard
600 EnsureOnScreen (int *x, int *y, int minX, int minY)
607 { // [HGM] args: allows testing if main window is realized from back-end
608 return DialogExists(BoardWindow);
612 PopUpStartupDialog ()
613 { // start menu not implemented in XBoard
617 ConvertToLine (int argc, char **argv)
619 static char line[128*1024], buf[1024];
623 for(i=1; i<argc; i++)
625 if( (strchr(argv[i], ' ') || strchr(argv[i], '\n') ||strchr(argv[i], '\t') || argv[i][0] == NULLCHAR)
626 && argv[i][0] != '{' )
627 snprintf(buf, sizeof(buf)/sizeof(buf[0]), "{%s} ", argv[i]);
629 snprintf(buf, sizeof(buf)/sizeof(buf[0]), "%s ", argv[i]);
630 strncat(line, buf, 128*1024 - strlen(line) - 1 );
633 line[strlen(line)-1] = NULLCHAR;
637 //--------------------------------------------------------------------------------------------
642 ResizeBoardWindow (int w, int h, int inhibit)
646 // if(clockKludge) return; // ignore as long as clock does not have final height
647 gtk_widget_get_allocation(optList[W_BOARD].handle, &a);
649 gtk_widget_get_allocation(shellWidget, &a);
650 marginW = a.width - bw;
651 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
652 w += marginW + 1; // [HGM] not sure why the +1 is (sometimes) needed...
653 h += marginH + a.height + 1;
654 gtk_window_resize(GTK_WINDOW(shellWidget), w, h);
659 { // dummy, as the GTK code does not make colors in advance
664 InitializeFonts (int clockFontPxlSize, int coordFontPxlSize, int fontPxlSize)
665 { // determine what fonts to use, and create them
667 if(!fontIsSet[CLOCK_FONT] && fontValid[CLOCK_FONT][squareSize])
668 appData.clockFont = fontTable[CLOCK_FONT][squareSize];
669 if(!fontIsSet[MESSAGE_FONT] && fontValid[MESSAGE_FONT][squareSize])
670 appData.font = fontTable[MESSAGE_FONT][squareSize];
671 if(!fontIsSet[COORD_FONT] && fontValid[COORD_FONT][squareSize])
672 appData.coordFont = fontTable[COORD_FONT][squareSize];
673 if(!fontIsSet[CONSOLE_FONT] && fontValid[CONSOLE_FONT][squareSize])
674 appData.icsFont = fontTable[CONSOLE_FONT][squareSize];
675 if(!fontIsSet[EDITTAGS_FONT] && fontValid[EDITTAGS_FONT][squareSize])
676 appData.tagsFont = fontTable[EDITTAGS_FONT][squareSize];
677 if(!fontIsSet[COMMENT_FONT] && fontValid[COMMENT_FONT][squareSize])
678 appData.commentFont = fontTable[COMMENT_FONT][squareSize];
679 if(!fontIsSet[MOVEHISTORY_FONT] && fontValid[MOVEHISTORY_FONT][squareSize])
680 appData.historyFont = fontTable[MOVEHISTORY_FONT][squareSize];
681 if(!fontIsSet[GAMELIST_FONT] && fontValid[GAMELIST_FONT][squareSize])
682 appData.gameListFont = fontTable[GAMELIST_FONT][squareSize];
684 appData.font = InsertPxlSize(appData.font, coordFontPxlSize);
685 appData.clockFont = InsertPxlSize(appData.clockFont, clockFontPxlSize);
686 appData.coordFont = InsertPxlSize(appData.coordFont, coordFontPxlSize);
687 appData.icsFont = InsertPxlSize(appData.icsFont, coordFontPxlSize);
688 appData.tagsFont = InsertPxlSize(appData.tagsFont, coordFontPxlSize);
689 appData.commentFont = InsertPxlSize(appData.commentFont, coordFontPxlSize);
690 appData.historyFont = InsertPxlSize(appData.historyFont, coordFontPxlSize);
691 appData.gameListFont = InsertPxlSize(appData.gameListFont, coordFontPxlSize);
697 if(!fontIsSet[CLOCK_FONT] && fontValid[CLOCK_FONT][squareSize])
698 appData.clockFont = fontTable[CLOCK_FONT][squareSize];
699 if(!fontIsSet[MESSAGE_FONT] && fontValid[MESSAGE_FONT][squareSize])
700 appData.font = fontTable[MESSAGE_FONT][squareSize];
701 if(!fontIsSet[COORD_FONT] && fontValid[COORD_FONT][squareSize])
702 appData.coordFont = fontTable[COORD_FONT][squareSize];
705 appData.font = InsertPxlSize(appData.font, fontPxlSize);
706 appData.clockFont = InsertPxlSize(appData.clockFont, clockFontPxlSize);
707 appData.coordFont = InsertPxlSize(appData.coordFont, coordFontPxlSize);
708 fontSet = CreateFontSet(appData.font);
709 clockFontSet = CreateFontSet(appData.clockFont);
711 /* For the coordFont, use the 0th font of the fontset. */
712 XFontSet coordFontSet = CreateFontSet(appData.coordFont);
713 XFontStruct **font_struct_list;
714 XFontSetExtents *fontSize;
715 char **font_name_list;
716 XFontsOfFontSet(coordFontSet, &font_struct_list, &font_name_list);
717 coordFontID = XLoadFont(xDisplay, font_name_list[0]);
718 coordFontStruct = XQueryFont(xDisplay, coordFontID);
719 fontSize = XExtentsOfFontSet(fontSet); // [HGM] figure out how much vertical space font takes
720 textHeight = fontSize->max_logical_extent.height + 5; // add borderWidth
723 appData.font = FindFont(appData.font, fontPxlSize);
724 appData.clockFont = FindFont(appData.clockFont, clockFontPxlSize);
725 appData.coordFont = FindFont(appData.coordFont, coordFontPxlSize);
726 clockFontID = XLoadFont(xDisplay, appData.clockFont);
727 clockFontStruct = XQueryFont(xDisplay, clockFontID);
728 coordFontID = XLoadFont(xDisplay, appData.coordFont);
729 coordFontStruct = XQueryFont(xDisplay, coordFontID);
730 // textHeight in !NLS mode!
732 countFontID = coordFontID; // [HGM] holdings
733 countFontStruct = coordFontStruct;
735 xdb = XtDatabase(xDisplay);
737 XrmPutLineResource(&xdb, "*international: True");
738 vTo.size = sizeof(XFontSet);
739 vTo.addr = (XtPointer) &fontSet;
740 XrmPutResource(&xdb, "*fontSet", XtRFontSet, &vTo);
742 XrmPutStringResource(&xdb, "*font", appData.font);
753 case ArgInt: p = " N"; break;
754 case ArgString: p = " STR"; break;
755 case ArgBoolean: p = " TF"; break;
756 case ArgSettingsFilename:
757 case ArgBackupSettingsFile:
758 case ArgFilename: p = " FILE"; break;
759 case ArgX: p = " Nx"; break;
760 case ArgY: p = " Ny"; break;
761 case ArgAttribs: p = " TEXTCOL"; break;
762 case ArgColor: p = " COL"; break;
763 case ArgFont: p = " FONT"; break;
764 case ArgBoardSize: p = " SIZE"; break;
765 case ArgFloat: p = " FLOAT"; break;
770 case ArgCommSettings:
782 ArgDescriptor *q, *p = argDescriptors+5;
783 printf("\nXBoard accepts the following options:\n"
784 "(N = integer, TF = true or false, STR = text string, FILE = filename,\n"
785 " Nx, Ny = relative coordinates, COL = color, FONT = X-font spec,\n"
786 " SIZE = board-size spec(s)\n"
787 " Within parentheses are short forms, or options to set to true or false.\n"
788 " Persistent options (saved in the settings file) are marked with *)\n\n");
790 if(p->argType == ArgCommSettings) { p++; continue; } // XBoard has no comm port
791 snprintf(buf+len, MSG_SIZ, "-%s%s", p->argName, PrintArg(p->argType));
792 if(p->save) strcat(buf+len, "*");
793 for(q=p+1; q->argLoc == p->argLoc; q++) {
794 if(q->argName[0] == '-') continue;
795 strcat(buf+len, q == p+1 ? " (" : " ");
796 sprintf(buf+strlen(buf), "-%s%s", q->argName, PrintArg(q->argType));
798 if(q != p+1) strcat(buf+len, ")");
800 if(len > 39) len = 0, printf("%s\n", buf); else while(len < 39) buf[len++] = ' ';
803 if(len) buf[len] = NULLCHAR, printf("%s\n", buf);
807 SlaveResize (Option *opt)
809 static int slaveW, slaveH, w, h;
812 gtk_widget_get_allocation(shells[DummyDlg], &a);
813 w = a.width; h = a.height;
814 gtk_widget_get_allocation(opt->handle, &a);
815 slaveW = w - opt->max; // [HGM] needed to set new shellWidget size when we resize board
816 slaveH = h - a.height + 13;
818 gtk_window_resize(GTK_WINDOW(shells[DummyDlg]), slaveW + opt->max, slaveH + opt->value);
822 LoadIconFile (gchar *svgFilename)
826 snprintf(buf, MSG_SIZ, "%s/%s" IMG, svgDir, svgFilename);
827 return gdk_pixbuf_new_from_file(buf, NULL);
831 static char clickedFile[MSG_SIZ];
835 StartNewXBoard(GtkosxApplication *app, gchar *path, gpointer user_data)
836 { // handler of OSX OpenFile signal, which sends us the filename of clicked file or first argument
837 if(suppress) { // we just started XBoard without arguments
838 strncpy(clickedFile, path, MSG_SIZ); // remember file name, but otherwise ignore
839 } else { // we are running something presumably useful
841 snprintf(buf, MSG_SIZ, "open -n -a \"xboard\" --args \"%s\"", path);
842 system(buf); // start new instance on this file
847 GtkosxApplication *theApp;
851 main (int argc, char **argv)
853 int i, clockFontPxlSize, coordFontPxlSize, fontPxlSize;
854 int boardWidth, w, h; //, boardHeight;
856 int forceMono = False;
858 srandom(time(0)); // [HGM] book: make random truly random
860 setbuf(stdout, NULL);
861 setbuf(stderr, NULL);
864 if(argc > 1 && (!strcmp(argv[1], "-v" ) || !strcmp(argv[1], "--version" ))) {
865 printf("%s version %s\n\n configure options: %s\n", PACKAGE_NAME, PACKAGE_VERSION, CONFIGURE_OPTIONS);
869 if(argc > 1 && !strcmp(argv[1], "--help" )) {
875 gtk_init (&argc, &argv);
877 { // prepare to catch OX OpenFile signal, which will tell us the clicked file
878 char *path = gtkosx_application_get_bundle_path();
880 char *res_path = gtkosx_application_get_resource_path();
881 snprintf(localeDir, MSG_SIZ, "%s/share/locale", res_path); // redefine locale dir for OSX bundle
883 theApp = g_object_new(GTKOSX_TYPE_APPLICATION, NULL);
884 strncpy(dataDir, path, MSG_SIZ);
885 snprintf(masterSettings, MSG_SIZ, "%s/Contents/Resources/etc/xboard.conf", path);
886 snprintf(svgDir, MSG_SIZ, "%s/Contents/Resources/share/xboard/themes/default", path);
887 suppress = (argc == 1 || argc > 1 && argv[1][00] != '-'); // OSX sends signal even if name was already argv[1]!
888 g_signal_connect(theApp, "NSApplicationOpenFile", G_CALLBACK(StartNewXBoard), NULL);
889 // we must call application ready before we can get the signal,
890 // and supply a (dummy) menu bar before that, to avoid problems with dual apples in it
891 gtkosx_application_set_menu_bar(theApp, GTK_MENU_SHELL(gtk_menu_bar_new()));
892 gtkosx_application_ready(theApp);
893 if(argc == 1) { // called without args: OSX open-file signal might follow
894 static char *fakeArgv[3] = {NULL, clickedFile, NULL};
895 usleep(10000); // wait 10 msec (and hope this is long enough).
896 while(gtk_events_pending())
897 gtk_main_iteration(); // process all events that came in upto now
898 suppress = 0; // future open-file signals should start new instance
899 if(clickedFile[0]) { // we were sent an open-file signal with filename!
900 fakeArgv[0] = argv[0];
901 argc = 2; argv = fakeArgv; // fake that we were called as "xboard filename"
907 if(argc > 1 && !strcmp(argv[1], "--show-config")) { // [HGM] install: called to print config info
908 typedef struct {char *name, *value; } Config;
909 static Config configList[] = {
910 { "Datadir", DATADIR },
911 { "Sysconfdir", SYSCONFDIR },
916 for(i=0; configList[i].name; i++) {
917 if(argc > 2 && strcmp(argv[2], configList[i].name)) continue;
918 if(argc > 2) printf("%s", configList[i].value);
919 else printf("%-12s: %s\n", configList[i].name, configList[i].value);
924 /* set up keyboard accelerators group */
925 GtkAccelerators = gtk_accel_group_new();
927 programName = strrchr(argv[0], '/');
928 if (programName == NULL)
929 programName = argv[0];
934 // if (appData.debugMode) {
935 // fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
938 bindtextdomain(PACKAGE, LOCALEDIR);
939 bind_textdomain_codeset(PACKAGE, "UTF-8"); // needed when creating markup for the clocks
943 appData.boardSize = "";
944 InitAppData(ConvertToLine(argc, argv));
946 if (p == NULL) p = "/tmp";
947 i = strlen(p) + strlen("/.xboardXXXXXx.pgn") + 1;
948 gameCopyFilename = (char*) malloc(i);
949 gamePasteFilename = (char*) malloc(i);
950 snprintf(gameCopyFilename,i, "%s/.xboard%05uc.pgn", p, getpid());
951 snprintf(gamePasteFilename,i, "%s/.xboard%05up.pgn", p, getpid());
953 { // [HGM] initstring: kludge to fix bad bug. expand '\n' characters in init string and computer string.
954 static char buf[MSG_SIZ];
955 snprintf(buf, MSG_SIZ, appData.sysOpen, DATADIR);
956 ASSIGN(appData.sysOpen, buf); // expand %s in -openCommand to DATADIR (usefull for OS X configuring)
957 EscapeExpand(buf, appData.firstInitString);
958 appData.firstInitString = strdup(buf);
959 EscapeExpand(buf, appData.secondInitString);
960 appData.secondInitString = strdup(buf);
961 EscapeExpand(buf, appData.firstComputerString);
962 appData.firstComputerString = strdup(buf);
963 EscapeExpand(buf, appData.secondComputerString);
964 appData.secondComputerString = strdup(buf);
967 if ((chessDir = (char *) getenv("CHESSDIR")) == NULL) {
970 if (chdir(chessDir) != 0) {
971 fprintf(stderr, _("%s: can't cd to CHESSDIR: "), programName);
977 if (appData.debugMode && appData.nameOfDebugFile && strcmp(appData.nameOfDebugFile, "stderr")) {
978 /* [DM] debug info to file [HGM] make the filename a command-line option, and allow it to remain stderr */
979 if ((debugFP = fopen(appData.nameOfDebugFile, "w")) == NULL) {
980 printf(_("Failed to open file '%s'\n"), appData.nameOfDebugFile);
983 setbuf(debugFP, NULL);
987 if (appData.debugMode) {
988 fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
992 /* [HGM,HR] make sure board size is acceptable */
993 if(appData.NrFiles > BOARD_FILES ||
994 appData.NrRanks > BOARD_RANKS )
995 DisplayFatalError(_("Recompile with larger BOARD_RANKS or BOARD_FILES to support this size"), 0, 2);
998 /* This feature does not work; animation needs a rewrite */
999 appData.highlightDragging = FALSE;
1003 gameInfo.variant = StringToVariant(appData.variant);
1004 InitPosition(FALSE);
1007 * determine size, based on supplied or remembered -size, or screen size
1009 if (isdigit(appData.boardSize[0])) {
1010 i = sscanf(appData.boardSize, "%d,%d,%d,%d,%d,%d,%d", &squareSize,
1011 &lineGap, &clockFontPxlSize, &coordFontPxlSize,
1012 &fontPxlSize, &smallLayout, &tinyLayout);
1014 fprintf(stderr, _("%s: bad boardSize syntax %s\n"),
1015 programName, appData.boardSize);
1019 squareSize = (squareSize*8 + BOARD_WIDTH/2)/BOARD_WIDTH; // scale height
1021 /* Find some defaults; use the nearest known size */
1022 SizeDefaults *szd, *nearest;
1023 int distance = 99999;
1024 nearest = szd = sizeDefaults;
1025 while (szd->name != NULL) {
1026 if (abs(szd->squareSize - squareSize) < distance) {
1028 distance = abs(szd->squareSize - squareSize);
1029 if (distance == 0) break;
1033 if (i < 2) lineGap = nearest->lineGap;
1034 if (i < 3) clockFontPxlSize = nearest->clockFontPxlSize;
1035 if (i < 4) coordFontPxlSize = nearest->coordFontPxlSize;
1036 if (i < 5) fontPxlSize = nearest->fontPxlSize;
1037 if (i < 6) smallLayout = nearest->smallLayout;
1038 if (i < 7) tinyLayout = nearest->tinyLayout;
1041 SizeDefaults *szd = sizeDefaults;
1042 if (*appData.boardSize == NULLCHAR) {
1043 // GdkScreen *screen = gtk_window_get_screen(GTK_WINDOW(mainwindow)); // TODO: this does not work, as no mainwindow yet
1044 GdkScreen *screen = gdk_screen_get_default();
1045 guint screenwidth = gdk_screen_get_width(screen);
1046 guint screenheight = gdk_screen_get_height(screen);
1047 while (screenwidth < (szd->minScreenSize*BOARD_WIDTH + 4)/8 ||
1048 screenheight < (szd->minScreenSize*BOARD_HEIGHT + 4)/8) {
1051 if (szd->name == NULL) szd--;
1052 appData.boardSize = strdup(szd->name); // [HGM] settings: remember name for saving settings
1054 while (szd->name != NULL &&
1055 StrCaseCmp(szd->name, appData.boardSize) != 0) szd++;
1056 if (szd->name == NULL) {
1057 fprintf(stderr, _("%s: unrecognized boardSize name %s\n"),
1058 programName, appData.boardSize);
1062 squareSize = szd->squareSize;
1063 lineGap = szd->lineGap;
1064 clockFontPxlSize = szd->clockFontPxlSize;
1065 coordFontPxlSize = szd->coordFontPxlSize;
1066 fontPxlSize = szd->fontPxlSize;
1067 smallLayout = szd->smallLayout;
1068 tinyLayout = szd->tinyLayout;
1069 // [HGM] font: use defaults from settings file if available and not overruled
1072 defaultLineGap = lineGap;
1073 if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
1075 /* [HR] height treated separately (hacked) */
1076 boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
1077 // boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
1080 * Determine what fonts to use.
1082 InitializeFonts((2*clockFontPxlSize+1)/3, coordFontPxlSize, fontPxlSize);
1085 * Detect if there are not enough colors available and adapt.
1088 if (DefaultDepth(xDisplay, xScreen) <= 2) {
1089 appData.monoMode = True;
1093 forceMono = MakeColors();
1096 fprintf(stderr, _("%s: too few colors available; trying monochrome mode\n"),
1098 appData.monoMode = True;
1101 ParseIcsTextColors();
1107 layoutName = "tinyLayout";
1108 } else if (smallLayout) {
1109 layoutName = "smallLayout";
1111 layoutName = "normalLayout";
1114 if(appData.logoSize) appData.logoSize = boardWidth/4-3;
1115 wpMain.width = -1; // prevent popup sizes window
1116 optList = BoardPopUp(squareSize, lineGap, (void*)
1126 InitDrawingHandle(optList + W_BOARD);
1127 shellWidget = shells[BoardWindow];
1128 currBoard = &optList[W_BOARD];
1129 boardWidget = optList[W_BOARD].handle;
1130 menuBarWidget = optList[W_MENU].handle;
1131 dropMenu = optList[W_DROP].handle;
1132 titleWidget = optList[optList[W_TITLE].type != -1 ? W_TITLE : W_SMALL].handle;
1134 formWidget = XtParent(boardWidget);
1135 XtSetArg(args[0], XtNbackground, &timerBackgroundPixel);
1136 XtSetArg(args[1], XtNforeground, &timerForegroundPixel);
1137 XtGetValues(optList[W_WHITE].handle, args, 2);
1138 if (appData.showButtonBar) { // can't we use timer pixels for this? (Or better yet, just black & white?)
1139 XtSetArg(args[0], XtNbackground, &buttonBackgroundPixel);
1140 XtSetArg(args[1], XtNforeground, &buttonForegroundPixel);
1141 XtGetValues(optList[W_PAUSE].handle, args, 2);
1145 // [HGM] it seems the layout code ends here, but perhaps the color stuff is size independent and would
1146 // not need to go into InitDrawingSizes().
1150 // add accelerators to main shell
1151 gtk_window_add_accel_group(GTK_WINDOW(shellWidget), GtkAccelerators);
1154 * Create an icon. (Use two icons, to indicate whther it is white's or black's turn.)
1156 WhiteIcon = LoadIconFile("icon_white");
1157 BlackIcon = LoadIconFile("icon_black");
1158 SetClockIcon(0); // sets white icon
1162 * Create a cursor for the board widget.
1165 window_attributes.cursor = XCreateFontCursor(xDisplay, XC_hand2);
1166 XChangeWindowAttributes(xDisplay, xBoardWindow,
1167 CWCursor, &window_attributes);
1171 * Inhibit shell resizing.
1174 shellArgs[0].value = (XtArgVal) &w;
1175 shellArgs[1].value = (XtArgVal) &h;
1176 XtGetValues(shellWidget, shellArgs, 2);
1177 shellArgs[4].value = shellArgs[2].value = w;
1178 shellArgs[5].value = shellArgs[3].value = h;
1179 // XtSetValues(shellWidget, &shellArgs[2], 4);
1182 // Note: We cannot do sensible sizing here, because the height of the clock widget is not yet known
1183 // It wil only become known asynchronously, when we first write a string into it.
1184 // This will then change the clock widget height, which triggers resizing the top-level window
1185 // and a configure event. Only then can we know the total height of the top-level window,
1186 // and calculate the height we need. The clockKludge flag suppresses all resizing until
1187 // that moment comes, after which the configure event-handler handles it through a (delayed) DragProg.
1190 gtk_widget_get_allocation(shells[BoardWindow], &a);
1191 w = a.width; h = a.height;
1192 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1193 clockKludge = hc = a.height;
1194 gtk_widget_get_allocation(boardWidget, &a);
1195 // marginW = w - boardWidth; // [HGM] needed to set new shellWidget size when we resize board
1196 marginH = h - a.height - hc; // subtract current clock height, so it can be added back dynamically
1202 if(appData.logoSize)
1203 { // locate and read user logo
1205 snprintf(buf, MSG_SIZ, "%s/%s.png", appData.logoDir, UserName());
1206 ASSIGN(userLogo, buf);
1209 if (appData.animate || appData.animateDragging)
1212 g_signal_connect(shells[BoardWindow], "key-press-event", G_CALLBACK(KeyPressProc), NULL);
1213 g_signal_connect(shells[BoardWindow], "configure-event", G_CALLBACK(EventProc), NULL);
1215 /* [AS] Restore layout */
1216 if( wpMoveHistory.visible ) {
1220 if( wpEvalGraph.visible )
1225 if( wpEngineOutput.visible ) {
1226 EngineOutputPopUp();
1229 if( wpConsole.visible && appData.icsActive ) {
1234 gameInfo.boardWidth = 0; // [HGM] pieces: kludge to ensure InitPosition() calls InitDrawingSizes()
1239 if (errorExitStatus == -1) {
1240 if (appData.icsActive) {
1241 /* We now wait until we see "login:" from the ICS before
1242 sending the logon script (problems with timestamp otherwise) */
1243 /*ICSInitScript();*/
1244 if (appData.icsInputBox) ICSInputBoxPopUp();
1248 signal(SIGWINCH, TermSizeSigHandler);
1250 signal(SIGINT, IntSigHandler);
1251 signal(SIGTERM, IntSigHandler);
1252 if (*appData.cmailGameName != NULLCHAR) {
1253 signal(SIGUSR1, CmailSigHandler);
1258 // XtSetKeyboardFocus(shellWidget, formWidget);
1260 XSetInputFocus(xDisplay, XtWindow(formWidget), RevertToPointerRoot, CurrentTime);
1263 /* check for GTK events and process them */
1266 gtk_main_iteration();
1269 if (appData.debugMode) fclose(debugFP); // [DM] debug
1276 while(gtk_events_pending()) gtk_main_iteration();
1280 TermSizeSigHandler (int sig)
1286 IntSigHandler (int sig)
1292 CmailSigHandler (int sig)
1297 signal(SIGUSR1, SIG_IGN); /* suspend handler */
1299 /* Activate call-back function CmailSigHandlerCallBack() */
1300 OutputToProcess(cmailPR, (char *)(&dummy), sizeof(int), &error);
1302 signal(SIGUSR1, CmailSigHandler); /* re-activate handler */
1306 CmailSigHandlerCallBack (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1309 ReloadCmailMsgEvent(TRUE); /* Reload cmail msg */
1311 /**** end signal code ****/
1314 #define Abs(n) ((n)<0 ? -(n) : (n))
1317 InsertPxlSize (char *pattern, int targetPxlSize)
1320 snprintf(buf, MSG_SIZ, pattern, targetPxlSize); // pattern is something like "Sans Bold %d"
1327 InsertPxlSize (char *pattern, int targetPxlSize)
1329 char *base_fnt_lst, strInt[12], *p, *q;
1330 int alternatives, i, len, strIntLen;
1333 * Replace the "*" (if present) in the pixel-size slot of each
1334 * alternative with the targetPxlSize.
1338 while ((p = strchr(p, ',')) != NULL) {
1342 snprintf(strInt, sizeof(strInt), "%d", targetPxlSize);
1343 strIntLen = strlen(strInt);
1344 base_fnt_lst = calloc(1, strlen(pattern) + strIntLen * alternatives + 1);
1348 while (alternatives--) {
1349 char *comma = strchr(p, ',');
1350 for (i=0; i<14; i++) {
1351 char *hyphen = strchr(p, '-');
1353 if (comma && hyphen > comma) break;
1354 len = hyphen + 1 - p;
1355 if (i == 7 && *p == '*' && len == 2) {
1357 memcpy(q, strInt, strIntLen);
1367 len = comma + 1 - p;
1374 return base_fnt_lst;
1380 CreateFontSet (char *base_fnt_lst)
1383 char **missing_list;
1387 fntSet = XCreateFontSet(xDisplay, base_fnt_lst,
1388 &missing_list, &missing_count, &def_string);
1389 if (appData.debugMode) {
1391 XFontStruct **font_struct_list;
1392 char **font_name_list;
1393 fprintf(debugFP, "Requested font set for list %s\n", base_fnt_lst);
1395 fprintf(debugFP, " got list %s, locale %s\n",
1396 XBaseFontNameListOfFontSet(fntSet),
1397 XLocaleOfFontSet(fntSet));
1398 count = XFontsOfFontSet(fntSet, &font_struct_list, &font_name_list);
1399 for (i = 0; i < count; i++) {
1400 fprintf(debugFP, " got charset %s\n", font_name_list[i]);
1403 for (i = 0; i < missing_count; i++) {
1404 fprintf(debugFP, " missing charset %s\n", missing_list[i]);
1407 if (fntSet == NULL) {
1408 fprintf(stderr, _("Unable to create font set for %s.\n"), base_fnt_lst);
1414 #else // not ENABLE_NLS
1416 * Find a font that matches "pattern" that is as close as
1417 * possible to the targetPxlSize. Prefer fonts that are k
1418 * pixels smaller to fonts that are k pixels larger. The
1419 * pattern must be in the X Consortium standard format,
1420 * e.g. "-*-helvetica-bold-r-normal--*-*-*-*-*-*-*-*".
1421 * The return value should be freed with XtFree when no
1426 FindFont (char *pattern, int targetPxlSize)
1428 char **fonts, *p, *best, *scalable, *scalableTail;
1429 int i, j, nfonts, minerr, err, pxlSize;
1431 fonts = XListFonts(xDisplay, pattern, 999999, &nfonts);
1433 fprintf(stderr, _("%s: no fonts match pattern %s\n"),
1434 programName, pattern);
1441 for (i=0; i<nfonts; i++) {
1444 if (*p != '-') continue;
1446 if (*p == NULLCHAR) break;
1447 if (*p++ == '-') j++;
1449 if (j < 7) continue;
1452 scalable = fonts[i];
1455 err = pxlSize - targetPxlSize;
1456 if (Abs(err) < Abs(minerr) ||
1457 (minerr > 0 && err < 0 && -err == minerr)) {
1463 if (scalable && Abs(minerr) > appData.fontSizeTolerance) {
1464 /* If the error is too big and there is a scalable font,
1465 use the scalable font. */
1466 int headlen = scalableTail - scalable;
1467 p = (char *) XtMalloc(strlen(scalable) + 10);
1468 while (isdigit(*scalableTail)) scalableTail++;
1469 sprintf(p, "%.*s%d%s", headlen, scalable, targetPxlSize, scalableTail);
1471 p = (char *) XtMalloc(strlen(best) + 2);
1472 safeStrCpy(p, best, strlen(best)+1 );
1474 if (appData.debugMode) {
1475 fprintf(debugFP, "resolved %s at pixel size %d\n to %s\n",
1476 pattern, targetPxlSize, p);
1478 XFreeFontNames(fonts);
1485 MarkMenuItem (char *menuRef, int state)
1487 MenuItem *item = MenuNameToItem(menuRef);
1489 if(item && item->handle) {
1490 ((GtkCheckMenuItem *) (item->handle))->active = state;
1496 EnableNamedMenuItem (char *menuRef, int state)
1498 MenuItem *item = MenuNameToItem(menuRef);
1500 if(item && item->handle) gtk_widget_set_sensitive(item->handle, state);
1505 EnableButtonBar (int state)
1508 XtSetSensitive(optList[W_BUTTON].handle, state);
1514 SetMenuEnables (Enables *enab)
1516 while (enab->name != NULL) {
1517 EnableNamedMenuItem(enab->name, enab->value);
1522 gboolean KeyPressProc(window, eventkey, data)
1524 GdkEventKey *eventkey;
1528 MoveTypeInProc(eventkey); // pop up for typed in moves
1531 /* check for other key values */
1532 switch(eventkey->keyval) {
1544 KeyBindingProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1545 { // [HGM] new method of key binding: specify MenuItem(FlipView) in stead of FlipViewProc in translation string
1547 if(*nprms == 0) return;
1548 item = MenuNameToItem(prms[0]);
1549 if(item) ((MenuProc *) item->proc) ();
1563 for (i=0; i<sizeof(dmEnables)/sizeof(DropMenuEnables); i++) {
1564 entry = XtNameToWidget(dropMenu, dmEnables[i].widget);
1565 p = strchr(gameMode == IcsPlayingWhite ? white_holding : black_holding,
1566 dmEnables[i].piece);
1567 XtSetSensitive(entry, p != NULL || !appData.testLegality
1568 /*!!temp:*/ || (gameInfo.variant == VariantCrazyhouse
1569 && !appData.icsActive));
1571 while (p && *p++ == dmEnables[i].piece) count++;
1572 snprintf(label, sizeof(label), "%s %d", dmEnables[i].widget, count);
1574 XtSetArg(args[j], XtNlabel, label); j++;
1575 XtSetValues(entry, args, j);
1581 do_flash_delay (unsigned long msec)
1587 FlashDelay (int flash_delay)
1589 if(flash_delay) do_flash_delay(flash_delay);
1593 Fraction (int x, int start, int stop)
1595 double f = ((double) x - start)/(stop - start);
1596 if(f > 1.) f = 1.; else if(f < 0.) f = 0.;
1600 static WindowPlacement wpNew;
1603 CoDrag (GtkWidget *sh, WindowPlacement *wp)
1605 int touch=0, fudge = 4, f = 3;
1606 GetActualPlacement(sh, wp);
1607 if(abs(wpMain.x + wpMain.width + 2*frameX - f - wp->x) < fudge) touch = 1; else // right touch
1608 if(abs(wp->x + wp->width + 2*frameX - f - wpMain.x) < fudge) touch = 2; else // left touch
1609 if(abs(wpMain.y + wpMain.height + frameX - f + frameY - wp->y) < fudge) touch = 3; else // bottom touch
1610 if(abs(wp->y + wp->height + frameX + frameY - f - wpMain.y) < fudge) touch = 4; // top touch
1611 //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);
1612 if(!touch ) return; // only windows that touch co-move
1613 if(touch < 3 && wpNew.height != wpMain.height) { // left or right and height changed
1614 int heightInc = wpNew.height - wpMain.height;
1615 double fracTop = Fraction(wp->y, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1616 double fracBot = Fraction(wp->y + wp->height + frameX + frameY + 1, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1617 wp->y += fracTop * heightInc;
1618 heightInc = (int) (fracBot * heightInc) - (int) (fracTop * heightInc);
1620 if(heightInc) XtSetArg(args[j], XtNheight, wp->height + heightInc), j++;
1622 wp->height += heightInc;
1623 } else if(touch > 2 && wpNew.width != wpMain.width) { // top or bottom and width changed
1624 int widthInc = wpNew.width - wpMain.width;
1625 double fracLeft = Fraction(wp->x, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1626 double fracRght = Fraction(wp->x + wp->width + 2*frameX + 1, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1627 wp->y += fracLeft * widthInc;
1628 widthInc = (int) (fracRght * widthInc) - (int) (fracLeft * widthInc);
1630 if(widthInc) XtSetArg(args[j], XtNwidth, wp->width + widthInc), j++;
1632 wp->width += widthInc;
1634 wp->x += wpNew.x - wpMain.x;
1635 wp->y += wpNew.y - wpMain.y;
1636 if(touch == 1) wp->x += wpNew.width - wpMain.width; else
1637 if(touch == 3) wp->y += wpNew.height - wpMain.height;
1639 XtSetArg(args[j], XtNx, wp->x); j++;
1640 XtSetArg(args[j], XtNy, wp->y); j++;
1641 XtSetValues(sh, args, j);
1643 gtk_window_move(GTK_WINDOW(sh), wp->x, wp->y);
1644 //printf("moved to (%d,%d)\n", wp->x, wp->y);
1645 gtk_window_resize(GTK_WINDOW(sh), wp->width, wp->height);
1649 ReSize (WindowPlacement *wp)
1652 int sqx, sqy, w, h, hc, lg = lineGap;
1653 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1654 hc = a.height; // clock height can depend on single / double line clock text!
1655 if(clockKludge && hc != clockKludge) wp->height += hc - clockKludge, clockKludge = 0;
1656 wpMain.height = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + marginH + hc;
1657 if(wp->width == wpMain.width && wp->height == wpMain.height) return; // not sized
1658 sqx = (wp->width - lg - marginW) / BOARD_WIDTH - lg;
1659 sqy = (wp->height - lg - marginH - hc) / BOARD_HEIGHT - lg;
1660 if(sqy < sqx) sqx = sqy;
1661 if(sqx < 20) return;
1662 if(appData.overrideLineGap < 0) { // do second iteration with adjusted lineGap
1664 lg = lineGap = sqx < 37 ? 1 : sqx < 59 ? 2 : sqx < 116 ? 3 : 4;
1665 sqx = (wp->width - lg - marginW) / BOARD_WIDTH - lg;
1666 sqy = (wp->height - lg - marginH - hc) / BOARD_HEIGHT - lg;
1667 if(sqy < sqx) sqx = sqy;
1668 lg = sqx < 37 ? 1 : sqx < 59 ? 2 : sqx < 116 ? 3 : 4;
1669 if(sqx == oldSqx + 1 && lg == lineGap + 1) sqx = oldSqx, squareSize = 0; // prevent oscillations, force resize by kludge
1671 if(sqx != squareSize) {
1672 squareSize = sqx; // adopt new square size
1673 CreatePNGPieces(); // make newly scaled pieces
1674 InitDrawingSizes(0, 0); // creates grid etc.
1675 } else ResizeBoardWindow(BOARD_WIDTH * (squareSize + lineGap) + lineGap, BOARD_HEIGHT * (squareSize + lineGap) + lineGap, 0);
1676 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
1677 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
1678 if(optList[W_BOARD].max > w) optList[W_BOARD].max = w;
1679 if(optList[W_BOARD].value > h) optList[W_BOARD].value = h;
1682 static guint delayedDragTag = 0;
1691 // GetActualPlacement(shellWidget, &wpNew);
1692 if(wpNew.x == wpMain.x && wpNew.y == wpMain.y && // not moved
1693 wpNew.width == wpMain.width && wpNew.height == wpMain.height) { // not sized
1694 busy = 0; return; // false alarm
1697 if(appData.useStickyWindows) {
1698 if(shellUp[EngOutDlg]) CoDrag(shells[EngOutDlg], &wpEngineOutput);
1699 if(shellUp[HistoryDlg]) CoDrag(shells[HistoryDlg], &wpMoveHistory);
1700 if(shellUp[EvalGraphDlg]) CoDrag(shells[EvalGraphDlg], &wpEvalGraph);
1701 if(shellUp[GameListDlg]) CoDrag(shells[GameListDlg], &wpGameList);
1702 if(shellUp[ChatDlg]) CoDrag(shells[ChatDlg], &wpConsole);
1705 DrawPosition(True, NULL);
1706 if(delayedDragTag) g_source_remove(delayedDragTag);
1707 delayedDragTag = 0; // now drag executed, make sure next DelayedDrag will not cancel timer event (which could now be used by other)
1714 //printf("old timr = %d\n", delayedDragTag);
1715 if(delayedDragTag) g_source_remove(delayedDragTag);
1716 delayedDragTag = g_timeout_add( 200, (GSourceFunc) DragProc, NULL);
1717 //printf("new timr = %d\n", delayedDragTag);
1721 EventProc (GtkWidget *widget, GdkEvent *event, gpointer g)
1723 //printf("event proc (%d,%d) %dx%d\n", event->configure.x, event->configure.y, event->configure.width, event->configure.height);
1725 wpNew.x = event->configure.x;
1726 wpNew.y = event->configure.y;
1727 wpNew.width = event->configure.width;
1728 wpNew.height = event->configure.height;
1729 DelayedDrag(); // as long as events keep coming in faster than 50 msec, they destroy each other
1735 /* Disable all user input other than deleting the window */
1736 static int frozen = 0;
1742 /* Grab by a widget that doesn't accept input */
1743 gtk_grab_add(optList[W_MESSG].handle);
1747 /* Undo a FreezeUI */
1751 if (!frozen) return;
1752 gtk_grab_remove(optList[W_MESSG].handle);
1759 static int oldPausing = FALSE;
1760 static GameMode oldmode = (GameMode) -1;
1762 if (!boardWidget) return;
1764 if (pausing != oldPausing) {
1765 oldPausing = pausing;
1766 MarkMenuItem("Mode.Pause", pausing);
1768 if (appData.showButtonBar) {
1769 /* Always toggle, don't set. Previous code messes up when
1770 invoked while the button is pressed, as releasing it
1771 toggles the state again. */
1773 gdk_color_parse( pausing ? "#808080" : "#F0F0F0", &color );
1774 gtk_widget_modify_bg ( GTK_WIDGET(optList[W_PAUSE].handle), GTK_STATE_NORMAL, &color );
1778 wname = ModeToWidgetName(oldmode);
1779 if (wname != NULL) {
1780 MarkMenuItem(wname, False);
1782 wname = ModeToWidgetName(gameMode);
1783 if (wname != NULL) {
1784 MarkMenuItem(wname, True);
1787 MarkMenuItem("Mode.MachineMatch", matchMode && matchGame < appData.matchGames);
1789 /* Maybe all the enables should be handled here, not just this one */
1790 EnableNamedMenuItem("Mode.Training", gameMode == Training || gameMode == PlayFromGameFile);
1792 DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
1797 * Button/menu procedures
1800 void CopyFileToClipboard(gchar *filename)
1802 gchar *selection_tmp;
1806 FILE* f = fopen(filename, "r");
1809 if (f == NULL) return;
1813 selection_tmp = g_try_malloc(len + 1);
1814 if (selection_tmp == NULL) {
1815 printf("Malloc failed in CopyFileToClipboard\n");
1818 count = fread(selection_tmp, 1, len, f);
1821 g_free(selection_tmp);
1824 selection_tmp[len] = NULLCHAR; // file is now in selection_tmp
1826 // copy selection_tmp to clipboard
1827 GdkDisplay *gdisp = gdk_display_get_default();
1829 g_free(selection_tmp);
1832 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1833 gtk_clipboard_set_text(cb, selection_tmp, -1);
1834 g_free(selection_tmp);
1838 CopySomething (char *src)
1840 GdkDisplay *gdisp = gdk_display_get_default();
1842 if(!src) { CopyFileToClipboard(gameCopyFilename); return; }
1843 if (gdisp == NULL) return;
1844 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1845 gtk_clipboard_set_text(cb, src, -1);
1849 PastePositionProc ()
1851 GdkDisplay *gdisp = gdk_display_get_default();
1855 if (gdisp == NULL) return;
1856 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1857 fenstr = gtk_clipboard_wait_for_text(cb);
1858 if (fenstr==NULL) return; // nothing had been selected to copy
1859 EditPositionPasteFEN(fenstr);
1871 // get game from clipboard
1872 GdkDisplay *gdisp = gdk_display_get_default();
1873 if (gdisp == NULL) return;
1874 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1875 text = gtk_clipboard_wait_for_text(cb);
1876 if (text == NULL) return; // nothing to paste
1879 // write to temp file
1880 if (text == NULL || len == 0) {
1881 return; //nothing to paste
1883 f = fopen(gamePasteFilename, "w");
1885 DisplayError(_("Can't open temp file"), errno);
1888 fwrite(text, 1, len, f);
1892 LoadGameFromFile(gamePasteFilename, 0, gamePasteFilename, TRUE);
1899 QuitWrapper (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1905 void MoveTypeInProc(eventkey)
1906 GdkEventKey *eventkey;
1910 // ingnore if ctrl, alt, or meta is pressed
1911 if (eventkey->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_META_MASK)) {
1915 buf[0]=eventkey->keyval;
1917 if (eventkey->keyval > 32 && eventkey->keyval < 256)
1918 ConsoleAutoPopUp (buf);
1923 TempBackwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1925 if (!TempBackwardActive) {
1926 TempBackwardActive = True;
1932 TempForwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1934 /* Check to see if triggered by a key release event for a repeating key.
1935 * If so the next queued event will be a key press of the same key at the same time */
1936 if (XEventsQueued(xDisplay, QueuedAfterReading)) {
1938 XPeekEvent(xDisplay, &next);
1939 if (next.type == KeyPress && next.xkey.time == event->xkey.time &&
1940 next.xkey.keycode == event->xkey.keycode)
1944 TempBackwardActive = False;
1950 { // called from menu
1953 snprintf(buf, MSG_SIZ, "%s ./man.command", appData.sysOpen);
1956 system("xterm -e man xboard &");
1961 SetWindowTitle (char *text, char *title, char *icon)
1966 if (appData.titleInWindow) {
1968 XtSetArg(args[i], XtNlabel, text); i++;
1969 XtSetValues(titleWidget, args, i);
1972 XtSetArg(args[i], XtNiconName, (XtArgVal) icon); i++;
1973 XtSetArg(args[i], XtNtitle, (XtArgVal) title); i++;
1974 XtSetValues(shellWidget, args, i);
1975 XSync(xDisplay, False);
1977 if (appData.titleInWindow) {
1978 SetWidgetLabel(titleWidget, text);
1980 gtk_window_set_title (GTK_WINDOW(shells[BoardWindow]), title);
1985 DisplayIcsInteractionTitle (String message)
1988 if (oldICSInteractionTitle == NULL) {
1989 /* Magic to find the old window title, adapted from vim */
1990 char *wina = getenv("WINDOWID");
1992 Window win = (Window) atoi(wina);
1993 Window root, parent, *children;
1994 unsigned int nchildren;
1995 int (*oldHandler)() = XSetErrorHandler(NullXErrorCheck);
1997 if (XFetchName(xDisplay, win, &oldICSInteractionTitle)) break;
1998 if (!XQueryTree(xDisplay, win, &root, &parent,
1999 &children, &nchildren)) break;
2000 if (children) XFree((void *)children);
2001 if (parent == root || parent == 0) break;
2004 XSetErrorHandler(oldHandler);
2006 if (oldICSInteractionTitle == NULL) {
2007 oldICSInteractionTitle = "xterm";
2010 printf("\033]0;%s\007", message);
2017 DisplayTimerLabel (Option *opt, char *color, long timer, int highlight)
2019 GtkWidget *w = (GtkWidget *) opt->handle;
2026 strcpy(bgcolor, "black");
2027 strcpy(fgcolor, "white");
2029 strcpy(bgcolor, "white");
2030 strcpy(fgcolor, "black");
2033 appData.lowTimeWarning &&
2034 (timer / 1000) < appData.icsAlarmTime) {
2035 strcpy(fgcolor, appData.lowTimeWarningColor);
2038 gdk_color_parse( bgcolor, &col );
2039 gtk_widget_modify_bg(gtk_widget_get_parent(opt->handle), GTK_STATE_NORMAL, &col);
2041 if (appData.clockMode) {
2042 markup = g_markup_printf_escaped("<span font=\"%s\" background=\"%s\" foreground=\"%s\">%s:%s%s</span>", appData.clockFont,
2043 bgcolor, fgcolor, color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
2044 // markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s:%s%s</span>",
2045 // bgcolor, fgcolor, color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
2047 markup = g_markup_printf_escaped("<span font=\"%s\" background=\"%s\" foreground=\"%s\">%s </span>", appData.clockFont,
2048 bgcolor, fgcolor, color);
2049 // markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s </span>",
2050 // bgcolor, fgcolor, color);
2052 gtk_label_set_markup(GTK_LABEL(w), markup);
2056 static GdkPixbuf **clockIcons[] = { &WhiteIcon, &BlackIcon };
2059 SetClockIcon (int color)
2061 GdkPixbuf *pm = *clockIcons[color];
2062 if (mainwindowIcon != pm) {
2063 mainwindowIcon = pm;
2065 gtkosx_application_set_dock_icon_pixbuf(theApp, mainwindowIcon);
2067 gtk_window_set_icon(GTK_WINDOW(shellWidget), mainwindowIcon);
2072 #define INPUT_SOURCE_BUF_SIZE 8192
2081 char buf[INPUT_SOURCE_BUF_SIZE];
2086 DoInputCallback(io, cond, data)
2091 /* read input from one of the input source (for example a chess program, ICS, etc).
2092 * and call a function that will handle the input
2099 /* All information (callback function, file descriptor, etc) is
2100 * saved in an InputSource structure
2102 InputSource *is = (InputSource *) data;
2104 if (is->lineByLine) {
2105 count = read(is->fd, is->unused,
2106 INPUT_SOURCE_BUF_SIZE - (is->unused - is->buf));
2108 if(count == 0 && is->kind == CPReal && shells[ChatDlg]) { // [HGM] absence of terminal is no error if ICS Console present
2109 RemoveInputSource(is); // cease reading stdin
2110 stdoutClosed = TRUE; // suppress future output
2113 (is->func)(is, is->closure, is->buf, count, count ? errno : 0);
2116 is->unused += count;
2118 /* break input into lines and call the callback function on each
2121 while (p < is->unused) {
2122 q = memchr(p, '\n', is->unused - p);
2123 if (q == NULL) break;
2125 (is->func)(is, is->closure, p, q - p, 0);
2128 /* remember not yet used part of the buffer */
2130 while (p < is->unused) {
2135 /* read maximum length of input buffer and send the whole buffer
2136 * to the callback function
2138 count = read(is->fd, is->buf, INPUT_SOURCE_BUF_SIZE);
2143 (is->func)(is, is->closure, is->buf, count, error);
2145 return True; // Must return true or the watch will be removed
2148 InputSourceRef AddInputSource(pr, lineByLine, func, closure)
2155 GIOChannel *channel;
2156 ChildProc *cp = (ChildProc *) pr;
2158 is = (InputSource *) calloc(1, sizeof(InputSource));
2159 is->lineByLine = lineByLine;
2163 is->fd = fileno(stdin);
2165 is->kind = cp->kind;
2166 is->fd = cp->fdFrom;
2169 is->unused = is->buf;
2173 /* GTK-TODO: will this work on windows?*/
2175 channel = g_io_channel_unix_new(is->fd);
2176 g_io_channel_set_close_on_unref (channel, TRUE);
2177 is->sid = g_io_add_watch(channel, G_IO_IN,(GIOFunc) DoInputCallback, is);
2179 is->closure = closure;
2180 return (InputSourceRef) is;
2185 RemoveInputSource(isr)
2188 InputSource *is = (InputSource *) isr;
2190 if (is->sid == 0) return;
2191 g_source_remove(is->sid);
2198 static Boolean frameWaiting;
2201 FrameAlarm (int sig)
2203 frameWaiting = False;
2204 /* In case System-V style signals. Needed?? */
2205 signal(SIGALRM, FrameAlarm);
2209 FrameDelay (int time)
2211 struct itimerval delay;
2214 frameWaiting = True;
2215 signal(SIGALRM, FrameAlarm);
2216 delay.it_interval.tv_sec =
2217 delay.it_value.tv_sec = time / 1000;
2218 delay.it_interval.tv_usec =
2219 delay.it_value.tv_usec = (time % 1000) * 1000;
2220 setitimer(ITIMER_REAL, &delay, NULL);
2221 while (frameWaiting) pause();
2222 delay.it_interval.tv_sec = delay.it_value.tv_sec = 0;
2223 delay.it_interval.tv_usec = delay.it_value.tv_usec = 0;
2224 setitimer(ITIMER_REAL, &delay, NULL);
2231 FrameDelay (int time)
2234 XSync(xDisplay, False);
2236 // gtk_main_iteration_do(False);
2239 usleep(time * 1000);
2245 FindLogo (char *place, char *name, char *buf)
2246 { // check if file exists in given place
2248 if(!place) return 0;
2249 snprintf(buf, MSG_SIZ, "%s/%s.png", place, name);
2250 if(*place && strcmp(place, ".") && (f = fopen(buf, "r")) ) {
2258 LoadLogo (ChessProgramState *cps, int n, Boolean ics)
2260 char buf[MSG_SIZ], *logoName = buf;
2261 if(appData.logo[n][0]) {
2262 logoName = appData.logo[n];
2263 } else if(appData.autoLogo) {
2264 if(ics) { // [HGM] logo: in ICS mode second can be used for ICS
2265 sprintf(buf, "%s/%s.png", appData.logoDir, appData.icsHost);
2266 } else { // engine; cascade
2267 if(!FindLogo(appData.logoDir, cps->tidy, buf) && // first try user log folder
2268 !FindLogo(appData.directory[n], "logo", buf) && // then engine directory
2269 !FindLogo("/usr/local/share/games/plugins/logos", cps->tidy, buf) ) // then system folders
2270 FindLogo("/usr/share/games/plugins/logos", cps->tidy, buf);
2274 { ASSIGN(cps->programLogo, logoName); }
2278 UpdateLogos (int displ)
2280 if(optList[W_WHITE-1].handle == NULL) return;
2281 LoadLogo(&first, 0, 0);
2282 LoadLogo(&second, 1, appData.icsActive);
2283 if(displ) DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
2287 void FileNamePopUpWrapper(label, def, filter, proc, pathFlag, openMode, name, fp)
2298 GtkFileFilter *gtkfilter;
2299 GtkFileFilter *gtkfilter_all;
2301 char fileext[10] = "";
2302 char *result = NULL;
2305 /* make a copy of the filter string, so that strtok can work with it*/
2306 cp = strdup(filter);
2308 /* add filters for file extensions */
2309 gtkfilter = gtk_file_filter_new();
2310 gtkfilter_all = gtk_file_filter_new();
2312 /* one filter to show everything */
2313 gtk_file_filter_add_pattern(gtkfilter_all, "*.*");
2314 gtk_file_filter_set_name (gtkfilter_all, "All Files");
2316 /* add filter if present */
2317 result = strtok(cp, space);
2318 while( result != NULL ) {
2319 snprintf(fileext,10,"*%s",result);
2320 result = strtok( NULL, space );
2321 gtk_file_filter_add_pattern(gtkfilter, fileext);
2324 /* second filter to only show what's useful */
2325 gtk_file_filter_set_name (gtkfilter,filter);
2327 if (openMode[0] == 'r')
2329 dialog = gtk_file_chooser_dialog_new (label,
2331 GTK_FILE_CHOOSER_ACTION_OPEN,
2332 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2333 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
2338 dialog = gtk_file_chooser_dialog_new (label,
2340 GTK_FILE_CHOOSER_ACTION_SAVE,
2341 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2342 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
2344 /* add filename suggestions */
2345 if (strlen(def) > 0 )
2346 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), def);
2348 //gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER (dialog),TRUE);
2352 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter_all);
2353 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2354 /* activate filter */
2355 gtk_file_chooser_set_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2357 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
2362 filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
2365 f = fopen(filename, openMode);
2368 DisplayError(_("Failed to open file"), errno);
2372 /* TODO add indec */
2374 ASSIGN(*name, filename);
2375 ScheduleDelayedEvent(DelayedLoad, 50);
2380 gtk_widget_destroy (dialog);