diff --git a/xwords4/common/comms.c b/xwords4/common/comms.c index 825184a80..2e9d84700 100644 --- a/xwords4/common/comms.c +++ b/xwords4/common/comms.c @@ -643,15 +643,14 @@ CommsCtxt* comms_makeFromStream( MPFORMAL XWStreamCtxt* stream, XW_UtilCtxt* util, const TransportProcs* procs, XP_U16 forceChannel ) { - XP_Bool isServer; - XP_U16 nAddrRecs, nPlayersHere, nPlayersTotal; + XP_U16 nPlayersHere, nPlayersTotal; AddressRecord** prevsAddrNext; MsgQueueElem** prevsQueueNext; XP_U16 version = stream_getVersion( stream ); CommsAddrRec addr; short ii; - isServer = stream_getU8( stream ); + XP_Bool isServer = stream_getU8( stream ); addrFromStream( &addr, stream ); if ( version >= STREAM_VERS_DEVIDS @@ -692,7 +691,7 @@ comms_makeFromStream( MPFORMAL XWStreamCtxt* stream, XW_UtilCtxt* util, comms->queueLen = stream_getU8( stream ); - nAddrRecs = stream_getU8( stream ); + XP_U16 nAddrRecs = stream_getU8( stream ); prevsAddrNext = &comms->recs; for ( ii = 0; ii < nAddrRecs; ++ii ) { AddressRecord* rec = (AddressRecord*)XP_CALLOC( mpool, sizeof(*rec)); @@ -1206,13 +1205,15 @@ makeElemWithID( CommsCtxt* comms, MsgID msgID, AddressRecord* rec, XP_U16 comms_getChannelSeed( CommsCtxt* comms ) { - while ( 0 == (comms->channelSeed & ~CHANNEL_MASK) ) { - comms->channelSeed = XP_RANDOM() & ~CHANNEL_MASK; - comms->channelSeed |= comms->forceChannel; - CNO_FMT( cbuf, comms->channelSeed ); - XP_LOGF( "%s: made seed: %s(%d)", __func__, cbuf, comms->channelSeed ); + XP_U16 result = !!comms ? comms->channelSeed : 0; + while ( !!comms && 0 == (result & ~CHANNEL_MASK) ) { + result = XP_RANDOM() & ~CHANNEL_MASK; + result |= comms->forceChannel; + CNO_FMT( cbuf, result ); + XP_LOGF( "%s: made seed: %s(%d)", __func__, cbuf, result ); + comms->channelSeed = result; } - return comms->channelSeed; + return result; } /* Send a message using the sequentially next MsgID. Save the message so @@ -1687,16 +1688,16 @@ got_connect_cmd( CommsCtxt* comms, XWStreamCtxt* stream, set_relay_state( comms, reconnected ? COMMS_RELAYSTATE_RECONNECTED : COMMS_RELAYSTATE_CONNECTED ); XWHostID myHostID = stream_getU8( stream ); - XP_LOGF( "%s: changing rr.myHostID from %x to %x", __func__, - comms->rr.myHostID, myHostID ); if ( comms->rr.myHostID != myHostID ) { + XP_LOGF( "%s: changing rr.myHostID from %x to %x", __func__, + comms->rr.myHostID, myHostID ); comms->rr.myHostID = myHostID; } isServer = HOST_ID_SERVER == comms->rr.myHostID; if ( isServer != comms->isServer ) { - XP_LOGF( "%s: becoming a server", __func__ ); + XP_LOGF( "%s: becoming%s a server", __func__, isServer ? "" : " NOT" ); comms->isServer = isServer; util_setIsServer( comms->util, comms->isServer ); diff --git a/xwords4/common/device.c b/xwords4/common/device.c index 7eedaf462..bf68cd471 100644 --- a/xwords4/common/device.c +++ b/xwords4/common/device.c @@ -53,7 +53,7 @@ load( XW_DUtilCtxt* dutil ) if ( 0 < stream_getSize( stream ) ) { state->devCount = stream_getU16( stream ); ++state->devCount; /* for testing until something's there */ - XP_LOGF( "%s(): read devCount: %d", __func__, state->devCount ); + /* XP_LOGF( "%s(): read devCount: %d", __func__, state->devCount ); */ } else { XP_LOGF( "%s(): empty stream!!", __func__ ); } diff --git a/xwords4/common/game.c b/xwords4/common/game.c index 187a6e1c7..cc531e3e6 100644 --- a/xwords4/common/game.c +++ b/xwords4/common/game.c @@ -388,6 +388,13 @@ game_getState( const XWGame* game, GameStateInfo* gsi ) comms_countPendingPackets(game->comms) : 0; } +XP_Bool +game_getIsServer( const XWGame* game ) +{ + XP_Bool result = comms_getIsServer( game->comms ); + return result; +} + void game_dispose( XWGame* game ) { @@ -642,7 +649,6 @@ gi_writeToStream( XWStreamCtxt* stream, const CurGameInfo* gi ) stream_putBits( stream, NPLAYERS_NBITS, gi->nPlayers ); stream_putBits( stream, nColsNBits, gi->boardSize ); stream_putBits( stream, 2, gi->serverRole ); - /* XP_LOGF( "%s: wrote serverRole of %d", __func__, gi->serverRole ); */ stream_putBits( stream, 1, gi->hintsNotAllowed ); stream_putBits( stream, 2, gi->phoniesAction ); stream_putBits( stream, 1, gi->timerEnabled ); diff --git a/xwords4/common/game.h b/xwords4/common/game.h index 10d4d7187..969fca0b4 100644 --- a/xwords4/common/game.h +++ b/xwords4/common/game.h @@ -89,6 +89,7 @@ XP_Bool game_receiveMessage( XWGame* game, XWStreamCtxt* stream, void game_dispose( XWGame* game ); void game_getState( const XWGame* game, GameStateInfo* gsi ); +XP_Bool game_getIsServer( const XWGame* game ); void gi_initPlayerInfo( MPFORMAL CurGameInfo* gi, const XP_UCHAR* nameTemplate ); diff --git a/xwords4/common/server.c b/xwords4/common/server.c index 85d5404dd..150e03b1a 100644 --- a/xwords4/common/server.c +++ b/xwords4/common/server.c @@ -237,8 +237,10 @@ syncPlayers( ServerCtxt* server ) static XP_Bool amServer( const ServerCtxt* server ) -{ - return SERVER_ISSERVER == server->vol.gi->serverRole; +{ + XP_Bool result = SERVER_ISSERVER == server->vol.gi->serverRole; + // LOG_RETURNF( "%d (seed=%d)", result, comms_getChannelSeed( server->vol.comms ) ); + return result; } static void @@ -867,7 +869,6 @@ makeRobotMove( ServerCtxt* server ) XP_Bool result = XP_FALSE; XP_Bool searchComplete; XP_S16 turn; - const TrayTileSet* tileSet; MoveInfo newMove = {0}; ModelCtxt* model = server->vol.model; CurGameInfo* gi = server->vol.gi; @@ -899,7 +900,7 @@ makeRobotMove( ServerCtxt* server ) model_resetCurrentTurn( model, turn ); if ( !forceTrade ) { - tileSet = model_getPlayerTiles( model, turn ); + const TrayTileSet* tileSet = model_getPlayerTiles( model, turn ); #ifdef XWFEATURE_BONUSALL XP_U16 allTilesBonus = server_figureFinishBonus( server, turn ); #endif @@ -1143,7 +1144,7 @@ server_do( ServerCtxt* server ) } else { XP_Bool moreToDo = XP_FALSE; server->serverDoing = XP_TRUE; - + XP_LOGF( "%s(): gameState: %s", __func__, getStateStr(server->nv.gameState) ); switch( server->nv.gameState ) { case XWSTATE_BEGIN: if ( server->nv.pendingRegistrations == 0 ) { /* all players on @@ -2952,6 +2953,7 @@ server_receiveMessage( ServerCtxt* server, XWStreamCtxt* incoming ) XP_Bool accepted = XP_FALSE; XP_Bool isServer = amServer( server ); const XW_Proto code = readProto( server, incoming ); + XP_LOGF( "%s(code=%s)", __func__, codeToStr(code) ); if ( code == XWPROTO_DEVICE_REGISTRATION ) { accepted = isServer; diff --git a/xwords4/common/smsproto.c b/xwords4/common/smsproto.c index a7e36f3bd..226b49a89 100644 --- a/xwords4/common/smsproto.c +++ b/xwords4/common/smsproto.c @@ -257,7 +257,7 @@ smsproto_prepOutbound( SMSProto* state, SMS_CMD cmd, XP_U32 gameID, } static SMSMsgArray* -appendLocMsg( SMSProto* state, SMSMsgArray* arr, SMSMsgLoc* msg ) +appendLocMsg( SMSProto* XP_UNUSED_DBG(state), SMSMsgArray* arr, SMSMsgLoc* msg ) { if ( NULL == arr ) { arr = XP_CALLOC( state->mpool, sizeof(*arr) ); @@ -273,7 +273,7 @@ appendLocMsg( SMSProto* state, SMSMsgArray* arr, SMSMsgLoc* msg ) } static SMSMsgArray* -appendNetMsg( SMSProto* state, SMSMsgArray* arr, SMSMsgNet* msg ) +appendNetMsg( SMSProto* XP_UNUSED_DBG(state), SMSMsgArray* arr, SMSMsgNet* msg ) { if ( NULL == arr ) { arr = XP_CALLOC( state->mpool, sizeof(*arr) ); @@ -396,7 +396,7 @@ smsproto_freeMsgArray( SMSProto* state, SMSMsgArray* arr ) } static void -freeMsg( SMSProto* state, MsgRec** msgp ) +freeMsg( SMSProto* XP_UNUSED_DBG(state), MsgRec** msgp ) { XP_FREEP( state->mpool, &(*msgp)->msgNet.data ); XP_FREEP( state->mpool, msgp ); @@ -595,7 +595,7 @@ rmFromPhoneRec( SMSProto* state, int fromPhoneIndex ) } static void -freeMsgIDRec( SMSProto* state, MsgIDRec* rec, int fromPhoneIndex, int msgIDIndex ) +freeMsgIDRec( SMSProto* state, MsgIDRec* XP_UNUSED_DBG(rec), int fromPhoneIndex, int msgIDIndex ) { FromPhoneRec* fromPhoneRec = &state->fromPhoneRecs[fromPhoneIndex]; MsgIDRec* msgIDRec = &fromPhoneRec->msgIDRecs[msgIDIndex]; diff --git a/xwords4/common/smsproto.h b/xwords4/common/smsproto.h index d2124554e..3960b08b9 100644 --- a/xwords4/common/smsproto.h +++ b/xwords4/common/smsproto.h @@ -104,5 +104,7 @@ void smsproto_freeMsgArray( SMSProto* state, SMSMsgArray* arr ); # ifdef DEBUG void smsproto_runTests( MPFORMAL XW_DUtilCtxt* dutil ); +# else +# define smsproto_runTests( p1, p2 ) # endif #endif diff --git a/xwords4/linux/Makefile b/xwords4/linux/Makefile index dc52a8abf..366e698b0 100644 --- a/xwords4/linux/Makefile +++ b/xwords4/linux/Makefile @@ -21,6 +21,7 @@ BUILD_DIR ?= . ifeq ($(MEMDEBUG),TRUE) DEFINES = -DMEM_DEBUG -DDEBUG -DENABLE_LOGGING -DNUMBER_KEY_AS_INDEX DEFINES += -DCOMMS_CHECKSUM +DEFINES += -DLOG_COMMS_MSGNOS CFLAGS += -g $(GPROFFLAG) -Wall -Wunused-parameter -Wcast-align -Werror -O0 # DEFINES += -DDEBUG_HASHING CFLAGS += -DDEBUG_TS -rdynamic @@ -144,8 +145,6 @@ DEFINES += -DCURSES_CELL_WIDTH=$(CURSES_CELL_WIDTH) endif DEFINES += $(UNICODE) -DEFINES += -DLOG_COMMS_MSGNOS - # Networking-related features. Only set these if STANDALONE is not set ifeq ($(STANDALONE),) @@ -210,16 +209,19 @@ endif ifdef DO_CURSES CURSES_OBJS = \ $(BUILD_PLAT_DIR)/cursesmain.o \ + $(BUILD_PLAT_DIR)/cursesboard.o \ + $(BUILD_PLAT_DIR)/cursesmenu.o \ + $(BUILD_PLAT_DIR)/curgamlistwin.o \ $(BUILD_PLAT_DIR)/cursesdraw.o \ $(BUILD_PLAT_DIR)/cursesask.o \ $(BUILD_PLAT_DIR)/cursesdlgutil.o \ $(BUILD_PLAT_DIR)/cursesletterask.o endif ifndef LIB_NO_UI -MAIN_OBJS = $(BUILD_PLAT_DIR)/linuxmain.o +MAIN_OBJS = $(BUILD_PLAT_DIR)/linuxmain.o \ + $(BUILD_PLAT_DIR)/gsrcwrap.o endif - OBJ = \ $(BUILD_PLAT_DIR)/filestream.o \ $(BUILD_PLAT_DIR)/linuxbt.o \ diff --git a/xwords4/linux/curgamlistwin.c b/xwords4/linux/curgamlistwin.c new file mode 100644 index 000000000..9f4efb834 --- /dev/null +++ b/xwords4/linux/curgamlistwin.c @@ -0,0 +1,293 @@ +/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ +/* + * Copyright 2020 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include + +#include "curgamlistwin.h" + +struct CursGameList { + WINDOW* window; + int width, height; + int curSel; + int yOffset; + GSList* games; + sqlite3* pDb; +}; + +static void adjustCurSel( CursGameList* cgl ); + +CursGameList* +cgl_init( sqlite3* pDb, int width, int height ) +{ + CursGameList* cgl = g_malloc0( sizeof( *cgl ) ); + cgl->pDb = pDb; + cgl->window = newwin( height, width, 0, 0 ); + XP_LOGF( "%s(): made window with height=%d, width=%d", __func__, height, width ); + cgl->width = width; + cgl->height = height; + return cgl; +} + +void +cgl_destroy( CursGameList* cgl ) +{ + g_slist_free_full( cgl->games, g_free ); + delwin( cgl->window ); + g_free( cgl ); +} + +static void +addOne( CursGameList* cgl, sqlite3_int64 rowid ) +{ + GameInfo gib; + if ( getGameInfo( cgl->pDb, rowid, &gib ) ) { + GameInfo* gibp = g_malloc( sizeof(*gibp) ); + *gibp = gib; + cgl->games = g_slist_append( cgl->games, gibp ); + } +} + +/* Load from the DB */ +void +cgl_refresh( CursGameList* cgl ) +{ + g_slist_free_full( cgl->games, g_free ); + cgl->games = NULL; + + sqlite3* pDb = cgl->pDb; + GSList* games = listGames( pDb ); + for ( GSList* iter = games; !!iter; iter = iter->next ) { + sqlite3_int64* rowid = (sqlite3_int64*)iter->data; + addOne( cgl, *rowid ); + } + cgl_draw( cgl ); +} + +static GSList* +findFor( CursGameList* cgl, sqlite3_int64 rowid ) +{ + GSList* result = NULL; + for ( GSList* iter = cgl->games; !!iter && !result; iter = iter->next ) { + GameInfo* gib = (GameInfo*)iter->data; + if ( gib->rowid == rowid ) { + result = iter; + } + } + return result; +} + +void +cgl_refreshOne( CursGameList* cgl, sqlite3_int64 rowid, bool select ) +{ + // Update the info. In place if it exists, otherwise creating a new list + // elem + + GameInfo gib; + if ( getGameInfo( cgl->pDb, rowid, &gib ) ) { + GameInfo* found; + GSList* elem = findFor( cgl, rowid ); + if ( !!elem ) { + found = (GameInfo*)elem->data; + *found = gib; + } else { + found = g_malloc( sizeof(*found) ); + *found = gib; + cgl->games = g_slist_append( cgl->games, found ); + } + + if ( select ) { + cgl->curSel = g_slist_index( cgl->games, found ); + } + adjustCurSel( cgl ); + } +} + +void +cgl_remove( CursGameList* cgl, sqlite3_int64 rowid ) +{ + GSList* elem = findFor( cgl, rowid ); + if ( !!elem ) { + g_free( elem->data ); + cgl->games = g_slist_delete_link( cgl->games, elem ); + } + adjustCurSel( cgl ); +} + +void +cgl_moveSel( CursGameList* cgl, bool down ) +{ + int nGames = g_slist_length( cgl->games ); + cgl->curSel += nGames + (down ? 1 : -1); + cgl->curSel %= nGames; + adjustCurSel( cgl ); +} + +static void +adjustCurSel( CursGameList* cgl ) +{ + XP_LOGF( "%s() start: curSel: %d; yOffset: %d", __func__, cgl->curSel, cgl->yOffset ); + int nGames = g_slist_length( cgl->games ); + if ( cgl->curSel >= nGames ) { + cgl->curSel = nGames - 1; + } + + /* Now adjust yOffset */ + int nVisRows = cgl->height - 2; /* 1 for the title and header rows */ + if ( cgl->curSel - cgl->yOffset >= nVisRows ) { + cgl->yOffset = cgl->curSel - nVisRows + 1; + } else { + while ( cgl->curSel < cgl->yOffset ) { + --cgl->yOffset; + } + } + + XP_LOGF( "%s() end: curSel: %d; yOffset: %d", __func__, cgl->curSel, cgl->yOffset ); + cgl_draw( cgl ); +} + +static int +countBits( int bits ) +{ + int result = 0; + while ( 0 != bits ) { + ++result; + bits &= bits - 1; + } + return result; +} + +static const char* +codeToLang( XP_LangCode langCode ) +{ + const char* langName = "<\?\?\?>"; + switch( langCode ) { + case 1: langName = "English"; break; + case 2: langName = "French"; break; + case 3: langName = "German"; break; + case 4: langName = "Turkish";break; + case 5: langName = "Arabic"; break; + case 6: langName = "Spanish"; break; + case 7: langName = "Swedish"; break; + case 8:langName = "Polish";; break; + case 9: langName = "Danish"; break; + case 10: langName = "Italian"; break; + case 11: langName = "Dutch"; break; + case 12: langName = "Catalan"; break; + case 13: langName = "Portuguese"; break; + case 15: langName = "Russian"; break; + case 17: langName = "Czech"; break; + case 18: langName = "Greek"; break; + case 19: langName = "Slovak"; break; + default: + XP_LOGF( "%s(): bad code %d", __func__, langCode ); + break; + // XP_ASSERT(0); + } + return langName; +} + +void +cgl_draw( CursGameList* cgl ) +{ + WINDOW* win = cgl->window; + werase( win ); + + const int nGames = g_slist_length( cgl->games ); + + /* Draw '+' at far right if scrollable */ + int nBelow = nGames - (cgl->height-2) - cgl->yOffset; + XP_LOGF( "%s(): yOffset: %d; nBelow: %d", __func__, cgl->yOffset, nBelow ); + if ( 0 < nBelow ) { + mvwaddstr( win, cgl->height-2, cgl->width - 1, "+" ); + } + if ( 0 < cgl->yOffset ) { + mvwaddstr( win, 0, cgl->width-1, "+" ); + } + + const char* cols[] = {"Row", "RowID", "Lang", "Scores", "GameID", "Role", "Room", + "nTotal", "nMissing", "Seed", "nMoves", "Turn", "nPend", }; + + int nShown = nGames <= cgl->height - 2 ? nGames : cgl->height - 2; + char* data[nShown + 1][VSIZE(cols)]; + for ( int ii = 0; ii < VSIZE(cols); ++ii ) { + data[0][ii] = g_strdup(cols[ii]); + } + int line = 1; + for ( int ii = 0; ii < nShown; ++ii ) { + const GameInfo* gi = g_slist_nth_data( cgl->games, ii + cgl->yOffset ); + int col = 0; + data[line][col++] = g_strdup_printf( "%d", ii + cgl->yOffset + 1 ); /* 1-based */ + data[line][col++] = g_strdup_printf( "%05lld", gi->rowid ); + data[line][col++] = g_strdup( codeToLang(gi->dictLang) ); + data[line][col++] = g_strdup( gi->scores ); + data[line][col++] = g_strdup_printf( "%d", gi->gameID ); + data[line][col++] = g_strdup_printf( "%d", gi->role ); + data[line][col++] = g_strdup( gi->room ); + data[line][col++] = g_strdup_printf( "%d", gi->nTotal ); + data[line][col++] = g_strdup_printf( "%d", countBits(gi->nMissing) ); + data[line][col++] = g_strdup_printf( "%d", gi->seed ); + data[line][col++] = g_strdup_printf( "%d", gi->nMoves ); + data[line][col++] = g_strdup_printf( "%d", gi->turn ); + data[line][col++] = g_strdup_printf( "%d", gi->nPending ); + XP_ASSERT( col == VSIZE(data[line]) ); + ++line; + } + + int maxlen = 0; + int offset = 0; + for ( int col = 0; col < VSIZE(data[0]); ++col ) { + for ( int line = 0; line < VSIZE(data); ++line ) { + char* str = data[line][col]; + int len = strlen(str); + if ( maxlen < len ) { + maxlen = len; + } + bool highlight = cgl->yOffset + line - 1 == cgl->curSel; + if ( highlight ) { + wstandout( win ); + } + mvwaddstr( win, line + 1, offset, str ); + if ( highlight ) { + wstandend( win ); + } + g_free( str ); + } + offset += maxlen + 2; + maxlen = 0; + } + + char buf[cgl->width + 1]; + snprintf( buf, VSIZE(buf), "%d games total", nGames ); + mvwaddstr( win, 0, 0, buf ); + + wrefresh( win ); +} + +const GameInfo* +cgl_getSel( CursGameList* cgl ) +{ + return g_slist_nth_data( cgl->games, cgl->curSel ); +} + +int +cgl_getNGames( CursGameList* cgl ) +{ + return g_slist_length( cgl->games ); +} diff --git a/xwords4/linux/curgamlistwin.h b/xwords4/linux/curgamlistwin.h new file mode 100644 index 000000000..e2841b280 --- /dev/null +++ b/xwords4/linux/curgamlistwin.h @@ -0,0 +1,43 @@ +/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ +/* + * Copyright 2020 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _CURGAMLISTWIN_H_ +#define _CURGAMLISTWIN_H_ + +#include + +#include "gamesdb.h" + +typedef struct CursGameList CursGameList; + +CursGameList* cgl_init( sqlite3* pDb, int width, int height ); +void cgl_destroy( CursGameList* cgl ); + +void cgl_refresh( CursGameList* cgl ); +void cgl_refreshOne( CursGameList* cgl, sqlite3_int64 rowid, bool select ); +void cgl_remove( CursGameList* cgl, sqlite3_int64 rowid ); + +void cgl_moveSel( CursGameList* cgl, bool down ); + +void cgl_draw( CursGameList* cgl ); + +const GameInfo* cgl_getSel( CursGameList* cgl ); +int cgl_getNGames( CursGameList* cgl ); + +#endif diff --git a/xwords4/linux/cursesask.c b/xwords4/linux/cursesask.c index 31dd27c82..5e7314310 100644 --- a/xwords4/linux/cursesask.c +++ b/xwords4/linux/cursesask.c @@ -32,11 +32,12 @@ /* Figure out how many lines there are and how wide the widest is. */ int -cursesask( CursesAppGlobals* globals, const char* question, short numButtons, +cursesask( WINDOW* window, const char* question, short numButtons, const char** buttons ) { WINDOW* confWin; int x, y, rows, row, nLines; + int left, top; short newSelButton = 0; short curSelButton = 1; /* force draw by being different */ short spacePerButton, num; @@ -45,7 +46,8 @@ cursesask( CursesAppGlobals* globals, const char* question, short numButtons, FormatInfo fi; int len; - getmaxyx(globals->boardWin, y, x); + getmaxyx( window, y, x); + getbegyx( window, top, left ); measureAskText( question, x-2, &fi ); len = fi.maxLen; @@ -62,8 +64,8 @@ cursesask( CursesAppGlobals* globals, const char* question, short numButtons, } nLines = ASK_HEIGHT + rows - 1; - confWin = newwin( nLines, len+(PAD*2), - (y/2) - (nLines/2), (x-len-2)/2 ); + confWin = newwin( nLines, len+(PAD*2), top + ((y/2) - (nLines/2)), + left + ((x-len-2)/2) ); keypad( confWin, TRUE ); wclear( confWin ); box( confWin, '|', '-'); @@ -120,9 +122,9 @@ cursesask( CursesAppGlobals* globals, const char* question, short numButtons, delwin( confWin ); /* this leaves a ghost line, but I can't figure out a better way. */ - wtouchln( globals->boardWin, (y/2)-(nLines/2), ASK_HEIGHT + rows - 1, 1 ); - wrefresh( globals->boardWin ); + wtouchln( window, (y/2)-(nLines/2), ASK_HEIGHT + rows - 1, 1 ); + wrefresh( window ); return curSelButton; -} /* ask */ +} /* cursesask */ #endif /* PLATFORM_NCURSES */ diff --git a/xwords4/linux/cursesask.h b/xwords4/linux/cursesask.h index 7e57db136..c53fd05b7 100644 --- a/xwords4/linux/cursesask.h +++ b/xwords4/linux/cursesask.h @@ -22,7 +22,7 @@ #include "cursesmain.h" -int cursesask( CursesAppGlobals* globals, const char* question, +int cursesask( WINDOW* window, const char* question, short numButtons, const char** buttons ); diff --git a/xwords4/linux/cursesboard.c b/xwords4/linux/cursesboard.c new file mode 100644 index 000000000..063ca443f --- /dev/null +++ b/xwords4/linux/cursesboard.c @@ -0,0 +1,1351 @@ +/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ +/* + * Copyright 2000 - 2020 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include + +#include "cursesboard.h" +#include "linuxmain.h" +#include "linuxutl.h" +#include "relaycon.h" +#include "cursesask.h" +#include "cursesmenu.h" +#include "cursesletterask.h" +#include "linuxdict.h" +#include "gamesdb.h" +#include "game.h" +#include "gsrcwrap.h" + +typedef struct CursesBoardState { + CursesAppGlobals* aGlobals; + LaunchParams* params; + CursesMenuState* menuState; + OnGameSaved onGameSaved; + + GSList* games; +} CursesBoardState; + +struct CursesBoardGlobals { + CommonGlobals cGlobals; + CursesBoardState* cbState; + CursesMenuState* menuState; /* null if we're not using menus */ + int refCount; + + TransportProcs procs; + + XP_Bool doDraw; + + union { + struct { + XWStreamCtxt* stream; /* how we can reach the server */ + } client; + struct { + int serverSocket; + XP_Bool socketOpen; + } server; + } csInfo; + XWGameState state; + XP_U16 nChatsSent; + XP_U16 nextQueryTimeSecs; + + WINDOW* boardWin; + int winWidth, winHeight; + + const MenuList* lastSubMenu; + + XP_Bool amServer; /* this process acting as server */ +}; + +static CursesBoardGlobals* findOrOpen( CursesBoardState* cbState, + sqlite3_int64 rowid ); +static void enableDraw( CursesBoardGlobals* bGlobals, + int width, int top, int height ); +static CursesBoardGlobals* ref( CursesBoardGlobals* bGlobals ); +static void unref( CursesBoardGlobals* bGlobals ); +static void setupBoard( CursesBoardGlobals* bGlobals ); + +CursesBoardState* +cb_init( CursesAppGlobals* aGlobals, LaunchParams* params, + CursesMenuState* menuState, OnGameSaved onGameSaved ) +{ + CursesBoardState* result = g_malloc0( sizeof(*result) ); + result->aGlobals = aGlobals; + result->params = params; + result->menuState = menuState; + result->onGameSaved = onGameSaved; + return result; +} + +void +cb_open( CursesBoardState* cbState, sqlite3_int64 rowid, + int width, int top, int height ) +{ + LOG_FUNC(); + CursesBoardGlobals* bGlobals = findOrOpen( cbState, rowid ); + enableDraw( bGlobals, width, top, height ); + setupBoard( bGlobals ); + + CommonGlobals* cGlobals = &bGlobals->cGlobals; + if ( !!cGlobals->game.comms ) { + comms_resendAll( cGlobals->game.comms, COMMS_CONN_NONE, XP_FALSE ); + } +} + +void +cb_new( CursesBoardState* cbState, int width, int top, int height ) +{ + CursesBoardGlobals* bGlobals = findOrOpen( cbState, -1 ); + enableDraw( bGlobals, width, top, height ); + setupBoard( bGlobals ); +} + +#ifdef KEYBOARD_NAV +static bool handleLeft( void* closure, int key ); +static bool handleRight( void* closure, int key ); +static bool handleUp( void* closure, int key ); +static bool handleDown( void* closure, int key ); +static bool handleClose( void* closure, int key ); +#endif +static bool handleAltLeft( void* closure, int key ); +static bool handleAltRight( void* closure, int key ); +static bool handleAltUp( void* closure, int key ); +static bool handleAltDown( void* closure, int key ); +static bool handleJuggle( void* closure, int key ); +static bool handleHide( void* closure, int key ); +/* static bool handleResend( void* closure, int key ); */ +static bool handleSpace( void* closure, int key ); +static bool handleRet( void* closure, int key ); +static bool handleHint( void* closure, int key ); +static bool handleCommit( void* closure, int key ); +static bool handleFlip( void* closure, int key ); +static bool handleToggleValues( void* closure, int key ); +static bool handleBackspace( void* closure, int key ); +static bool handleUndo( void* closure, int key ); +static bool handleReplace( void* closure, int key ); +#ifdef CURSES_SMALL_SCREEN +static bool handleRootKeyShow( void* closure, int key ); +static bool handleRootKeyHide( void* closure, int key ); +#endif + +static void relay_connd_curses( void* closure, XP_UCHAR* const room, + XP_Bool reconnect, XP_U16 devOrder, + XP_Bool allHere, XP_U16 nMissing ); +static void relay_status_curses( void* closure, CommsRelayState state ); +static void relay_error_curses( void* closure, XWREASON relayErr ); +static XP_Bool relay_sendNoConn_curses( const XP_U8* msg, XP_U16 len, + const XP_UCHAR* msgNo, + const XP_UCHAR* relayID, void* closure ); +static void curses_countChanged( void* closure, XP_U16 newCount ); +static XP_U32 curses_getFlags( void* closure ); +#ifdef RELAY_VIA_HTTP +static void relay_requestJoin_curses( void* closure, const XP_UCHAR* devID, + const XP_UCHAR* room, XP_U16 nPlayersHere, + XP_U16 nPlayersTotal, XP_U16 seed, XP_U16 lang ); +#endif + +const MenuList g_allMenuList[] = { + { handleLeft, "Left", "H", 'H' }, + { handleRight, "Right", "L", 'L' }, + { handleUp, "Up", "J", 'J' }, + { handleDown, "Down", "K", 'K' }, + { handleClose, "Close", "W", 'W' }, + { handleSpace, "Raise focus", "", ' ' }, + { handleRet, "Click/tap", "", '\r' }, +}; + +const MenuList g_boardMenuList[] = { + { handleAltLeft, "Force left", "{", '{' }, + { handleAltRight, "Force right", "}", '}' }, + { handleAltUp, "Force up", "_", '_' }, + { handleAltDown, "Force down", "+", '+' }, + { handleHint, "Hint", "?", '?' }, + + { handleCommit, "Commit move", "C", 'C' }, + { handleFlip, "Flip", "F", 'F' }, + { handleToggleValues, "Show values", "V", 'V' }, + + { handleBackspace, "Remove from board", "", 8 }, + { handleUndo, "Undo prev", "U", 'U' }, + { handleReplace, "uNdo cur", "N", 'N' }, + + { NULL, NULL, NULL, '\0'} +}; + +const MenuList g_trayMenuList[] = { + { handleJuggle, "Juggle", "G", 'G' }, + { handleHide, "[un]hIde", "I", 'I' }, + { handleAltLeft, "Divider left", "{", '{' }, + { handleAltRight, "Divider right", "}", '}' }, + + { NULL, NULL, NULL, '\0'} +}; + +const MenuList g_scoreMenuList[] = { +#ifdef KEYBOARD_NAV +#endif + { NULL, NULL, NULL, '\0'} +}; + +#ifdef KEYBOARD_NAV +static void +changeMenuForFocus( CursesBoardGlobals* bGlobals, BoardObjectType focussed ) +{ + const MenuList* subMenu = NULL; + if ( focussed == OBJ_TRAY ) { + subMenu = g_trayMenuList; + } else if ( focussed == OBJ_BOARD ) { + subMenu = g_boardMenuList; + } else if ( focussed == OBJ_SCORE ) { + subMenu = g_scoreMenuList; + } + + CursesMenuState* menuState = bGlobals->menuState; + if ( !!menuState ) { + cmenu_removeMenus( menuState, bGlobals->lastSubMenu, NULL ); + bGlobals->lastSubMenu = subMenu; + + cmenu_addMenus( menuState, bGlobals, subMenu, NULL ); + } + // drawMenuLargeOrSmall( globals->apg, menuList, globals ); +} /* changeMenuForFocus */ +#endif + +static void setupCursesUtilCallbacks( CursesBoardGlobals* bGlobals, XW_UtilCtxt* util ); + +static void initMenus( CursesBoardGlobals* bGlobals ) +{ + if ( !bGlobals->menuState ) { + bGlobals->menuState = bGlobals->cbState->menuState; + cmenu_push( bGlobals->menuState, bGlobals, g_allMenuList, NULL ); + } +} + +static void +onGameSaved( void* closure, sqlite3_int64 rowid, XP_Bool firstTime ) +{ + CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure; + CommonGlobals* cGlobals = &bGlobals->cGlobals; + /* onCursesGameSaved( bGlobals->aGlobals, rowid ); */ + + BoardCtxt* board = cGlobals->game.board; + board_invalAll( board ); + board_draw( board ); + /* May not be recorded */ + XP_ASSERT( cGlobals->rowid == rowid ); + // cGlobals->rowid = rowid; + + CursesBoardState* cbState = bGlobals->cbState; + (*cbState->onGameSaved)( cbState->aGlobals, rowid, firstTime ); +} + +static gboolean +fire_acceptor( GIOChannel* source, GIOCondition condition, gpointer data ) +{ + if ( 0 != (G_IO_IN & condition) ) { + CursesBoardGlobals* globals = (CursesBoardGlobals*)data; + + int fd = g_io_channel_unix_get_fd( source ); + XP_ASSERT( fd == globals->csInfo.server.serverSocket ); + (*globals->cGlobals.acceptor)( fd, globals ); + } + return TRUE; +} + +static void +curses_socket_acceptor( int listener, Acceptor func, CommonGlobals* cGlobals, + void** XP_UNUSED(storage) ) +{ + if ( -1 == listener ) { + XP_LOGF( "%s: removal of listener not implemented!!!!!", __func__ ); + } else { + CursesBoardGlobals* globals = (CursesBoardGlobals*)cGlobals; + XP_ASSERT( !cGlobals->acceptor || (func == cGlobals->acceptor) ); + cGlobals->acceptor = func; + globals->csInfo.server.serverSocket = listener; + ADD_SOCKET( globals, listener, fire_acceptor ); + } +} + +static void +copyParmsAddr( CommonGlobals* cGlobals ) +{ + LaunchParams* params = cGlobals->params; + CommsAddrRec* addr = &cGlobals->addr; + + CommsConnType typ; + for ( XP_U32 st = 0; addr_iter( ¶ms->addr, &typ, &st ); ) { + addr_addType( addr, typ ); + switch( typ ) { +#ifdef XWFEATURE_RELAY + case COMMS_CONN_RELAY: + addr->u.ip_relay.ipAddr = 0; /* ??? */ + addr->u.ip_relay.port = params->connInfo.relay.defaultSendPort; + addr->u.ip_relay.seeksPublicRoom = + params->connInfo.relay.seeksPublicRoom; + addr->u.ip_relay.advertiseRoom = params->connInfo.relay.advertiseRoom; + XP_STRNCPY( addr->u.ip_relay.hostName, + params->connInfo.relay.relayName, + sizeof(addr->u.ip_relay.hostName) - 1 ); + XP_STRNCPY( addr->u.ip_relay.invite, params->connInfo.relay.invite, + sizeof(addr->u.ip_relay.invite) - 1 ); + break; +#endif +#ifdef XWFEATURE_SMS + case COMMS_CONN_SMS: + XP_STRNCPY( addr->u.sms.phone, params->connInfo.sms.myPhone, + sizeof(addr->u.sms.phone) - 1 ); + addr->u.sms.port = params->connInfo.sms.port; + break; +#endif +#ifdef XWFEATURE_BLUETOOTH + case COMMS_CONN_BT: + XP_ASSERT( sizeof(addr->u.bt.btAddr) + >= sizeof(params->connInfo.bt.hostAddr)); + XP_MEMCPY( &addr->u.bt.btAddr, ¶ms->connInfo.bt.hostAddr, + sizeof(params->connInfo.bt.hostAddr) ); + break; +#endif + default: + break; + } + } +} + +static CursesBoardGlobals* +commonInit( CursesBoardState* cbState, sqlite3_int64 rowid ) +{ + CursesBoardGlobals* bGlobals = g_malloc0( sizeof(*bGlobals) ); + XP_LOGF( "%s(): alloc'd bGlobals %p", __func__, bGlobals ); + CommonGlobals* cGlobals = &bGlobals->cGlobals; + LaunchParams* params = cbState->params; + + cGlobals->gi = &cGlobals->_gi; + gi_copy( MPPARM(params->mpool) cGlobals->gi, ¶ms->pgi ); + + cGlobals->rowid = rowid; + bGlobals->cbState = cbState; + cGlobals->params = params; + setupUtil( cGlobals ); + setupCursesUtilCallbacks( bGlobals, cGlobals->util ); + + cGlobals->socketAddedClosure = bGlobals; + cGlobals->onSave = onGameSaved; + cGlobals->onSaveClosure = bGlobals; + cGlobals->addAcceptor = curses_socket_acceptor; + + bGlobals->procs.closure = cGlobals; + bGlobals->procs.send = LINUX_SEND; +#ifdef COMMS_HEARTBEAT + bGlobals->procs.reset = linux_reset; +#endif + bGlobals->procs.rstatus = relay_status_curses; + bGlobals->procs.rconnd = relay_connd_curses; + bGlobals->procs.rerror = relay_error_curses; + bGlobals->procs.sendNoConn = relay_sendNoConn_curses; + bGlobals->procs.countChanged = curses_countChanged; + bGlobals->procs.getFlags = curses_getFlags; +# ifdef RELAY_VIA_HTTP + bGlobals->procs.requestJoin = relay_requestJoin_curses; +#endif + + copyParmsAddr( cGlobals ); + + CurGameInfo* gi = cGlobals->gi; + if ( !!gi ) { + XP_ASSERT( !cGlobals->dict ); + cGlobals->dict = linux_dictionary_make( MPPARM(cGlobals->util->mpool) params, + gi->dictName, XP_TRUE ); + gi->dictLang = dict_getLangCode( cGlobals->dict ); + } + + setOneSecondTimer( cGlobals ); + return bGlobals; +} /* commonInit */ + +static void +disposeBoard( CursesBoardGlobals* bGlobals ) +{ + XP_LOGF( "%s(): passed bGlobals %p", __func__, bGlobals ); + /* XP_ASSERT( 0 == bGlobals->refCount ); */ + CommonGlobals* cGlobals = &bGlobals->cGlobals; + if ( !!bGlobals->boardWin ) { + cursesDrawCtxtFree( cGlobals->draw ); + wclear( bGlobals->boardWin ); + wrefresh( bGlobals->boardWin ); + delwin( bGlobals->boardWin ); + } + + if ( !!bGlobals->cbState->menuState ) { + cmenu_pop( bGlobals->cbState->menuState ); + } + + clearOneSecondTimer( cGlobals ); + + gi_disposePlayerInfo( MPPARM(cGlobals->util->mpool) cGlobals->gi ); + game_dispose( &cGlobals->game ); + + if ( !!cGlobals->dict ) { + dict_unref( cGlobals->dict ); + } + + disposeUtil( cGlobals ); + + CursesBoardState* cbState = bGlobals->cbState; + cbState->games = g_slist_remove( cbState->games, bGlobals ); + + /* onCursesBoardClosing( bGlobals->aGlobals, bGlobals ); */ + g_free( bGlobals ); +} + +static CursesBoardGlobals* +ref( CursesBoardGlobals* bGlobals ) +{ + ++bGlobals->refCount; + XP_LOGF( "%s(): refCount now %d", __func__, bGlobals->refCount ); + return bGlobals; +} + +static void +unref( CursesBoardGlobals* bGlobals ) +{ + --bGlobals->refCount; + XP_LOGF( "%s(): refCount now %d", __func__, bGlobals->refCount ); + if ( 0 == bGlobals->refCount ) { + disposeBoard( bGlobals ); + } +} + +static int +utf8_len( const char* face ) +{ + const int max = strlen(face); + int count = 0; + mbstate_t ps = {0}; + for ( int offset = 0; offset < max; ) { + size_t nBytes = mbrlen( &face[offset], max - offset, &ps ); + if ( 0 < nBytes ) { + ++count; + offset += nBytes; + } else { + break; + } + } + return count; +} + +static void +getFromDict( const CommonGlobals* cGlobals, XP_U16* fontWidthP, + XP_U16* fontHtP ) +{ + int maxLen = 0; + + DictionaryCtxt* dict = cGlobals->dict; + for ( Tile tile = 0; tile < dict->nFaces; ++tile ) { + const XP_UCHAR* face = dict_getTileString( dict, tile ); + int thisLen = utf8_len( face ); + /* XP_LOGF( "%s(): looking at face '%s' with len %d", __func__, */ + /* face, thisLen ); */ + if ( thisLen > maxLen ) { + maxLen = thisLen; + } + } + + /* XP_LOGF( "%s(): width = %d", __func__, maxLen ); */ + *fontWidthP = maxLen; + *fontHtP = 1; +} + +static void +setupBoard( CursesBoardGlobals* bGlobals ) +{ + LOG_FUNC(); + /* positionSizeStuff( bGlobals ); */ + CommonGlobals* cGlobals = &bGlobals->cGlobals; + BoardCtxt* board = cGlobals->game.board; + const int width = bGlobals->winWidth; + const int height = bGlobals->winHeight; + + XP_U16 fontWidth, fontHt; + getFromDict( cGlobals, &fontWidth, &fontHt ); + BoardDims dims; + board_figureLayout( board, cGlobals->gi, + 0, 0, width, height, 100, + 150, 200, /* percents */ + width*75/100, + fontWidth, fontHt, + XP_FALSE, &dims ); + board_applyLayout( board, &dims ); + XP_LOGF( "%s(): calling board_draw()", __func__ ); + board_draw( board ); +} + +static CursesBoardGlobals* +initNoDraw( CursesBoardState* cbState, sqlite3_int64 rowid ) +{ + LOG_FUNC(); + CursesBoardGlobals* bGlobals = commonInit( cbState, rowid ); + CommonGlobals* cGlobals = &bGlobals->cGlobals; + if ( -1 == rowid ) { + cGlobals->rowid = -1; + } + linuxOpenGame( cGlobals, &bGlobals->procs ); + return ref( bGlobals ); +} + +static void +enableDraw( CursesBoardGlobals* bGlobals, int width, int top, int height ) +{ + LOG_FUNC(); + XP_ASSERT( 0 != width ); + bGlobals->boardWin = newwin( height, width, top, 0 ); + getmaxyx( bGlobals->boardWin, bGlobals->winHeight, bGlobals->winWidth ); + + CommonGlobals* cGlobals = &bGlobals->cGlobals; + cGlobals->draw = cursesDrawCtxtMake( bGlobals->boardWin ); + board_setDraw( cGlobals->game.board, cGlobals->draw ); + + setupBoard( bGlobals ); + + initMenus( bGlobals ); +} + +static CursesBoardGlobals* +findOrOpen( CursesBoardState* cbState, sqlite3_int64 rowid ) +{ + CursesBoardGlobals* result = NULL; + for ( GSList* iter = cbState->games; + rowid != 0 && !!iter && !result; iter = iter->next ) { + CursesBoardGlobals* one = (CursesBoardGlobals*)iter->data; + if ( one->cGlobals.rowid == rowid ) { + result = one; + } + } + + if ( !result ) { + result = initNoDraw( cbState, rowid ); + setupBoard( result ); + cbState->games = g_slist_append( cbState->games, result ); + } + return result; +} + +XP_U16 +cb_feedBuffer( CursesBoardState* cbState, sqlite3_int64 rowid, + const XP_U8* buf, XP_U16 len, const CommsAddrRec* from ) +{ + CursesBoardGlobals* bGlobals = findOrOpen( cbState, rowid ); + CommonGlobals* cGlobals = &bGlobals->cGlobals; + gameGotBuf( cGlobals, XP_TRUE, buf, len, from ); + XP_U16 seed = comms_getChannelSeed( cGlobals->game.comms ); + return seed; +} + +static void +kill_board( gpointer data ) +{ + CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)data; + disposeBoard( bGlobals ); +} + +void +cb_closeAll( CursesBoardState* cbState ) +{ + g_slist_free_full( cbState->games, kill_board ); +} + +static void +cursesUserError( CursesBoardGlobals* bGlobals, const char* format, ... ) +{ + char buf[512]; + va_list ap; + va_start( ap, format ); + + vsprintf( buf, format, ap ); + + const char* buttons[] = {"OK"}; + (void)cursesask( bGlobals->boardWin, buf, VSIZE(buttons), buttons ); + + va_end(ap); +} /* cursesUserError */ + +static void +curses_util_notifyPickTileBlank( XW_UtilCtxt* uc, XP_U16 playerNum, + XP_U16 XP_UNUSED(col), XP_U16 XP_UNUSED(row), + const XP_UCHAR** texts, XP_U16 nTiles ) +{ + CursesBoardGlobals* globals = (CursesBoardGlobals*)uc->closure; + char query[128]; + char* playerName = globals->cGlobals.gi->players[playerNum].name; + + snprintf( query, sizeof(query), + "Pick tile for %s! (Tab or type letter to select " + "then hit .)", playerName ); + + /*index = */curses_askLetter( globals->boardWin, query, texts, nTiles ); + // return index; +} /* util_userPickTile */ + +static void +curses_util_informNeedPickTiles( XW_UtilCtxt* XP_UNUSED(uc), + XP_Bool XP_UNUSED(isInitial), + XP_U16 XP_UNUSED(player), + XP_U16 XP_UNUSED(nToPick), + XP_U16 XP_UNUSED(nFaces), + const XP_UCHAR** XP_UNUSED(faces), + const XP_U16* XP_UNUSED(counts) ) +{ + /* CursesBoardGlobals* globals = (CursesBoardGlobals*)uc->closure; */ + /* char query[128]; */ + /* XP_S16 index; */ + /* char* playerName = globals->cGlobals.gi->players[playerNum].name; */ + + /* snprintf( query, sizeof(query), */ + /* "Pick tile for %s! (Tab or type letter to select " */ + /* "then hit .)", playerName ); */ + + /* index = curses_askLetter( globals, query, texts, nTiles ); */ + /* return index; */ +} + +static void +curses_util_userError( XW_UtilCtxt* uc, UtilErrID id ) +{ + CursesBoardGlobals* globals = (CursesBoardGlobals*)uc->closure; + XP_Bool silent; + const XP_UCHAR* message = linux_getErrString( id, &silent ); + + if ( silent ) { + XP_LOGF( "silent userError: %s", message ); + } else { + cursesUserError( globals, message ); + } +} /* curses_util_userError */ + +static gint +ask_move( gpointer data ) +{ + CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)data; + CommonGlobals* cGlobals = &bGlobals->cGlobals; + const char* answers[] = {"Ok", "Cancel", NULL}; + + if (0 == cursesask(bGlobals->boardWin, cGlobals->question, + VSIZE(answers)-1, answers) ) { + BoardCtxt* board = cGlobals->game.board; + if ( board_commitTurn( board, XP_TRUE, XP_TRUE, NULL ) ) { + board_draw( board ); + linuxSaveGame( &bGlobals->cGlobals ); + } + } + + return FALSE; +} + +static void +curses_util_informNeedPassword( XW_UtilCtxt* XP_UNUSED(uc), + XP_U16 XP_UNUSED_DBG(playerNum), + const XP_UCHAR* XP_UNUSED_DBG(name) ) +{ + XP_WARNF( "curses_util_informNeedPassword(num=%d, name=%s", playerNum, name ); +} /* curses_util_askPassword */ + +static void +curses_util_yOffsetChange( XW_UtilCtxt* XP_UNUSED(uc), + XP_U16 XP_UNUSED(maxOffset), + XP_U16 XP_UNUSED(oldOffset), XP_U16 XP_UNUSED(newOffset) ) +{ + /* if ( oldOffset != newOffset ) { */ + /* XP_WARNF( "curses_util_yOffsetChange(%d,%d,%d) not implemented", */ + /* maxOffset, oldOffset, newOffset ); */ + /* } */ +} /* curses_util_yOffsetChange */ + +#ifdef XWFEATURE_TURNCHANGENOTIFY +static void +curses_util_turnChanged( XW_UtilCtxt* XP_UNUSED(uc), XP_S16 XP_UNUSED_DBG(newTurn) ) +{ + XP_LOGF( "%s(turn=%d)", __func__, newTurn ); +} +#endif + +static void +curses_util_notifyIllegalWords( XW_UtilCtxt* XP_UNUSED(uc), + BadWordInfo* XP_UNUSED(bwi), + XP_U16 XP_UNUSED(player), + XP_Bool XP_UNUSED(turnLost) ) +{ + XP_WARNF( "curses_util_notifyIllegalWords not implemented" ); +} /* curses_util_notifyIllegalWord */ + +/* this needs to change!!! */ +static void +curses_util_notifyMove( XW_UtilCtxt* uc, XWStreamCtxt* stream ) +{ + CursesBoardGlobals* globals = (CursesBoardGlobals*)uc->closure; + CommonGlobals* cGlobals = &globals->cGlobals; + XP_U16 len = stream_getSize( stream ); + XP_ASSERT( len <= VSIZE(cGlobals->question) ); + stream_getBytes( stream, cGlobals->question, len ); + (void)ADD_ONETIME_IDLE( ask_move, globals ); +} /* curses_util_not */ + +static gint +ask_trade( gpointer data ) +{ + CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)data; + CommonGlobals* cGlobals = &bGlobals->cGlobals; + + const char* buttons[] = { "Ok", "Cancel" }; + if (0 == cursesask( bGlobals->boardWin, cGlobals->question, + VSIZE(buttons), buttons ) ) { + BoardCtxt* board = cGlobals->game.board; + if ( board_commitTurn( board, XP_TRUE, XP_TRUE, NULL ) ) { + board_draw( board ); + linuxSaveGame( cGlobals ); + } + } + return FALSE; +} + +static void +curses_util_notifyTrade( XW_UtilCtxt* uc, const XP_UCHAR** tiles, XP_U16 nTiles ) +{ + CursesBoardGlobals* globals = (CursesBoardGlobals*)uc->closure; + formatConfirmTrade( &globals->cGlobals, tiles, nTiles ); + (void)ADD_ONETIME_IDLE( ask_trade, globals ); +} + +static void +curses_util_trayHiddenChange( XW_UtilCtxt* XP_UNUSED(uc), + XW_TrayVisState XP_UNUSED(state), + XP_U16 XP_UNUSED(nVisibleRows) ) +{ + /* nothing to do if we don't have a scrollbar */ +} /* curses_util_trayHiddenChange */ + +static void +cursesShowFinalScores( CursesBoardGlobals* bGlobals ) +{ + XWStreamCtxt* stream; + XP_UCHAR* text; + + CommonGlobals* cGlobals = &bGlobals->cGlobals; + stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool) + cGlobals->params->vtMgr ); + server_writeFinalScores( cGlobals->game.server, stream ); + + text = strFromStream( stream ); + + const char* buttons[] = { "Ok" }; + (void)cursesask( bGlobals->boardWin, text, VSIZE(buttons), buttons ); + + free( text ); + stream_destroy( stream ); +} /* cursesShowFinalScores */ + +static void +curses_util_informMove( XW_UtilCtxt* uc, XP_S16 XP_UNUSED(turn), + XWStreamCtxt* expl, XWStreamCtxt* XP_UNUSED(words)) +{ + CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)uc->closure; + char* question = strFromStream( expl ); + const char* buttons[] = { "Ok" }; + (void)cursesask( bGlobals->boardWin, question, VSIZE(buttons), buttons ); + free( question ); +} + +static void +curses_util_informUndo( XW_UtilCtxt* XP_UNUSED(uc)) +{ + LOG_FUNC(); +} + +static void +curses_util_notifyGameOver( XW_UtilCtxt* uc, XP_S16 quitter ) +{ + CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)uc->closure; + CommonGlobals* cGlobals = &bGlobals->cGlobals; + LaunchParams* params = cGlobals->params; + board_draw( cGlobals->game.board ); + + /* game belongs in cGlobals... */ + if ( params->printHistory ) { + catGameHistory( cGlobals ); + } + + catFinalScores( cGlobals, quitter ); + + if ( params->quitAfter >= 0 ) { + sleep( params->quitAfter ); + handleQuit( bGlobals->cbState->aGlobals, 0 ); + } else if ( params->undoWhenDone ) { + server_handleUndo( cGlobals->game.server, 0 ); + } else if ( !params->skipGameOver && !!bGlobals->boardWin ) { + /* This is modal. Don't show if quitting */ + cursesShowFinalScores( bGlobals ); + } +} /* curses_util_notifyGameOver */ + +static void +curses_util_informNetDict( XW_UtilCtxt* uc, XP_LangCode XP_UNUSED(lang), + const XP_UCHAR* XP_UNUSED_DBG(oldName), + const XP_UCHAR* XP_UNUSED_DBG(newName), + const XP_UCHAR* XP_UNUSED_DBG(newSum), + XWPhoniesChoice phoniesAction ) +{ + XP_USE(uc); + XP_USE(phoniesAction); + XP_LOGF( "%s: %s => %s (cksum: %s)", __func__, oldName, newName, newSum ); +} + +#ifdef XWFEATURE_SEARCHLIMIT +static XP_Bool +curses_util_getTraySearchLimits( XW_UtilCtxt* XP_UNUSED(uc), + XP_U16* XP_UNUSED(min), XP_U16* XP_UNUSED(max) ) +{ + XP_ASSERT(0); + return XP_TRUE; +} +#endif + + +#ifdef XWFEATURE_HILITECELL +static XP_Bool +curses_util_hiliteCell( XW_UtilCtxt* uc, + XP_U16 XP_UNUSED(col), XP_U16 XP_UNUSED(row) ) +{ + CursesBoardGlobals* globals = (CursesBoardGlobals*)uc->closure; + if ( globals->cGlobals.params->sleepOnAnchor ) { + usleep( 10000 ); + } + return XP_TRUE; +} /* curses_util_hiliteCell */ +#endif + +static XP_Bool +curses_util_engineProgressCallback( XW_UtilCtxt* XP_UNUSED(uc) ) +{ + return XP_TRUE; +} /* curses_util_engineProgressCallback */ + +#ifdef USE_GLIBLOOP +static gboolean +timerFired( gpointer data ) +{ + TimerInfo* ti = (TimerInfo*)data; + CommonGlobals* globals = ti->globals; + XWTimerReason why = ti - globals->timerInfo; + if ( linuxFireTimer( globals, why ) ) { + board_draw( globals->game.board ); + } + + return FALSE; +} +#endif + +static void +curses_util_setTimer( XW_UtilCtxt* uc, XWTimerReason why, XP_U16 when, + XWTimerProc proc, void* closure ) +{ + CursesBoardGlobals* globals = (CursesBoardGlobals*)uc->closure; + TimerInfo* ti = &globals->cGlobals.timerInfo[why]; + + ti->proc = proc; + ti->closure = closure; + +#ifdef USE_GLIBLOOP + ti->globals = &globals->cGlobals; + (void)g_timeout_add_seconds( when, timerFired, ti ); +#else + ti->when = util_getCurSeconds(uc) + when; +#endif +} /* curses_util_setTimer */ + +static void +curses_util_clearTimer( XW_UtilCtxt* uc, XWTimerReason why ) +{ + CursesBoardGlobals* globals = (CursesBoardGlobals*)uc->closure; + globals->cGlobals.timerInfo[why].proc = NULL; +} + + +#ifdef USE_GLIBLOOP +static gboolean +onetime_idle( gpointer data ) +{ + CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)data; + XP_LOGF( "%s(): passed bGlobals %p", __func__, bGlobals ); + XWGame* game = &bGlobals->cGlobals.game; + if ( !!game->server && server_do( game->server ) ) { + if ( !!game->board ) { + board_draw( game->board ); + } + linuxSaveGame( &bGlobals->cGlobals ); + } + return FALSE; +} +#endif + +static void +curses_util_requestTime( XW_UtilCtxt* uc ) +{ + CursesBoardGlobals* globals = (CursesBoardGlobals*)uc->closure; +#if 1 + (void)ADD_ONETIME_IDLE( onetime_idle, globals ); +#else + (void)g_timeout_add( 1,// interval, + onetime_idle, globals ); +#endif +} /* curses_util_requestTime */ + +static XP_Bool +curses_util_altKeyDown( XW_UtilCtxt* XP_UNUSED(uc) ) +{ + return XP_FALSE; +} + +static void +curses_util_remSelected( XW_UtilCtxt* uc ) +{ + CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)uc->closure; + CommonGlobals* cGlobals = (CommonGlobals*)uc->closure; + XWStreamCtxt* stream; + XP_UCHAR* text; + + stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool) + cGlobals->params->vtMgr ); + board_formatRemainingTiles( cGlobals->game.board, stream ); + + text = strFromStream( stream ); + + const char* buttons[] = { "Ok" }; + (void)cursesask( bGlobals->boardWin, text, VSIZE(buttons), buttons ); + + free( text ); +} + +static void +curses_util_bonusSquareHeld( XW_UtilCtxt* uc, XWBonusType bonus ) +{ + LOG_FUNC(); + XP_USE( uc ); + XP_USE( bonus ); +} + +static void +curses_util_playerScoreHeld( XW_UtilCtxt* uc, XP_U16 player ) +{ + CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)uc->closure; + LastMoveInfo lmi; + if ( model_getPlayersLastScore( bGlobals->cGlobals.game.model, + player, &lmi ) ) { + XP_UCHAR buf[128]; + formatLMI( &lmi, buf, VSIZE(buf) ); + const char* buttons[] = { "Ok" }; + (void)cursesask( bGlobals->boardWin, buf, VSIZE(buttons), buttons ); + } +} + +#ifdef XWFEATURE_BOARDWORDS +static void +curses_util_cellSquareHeld( XW_UtilCtxt* uc, XWStreamCtxt* words ) +{ + XP_USE( uc ); + catOnClose( words, NULL ); + fprintf( stderr, "\n" ); +} +#endif + + +#ifndef XWFEATURE_STANDALONE_ONLY +static XWStreamCtxt* +curses_util_makeStreamFromAddr(XW_UtilCtxt* uc, XP_PlayerAddr channelNo ) +{ + CursesBoardGlobals* globals = (CursesBoardGlobals*)uc->closure; + LaunchParams* params = globals->cGlobals.params; + + XWStreamCtxt* stream = mem_stream_make( MPPARM(uc->mpool) params->vtMgr, + &globals->cGlobals, channelNo, + sendOnClose ); + return stream; +} /* curses_util_makeStreamFromAddr */ +#endif + +#ifdef XWFEATURE_CHAT +static void +curses_util_showChat( XW_UtilCtxt* uc, + const XP_UCHAR* const XP_UNUSED_DBG(msg), + XP_S16 XP_UNUSED_DBG(from), XP_U32 XP_UNUSED(timestamp) ) +{ + CursesBoardGlobals* globals = (CursesBoardGlobals*)uc->closure; + globals->nChatsSent = 0; +# ifdef DEBUG + const XP_UCHAR* name = ""; + if ( 0 <= from ) { + CommonGlobals* cGlobals = &globals->cGlobals; + name = cGlobals->gi->players[from].name; + } + XP_LOGF( "%s: got \"%s\" from %s", __func__, msg, name ); +# endif +} +#endif + +static void +setupCursesUtilCallbacks( CursesBoardGlobals* bGlobals, XW_UtilCtxt* util ) +{ + util->closure = bGlobals; +#define SET_PROC(NAM) util->vtable->m_util_##NAM = curses_util_##NAM + SET_PROC(makeStreamFromAddr); + SET_PROC(userError); + SET_PROC(notifyMove); + SET_PROC(notifyTrade); + SET_PROC(notifyPickTileBlank); + SET_PROC(informNeedPickTiles); + SET_PROC(informNeedPassword); + SET_PROC(trayHiddenChange); + SET_PROC(yOffsetChange); +#ifdef XWFEATURE_TURNCHANGENOTIFY + SET_PROC(turnChanged); +#endif + SET_PROC(informMove); + SET_PROC(informUndo); + SET_PROC(informNetDict); + SET_PROC(notifyGameOver); +#ifdef XWFEATURE_HILITECELL + SET_PROC(hiliteCell); +#endif + SET_PROC(engineProgressCallback); + SET_PROC(setTimer); + SET_PROC(clearTimer); + SET_PROC(requestTime); + SET_PROC(altKeyDown); /* ?? */ + SET_PROC(notifyIllegalWords); + SET_PROC(remSelected); +#ifndef XWFEATURE_MINIWIN + SET_PROC(bonusSquareHeld); + SET_PROC(playerScoreHeld); +#endif +#ifdef XWFEATURE_BOARDWORDS + SET_PROC(cellSquareHeld); +#endif + +#ifdef XWFEATURE_SEARCHLIMIT + SET_PROC(getTraySearchLimits); +#endif +#ifdef XWFEATURE_CHAT + SET_PROC(showChat); +#endif +#undef SET_PROC + + assertAllCallbacksSet( util ); +} /* setupCursesUtilCallbacks */ + +static bool +handleFlip( void* closure, int XP_UNUSED(key) ) +{ + CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure; + bGlobals->doDraw = board_flip( bGlobals->cGlobals.game.board ); + return XP_TRUE; +} /* handleFlip */ + +static bool +handleToggleValues( void* closure, int XP_UNUSED(key) ) +{ + CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure; + bGlobals->doDraw = board_toggle_showValues( bGlobals->cGlobals.game.board ); + return XP_TRUE; +} /* handleToggleValues */ + +static bool +handleBackspace( void* closure, int XP_UNUSED(key) ) +{ + XP_Bool handled; + CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure; + bGlobals->doDraw = board_handleKey( bGlobals->cGlobals.game.board, + XP_CURSOR_KEY_DEL, &handled ); + return XP_TRUE; +} /* handleBackspace */ + +static bool +handleUndo( void* closure, int XP_UNUSED(key) ) +{ + CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure; + bGlobals->doDraw = server_handleUndo( bGlobals->cGlobals.game.server, 0 ); + return XP_TRUE; +} /* handleUndo */ + +static bool +handleReplace( void* closure, int XP_UNUSED(key) ) +{ + CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure; + bGlobals->doDraw = board_replaceTiles( bGlobals->cGlobals.game.board ); + return XP_TRUE; +} /* handleReplace */ + +static bool +handleCommit( void* closure, int XP_UNUSED(key) ) +{ + CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure; + bGlobals->doDraw = board_commitTurn( bGlobals->cGlobals.game.board, + XP_FALSE, XP_FALSE, NULL ); + return XP_TRUE; +} /* handleCommit */ + +static bool +handleJuggle( void* closure, int XP_UNUSED(key) ) +{ + CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure; + bGlobals->doDraw = board_juggleTray( bGlobals->cGlobals.game.board ); + return XP_TRUE; +} /* handleJuggle */ + +static bool +handleHide( void* closure, int XP_UNUSED(key) ) +{ + CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure; + CommonGlobals* cGlobals = &bGlobals->cGlobals; + XW_TrayVisState curState = + board_getTrayVisState( cGlobals->game.board ); + + if ( curState == TRAY_REVEALED ) { + bGlobals->doDraw = board_hideTray( cGlobals->game.board ); + } else { + bGlobals->doDraw = board_showTray( cGlobals->game.board ); + } + + return XP_TRUE; +} /* handleJuggle */ + +#ifdef KEYBOARD_NAV +static void +checkAssignFocus( BoardCtxt* board ) +{ + if ( OBJ_NONE == board_getFocusOwner(board) ) { + board_focusChanged( board, OBJ_BOARD, XP_TRUE ); + } +} + +static XP_Bool +handleFocusKey( CursesBoardGlobals* bGlobals, XP_Key key ) +{ + CommonGlobals* cGlobals = &bGlobals->cGlobals; + checkAssignFocus( cGlobals->game.board ); + + XP_Bool handled; + XP_Bool draw = board_handleKey( cGlobals->game.board, key, &handled ); + if ( !handled ) { + BoardObjectType nxt; + BoardObjectType order[] = { OBJ_BOARD, OBJ_SCORE, OBJ_TRAY }; + draw = linShiftFocus( cGlobals, key, order, &nxt ) || draw; + if ( nxt != OBJ_NONE ) { + changeMenuForFocus( bGlobals, nxt ); + } + } + + if ( draw ) { + board_draw( cGlobals->game.board ); + } + return XP_TRUE; +} /* handleFocusKey */ + +static bool +handleAltLeft( void* closure, int XP_UNUSED(key) ) +{ + CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure; + return handleFocusKey( bGlobals, XP_CURSOR_KEY_ALTLEFT ); +} + +static bool +handleAltRight( void* closure, int XP_UNUSED(key) ) +{ + CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure; + return handleFocusKey( bGlobals, XP_CURSOR_KEY_ALTRIGHT ); +} + +static bool +handleAltUp( void* closure, int XP_UNUSED(key) ) +{ + CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure; + return handleFocusKey( bGlobals, XP_CURSOR_KEY_ALTUP ); +} + +static bool +handleAltDown( void* closure, int XP_UNUSED(key) ) +{ + CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure; + return handleFocusKey( bGlobals, XP_CURSOR_KEY_ALTDOWN ); +} + +static bool +handleHint( void* closure, int XP_UNUSED(key) ) +{ + CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure; + CommonGlobals* cGlobals = &bGlobals->cGlobals; + XP_Bool redo; + XP_Bool draw = board_requestHint( cGlobals->game.board, + #ifdef XWFEATURE_SEARCHLIMIT + XP_FALSE, + #endif + XP_FALSE, &redo ); + if ( draw ) { + board_draw( cGlobals->game.board ); + } + return XP_TRUE; +} + +static bool +handleLeft( void* closure, int XP_UNUSED(key) ) +{ + CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure; + return handleFocusKey( bGlobals, XP_CURSOR_KEY_LEFT ); +} /* handleLeft */ + +static bool +handleRight( void* closure, int XP_UNUSED(key) ) +{ + CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure; + return handleFocusKey( bGlobals, XP_CURSOR_KEY_RIGHT ); +} /* handleRight */ + +static bool +handleUp( void* closure, int XP_UNUSED(key) ) +{ + CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure; + return handleFocusKey( bGlobals, XP_CURSOR_KEY_UP ); +} /* handleUp */ + +static bool +handleDown( void* closure, int XP_UNUSED(key) ) +{ + CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure; + return handleFocusKey( bGlobals, XP_CURSOR_KEY_DOWN ); +} /* handleDown */ + +static gboolean +idle_close_game( gpointer data ) +{ + CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)data; + linuxSaveGame( &bGlobals->cGlobals ); + unref( bGlobals ); + return FALSE; +} + +static bool +handleClose( void* closure, int XP_UNUSED(key) ) +{ + CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure; + (void)ADD_ONETIME_IDLE( idle_close_game, bGlobals ); + return XP_TRUE; +} /* handleDown */ + +static bool +handleSpace( void* closure, int XP_UNUSED(key) ) +{ + CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure; + BoardCtxt* board = bGlobals->cGlobals.game.board; + checkAssignFocus( board ); + XP_Bool handled; + (void)board_handleKey( board, XP_RAISEFOCUS_KEY, &handled ); + board_draw( board ); + return XP_TRUE; +} /* handleSpace */ + +static bool +handleRet( void* closure, int XP_UNUSED(key) ) +{ + CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)closure; + BoardCtxt* board = bGlobals->cGlobals.game.board; + XP_Bool handled; + (void)board_handleKey( board, XP_RETURN_KEY, &handled ); + board_draw( board ); + return XP_TRUE; +} /* handleRet */ + +#endif + +static void +relay_connd_curses( void* XP_UNUSED(closure), XP_UCHAR* const XP_UNUSED(room), + XP_Bool XP_UNUSED(reconnect), XP_U16 XP_UNUSED(devOrder), + XP_Bool XP_UNUSED_DBG(allHere), + XP_U16 XP_UNUSED_DBG(nMissing) ) +{ + XP_LOGF( "%s got allHere: %s; nMissing: %d", __func__, + allHere?"true":"false", nMissing ); +} + +static void +relay_error_curses( void* XP_UNUSED(closure), XWREASON XP_UNUSED_DBG(relayErr) ) +{ +#ifdef DEBUG + XP_LOGF( "%s(%s)", __func__, XWREASON2Str( relayErr ) ); +#endif +} + +static XP_Bool +relay_sendNoConn_curses( const XP_U8* msg, XP_U16 len, + const XP_UCHAR* XP_UNUSED(msgNo), + const XP_UCHAR* relayID, void* closure ) +{ + XP_Bool success = XP_FALSE; + CommonGlobals* cGlobals = (CommonGlobals*)closure; + LaunchParams* params = cGlobals->params; + if ( params->useUdp /*&& !cGlobals->draw*/ ) { + XP_U16 seed = comms_getChannelSeed( cGlobals->game.comms ); + XP_U32 clientToken = makeClientToken( cGlobals->rowid, seed ); + XP_S16 nSent = relaycon_sendnoconn( params, msg, len, relayID, + clientToken ); + success = nSent == len; + } + return success; + +} /* relay_sendNoConn_curses */ + +#ifdef RELAY_VIA_HTTP +static void +relay_requestJoin_curses( void* closure, const XP_UCHAR* devID, const XP_UCHAR* room, + XP_U16 nPlayersHere, XP_U16 nPlayersTotal, + XP_U16 seed, XP_U16 lang ) +{ + CommonGlobals* cGlobals = (CommonGlobals*)closure; + relaycon_join( cGlobals->params, devID, room, nPlayersHere, nPlayersTotal, + seed, lang, onJoined, globals ); +} +#endif + +static void +curses_countChanged( void* XP_UNUSED(closure), XP_U16 XP_UNUSED_DBG(newCount) ) +{ + XP_LOGF( "%s(newCount=%d)", __func__, newCount ); +} + +#ifdef COMMS_XPORT_FLAGSPROC +static XP_U32 +curses_getFlags( void* XP_UNUSED(closure) ) +{ + return COMMS_XPORT_FLAGS_HASNOCONN; +} +#endif + +static void +relay_status_curses( void* XP_UNUSED(closure), CommsRelayState XP_UNUSED_DBG(state) ) +{ + /* CommonGlobals* cGlobals = (CommonGlobals*)closure; */ + // bGlobals->commsRelayState = state; + XP_LOGF( "%s got status: %s", __func__, CommsRelayState2Str(state) ); +} diff --git a/xwords4/linux/cursesboard.h b/xwords4/linux/cursesboard.h new file mode 100644 index 000000000..0556a2f5c --- /dev/null +++ b/xwords4/linux/cursesboard.h @@ -0,0 +1,42 @@ +/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ +/* + * Copyright 2000 - 2020 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _CURSESBOARD_H_ +#define _CURSESBOARD_H_ + +#include "cursesmain.h" + +typedef struct CursesAppGlobals CursesAppGlobals; +typedef struct CursesBoardState CursesBoardState; + +typedef void (*OnGameSaved)( CursesAppGlobals* aGlobals, sqlite3_int64 rowid, bool isNew ); + +CursesBoardState* cb_init( CursesAppGlobals* aGlobals, LaunchParams* params, + CursesMenuState* menuState, OnGameSaved onGameSaved ); + +void cb_open( CursesBoardState* cbState, sqlite3_int64 rowid, + int width, int top, int height ); +void cb_new( CursesBoardState* cbState, int width, int top, int height ); + +XP_U16 cb_feedBuffer( CursesBoardState* cbState, sqlite3_int64 rowid, + const XP_U8* buf, XP_U16 len, const CommsAddrRec* from ); +void cb_closeAll( CursesBoardState* cbState ); + +#endif diff --git a/xwords4/linux/cursesdraw.c b/xwords4/linux/cursesdraw.c index 66f2c14d7..9ab8456ad 100644 --- a/xwords4/linux/cursesdraw.c +++ b/xwords4/linux/cursesdraw.c @@ -646,4 +646,12 @@ cursesDrawCtxtMake( WINDOW* boardWin ) return (DrawCtx*)dctx; } /* curses_drawctxt_init */ +void +cursesDrawCtxtFree( DrawCtx* pdctx ) +{ + CursesDrawCtx* dctx = (CursesDrawCtx*)pdctx; + free( dctx->vtable ); + free( dctx ); +} + #endif /* PLATFORM_NCURSES */ diff --git a/xwords4/linux/cursesletterask.c b/xwords4/linux/cursesletterask.c index 1dc5d0e19..872fd654c 100644 --- a/xwords4/linux/cursesletterask.c +++ b/xwords4/linux/cursesletterask.c @@ -47,7 +47,7 @@ sizeTextsAsButtons( XP_U16 maxLen, XP_U16 nTiles, XP_U16* textsCols, } /* sizeTextsAsButtons */ XP_S16 -curses_askLetter( CursesAppGlobals* globals, XP_UCHAR* query, +curses_askLetter( WINDOW* window, XP_UCHAR* query, const XP_UCHAR** texts, XP_U16 nTiles ) { XP_S16 result; @@ -70,7 +70,7 @@ curses_askLetter( CursesAppGlobals* globals, XP_UCHAR* query, textPtrs[i] = (char*)&texts[i]; } - getmaxyx( globals->boardWin, y, x ); + getmaxyx( window, y, x ); numCtlButtons = VSIZE(ctlButtons); @@ -209,8 +209,8 @@ curses_askLetter( CursesAppGlobals* globals, XP_UCHAR* query, delwin( confWin ); /* this leaves a ghost line, but I can't figure out a better way. */ - wtouchln( globals->boardWin, (y/2)-(nLines/2), ASK_HEIGHT + rows - 1, 1 ); - wrefresh( globals->boardWin ); + wtouchln( window, (y/2)-(nLines/2), ASK_HEIGHT + rows - 1, 1 ); + wrefresh( window ); return result; } /* curses_askLetter */ diff --git a/xwords4/linux/cursesletterask.h b/xwords4/linux/cursesletterask.h index 0acc8dcf1..98484e31a 100644 --- a/xwords4/linux/cursesletterask.h +++ b/xwords4/linux/cursesletterask.h @@ -23,7 +23,7 @@ #include "linuxmain.h" #include "cursesmain.h" -XP_S16 curses_askLetter( CursesAppGlobals* globals, XP_UCHAR* query, +XP_S16 curses_askLetter( WINDOW* window, XP_UCHAR* query, const XP_UCHAR** texts, XP_U16 nTiles ); diff --git a/xwords4/linux/cursesmain.c b/xwords4/linux/cursesmain.c index c6807c391..3d50ec9dd 100644 --- a/xwords4/linux/cursesmain.c +++ b/xwords4/linux/cursesmain.c @@ -66,14 +66,10 @@ #include "relaycon.h" #include "smsproto.h" #include "device.h" - -#ifdef CURSES_SMALL_SCREEN -# define MENU_WINDOW_HEIGHT 1 -# define BOARD_OFFSET 0 -#else -# define MENU_WINDOW_HEIGHT 5 /* three lines plus borders */ -# define BOARD_OFFSET 1 -#endif +#include "cursesmenu.h" +#include "cursesboard.h" +#include "curgamlistwin.h" +#include "gsrcwrap.h" #ifndef CURSES_CELL_HT # define CURSES_CELL_HT 1 @@ -85,91 +81,68 @@ #define INFINITE_TIMEOUT -1 #define BOARD_SCORE_PADDING 3 +struct CursesAppGlobals { + CommonAppGlobals cag; + CursesMenuState* menuState; + CursGameList* gameList; + CursesBoardState* cbState; + GSList* openGames; + WINDOW* mainWin; + int winWidth, winHeight; -typedef XP_Bool (*CursesMenuHandler)(CursesAppGlobals* globals); -typedef struct MenuList { - CursesMenuHandler handler; - char* desc; - char* keyDesc; - char key; -} MenuList; + XP_U16 nLinesMenu; + gchar* lastErr; -static XP_Bool handleQuit( CursesAppGlobals* globals ); -static XP_Bool handleResend( CursesAppGlobals* globals ); -static XP_Bool handleSpace( CursesAppGlobals* globals ); -static XP_Bool handleRet( CursesAppGlobals* globals ); -static XP_Bool handleHint( CursesAppGlobals* globals ); -#ifdef KEYBOARD_NAV -static XP_Bool handleLeft( CursesAppGlobals* globals ); -static XP_Bool handleRight( CursesAppGlobals* globals ); -static XP_Bool handleUp( CursesAppGlobals* globals ); -static XP_Bool handleDown( CursesAppGlobals* globals ); -#endif -static XP_Bool handleCommit( CursesAppGlobals* globals ); -static XP_Bool handleFlip( CursesAppGlobals* globals ); -static XP_Bool handleToggleValues( CursesAppGlobals* globals ); -static XP_Bool handleBackspace( CursesAppGlobals* globals ); -static XP_Bool handleUndo( CursesAppGlobals* globals ); -static XP_Bool handleReplace( CursesAppGlobals* globals ); -static XP_Bool handleJuggle( CursesAppGlobals* globals ); -static XP_Bool handleHide( CursesAppGlobals* globals ); -static XP_Bool handleAltLeft( CursesAppGlobals* globals ); -static XP_Bool handleAltRight( CursesAppGlobals* globals ); -static XP_Bool handleAltUp( CursesAppGlobals* globals ); -static XP_Bool handleAltDown( CursesAppGlobals* globals ); -#ifdef CURSES_SMALL_SCREEN -static XP_Bool handleRootKeyShow( CursesAppGlobals* globals ); -static XP_Bool handleRootKeyHide( CursesAppGlobals* globals ); + short statusLine; + + struct sockaddr_in listenerSockAddr; +#ifdef USE_GLIBLOOP + GMainLoop* loop; + GList* sources; + int quitpipe[2]; +#else + XP_Bool timeToExit; + short fdCount; + struct pollfd fdArray[FD_MAX]; /* one for stdio, one for listening socket */ + int timepipe[2]; /* for reading/writing "user events" */ #endif +}; +static bool handleOpenGame( void* closure, int key ); +static bool handleNewGame( void* closure, int key ); +static bool handleDeleteGame( void* closure, int key ); +static bool handleSel( void* closure, int key ); const MenuList g_sharedMenuList[] = { { handleQuit, "Quit", "Q", 'Q' }, - { handleResend, "Resend", "R", 'R' }, - { handleSpace, "Raise focus", "", ' ' }, - { handleRet, "Click/tap", "", '\r' }, - { handleHint, "Hint", "?", '?' }, + { handleNewGame, "New Game", "N", 'N' }, + { handleOpenGame, "Open Sel.", "O", 'O' }, + { handleDeleteGame, "Delete Sel.", "D", 'D' }, + { handleSel, "Select up", "J", 'J' }, + { handleSel, "Select down", "K", 'K' }, +/* { handleResend, "Resend", "R", 'R' }, */ +/* { handleSpace, "Raise focus", "", ' ' }, */ +/* { handleRet, "Click/tap", "", '\r' }, */ +/* { handleHint, "Hint", "?", '?' }, */ -#ifdef KEYBOARD_NAV - { handleLeft, "Left", "H", 'H' }, - { handleRight, "Right", "L", 'L' }, - { handleUp, "Up", "J", 'J' }, - { handleDown, "Down", "K", 'K' }, -#endif +/* #ifdef KEYBOARD_NAV */ +/* { handleLeft, "Left", "H", 'H' }, */ +/* { handleRight, "Right", "L", 'L' }, */ +/* { handleUp, "Up", "J", 'J' }, */ +/* { handleDown, "Down", "K", 'K' }, */ +/* #endif */ - { handleCommit, "Commit move", "C", 'C' }, - { handleFlip, "Flip", "F", 'F' }, - { handleToggleValues, "Show values", "V", 'V' }, +/* { handleCommit, "Commit move", "C", 'C' }, */ +/* { handleFlip, "Flip", "F", 'F' }, */ +/* { handleToggleValues, "Show values", "V", 'V' }, */ - { handleBackspace, "Remove from board", "", 8 }, - { handleUndo, "Undo prev", "U", 'U' }, - { handleReplace, "uNdo cur", "N", 'N' }, +/* { handleBackspace, "Remove from board", "", 8 }, */ +/* { handleUndo, "Undo prev", "U", 'U' }, */ +/* { handleReplace, "uNdo cur", "N", 'N' }, */ { NULL, NULL, NULL, '\0'} }; -const MenuList g_boardMenuList[] = { - { handleAltLeft, "Force left", "{", '{' }, - { handleAltRight, "Force right", "}", '}' }, - { handleAltUp, "Force up", "_", '_' }, - { handleAltDown, "Force down", "+", '+' }, - { NULL, NULL, NULL, '\0'} -}; - -const MenuList g_scoreMenuList[] = { -#ifdef KEYBOARD_NAV -#endif - { NULL, NULL, NULL, '\0'} -}; - -const MenuList g_trayMenuList[] = { - { handleJuggle, "Juggle", "G", 'G' }, - { handleHide, "[un]hIde", "I", 'I' }, - { handleAltLeft, "Divider left", "{", '{' }, - { handleAltRight, "Divider right", "}", '}' }, - - { NULL, NULL, NULL, '\0'} -}; #ifdef CURSES_SMALL_SCREEN const MenuList g_rootMenuListShow[] = { @@ -187,21 +160,21 @@ const MenuList g_rootMenuListHide[] = { static CursesAppGlobals g_globals; /* must be global b/c of SIGWINCH_handler */ #ifdef KEYBOARD_NAV -static void changeMenuForFocus( CursesAppGlobals* globals, - BoardObjectType obj ); -static XP_Bool handleLeft( CursesAppGlobals* globals ); -static XP_Bool handleRight( CursesAppGlobals* globals ); -static XP_Bool handleUp( CursesAppGlobals* globals ); -static XP_Bool handleDown( CursesAppGlobals* globals ); -static XP_Bool handleFocusKey( CursesAppGlobals* globals, XP_Key key ); +/* static void changeMenuForFocus( CursesAppGlobals* globals, */ +/* BoardObjectType obj ); */ +/* static XP_Bool handleLeft( CursesAppGlobals* globals ); */ +/* static XP_Bool handleRight( CursesAppGlobals* globals ); */ +/* static XP_Bool handleUp( CursesAppGlobals* globals ); */ +/* static XP_Bool handleDown( CursesAppGlobals* globals ); */ +/* static XP_Bool handleFocusKey( CursesAppGlobals* globals, XP_Key key ); */ #else # define handleFocusKey( g, k ) XP_FALSE #endif -static void countMenuLines( const MenuList** menuLists, int maxX, int padding, - int* nLinesP, int* nColsP ); -static void drawMenuFromList( WINDOW* win, const MenuList** menuLists, - int nLines, int padding ); -static CursesMenuHandler getHandlerForKey( const MenuList* list, char ch ); +/* static void countMenuLines( const MenuList** menuLists, int maxX, int padding, */ +/* int* nLinesP, int* nColsP ); */ +/* static void drawMenuFromList( WINDOW* win, const MenuList** menuLists, */ +/* int nLines, int padding ); */ +/* static CursesMenuHandler getHandlerForKey( const MenuList* list, char ch ); */ #ifdef MEM_DEBUG @@ -213,342 +186,25 @@ static CursesMenuHandler getHandlerForKey( const MenuList* list, char ch ); /* extern int errno; */ static void -cursesUserError( CursesAppGlobals* globals, const char* format, ... ) +initCurses( CursesAppGlobals* aGlobals ) { - char buf[512]; - va_list ap; - - va_start( ap, format ); - - vsprintf( buf, format, ap ); - - const char* buttons[] = {"OK"}; - (void)cursesask( globals, buf, VSIZE(buttons), buttons ); - - va_end(ap); -} /* cursesUserError */ - -static void -curses_util_notifyPickTileBlank( XW_UtilCtxt* uc, XP_U16 playerNum, - XP_U16 XP_UNUSED(col), XP_U16 XP_UNUSED(row), - const XP_UCHAR** texts, XP_U16 nTiles ) -{ - CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; - char query[128]; - char* playerName = globals->cGlobals.gi->players[playerNum].name; - - snprintf( query, sizeof(query), - "Pick tile for %s! (Tab or type letter to select " - "then hit .)", playerName ); - - /*index = */curses_askLetter( globals, query, texts, nTiles ); - // return index; -} /* util_userPickTile */ - -static void -curses_util_informNeedPickTiles( XW_UtilCtxt* XP_UNUSED(uc), - XP_Bool XP_UNUSED(isInitial), - XP_U16 XP_UNUSED(player), - XP_U16 XP_UNUSED(nToPick), - XP_U16 XP_UNUSED(nFaces), - const XP_UCHAR** XP_UNUSED(faces), - const XP_U16* XP_UNUSED(counts) ) -{ - /* CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; */ - /* char query[128]; */ - /* XP_S16 index; */ - /* char* playerName = globals->cGlobals.gi->players[playerNum].name; */ - - /* snprintf( query, sizeof(query), */ - /* "Pick tile for %s! (Tab or type letter to select " */ - /* "then hit .)", playerName ); */ - - /* index = curses_askLetter( globals, query, texts, nTiles ); */ - /* return index; */ -} /* util_userPickTile */ - -static void -curses_util_userError( XW_UtilCtxt* uc, UtilErrID id ) -{ - CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; - XP_Bool silent; - const XP_UCHAR* message = linux_getErrString( id, &silent ); - - if ( silent ) { - XP_LOGF( "silent userError: %s", message ); - } else { - cursesUserError( globals, message ); - } -} /* curses_util_userError */ - -static gint -ask_move( gpointer data ) -{ - CursesAppGlobals* globals = (CursesAppGlobals*)data; - CommonGlobals* cGlobals = &globals->cGlobals; - const char* answers[] = {"Ok", "Cancel", NULL}; - - if (0 == cursesask(globals, cGlobals->question, VSIZE(answers)-1, answers) ) { - BoardCtxt* board = cGlobals->game.board; - if ( board_commitTurn( board, XP_TRUE, XP_TRUE, NULL ) ) { - board_draw( board ); - } - } - - return FALSE; -} - -/* this needs to change!!! */ -static void -curses_util_notifyMove( XW_UtilCtxt* uc, XWStreamCtxt* stream ) -{ - CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; - CommonGlobals* cGlobals = &globals->cGlobals; - XP_U16 len = stream_getSize( stream ); - XP_ASSERT( len <= VSIZE(cGlobals->question) ); - stream_getBytes( stream, cGlobals->question, len ); - (void)g_idle_add( ask_move, globals ); -} /* curses_util_userQuery */ - -static gint -ask_trade( gpointer data ) -{ - CursesAppGlobals* globals = (CursesAppGlobals*)data; - CommonGlobals* cGlobals = &globals->cGlobals; - - const char* buttons[] = { "Ok", "Cancel" }; - if (0 == cursesask( globals, cGlobals->question, VSIZE(buttons), buttons ) ) { - BoardCtxt* board = cGlobals->game.board; - if ( board_commitTurn( board, XP_TRUE, XP_TRUE, NULL ) ) { - board_draw( board ); - } - } - return FALSE; -} - -static void -curses_util_notifyTrade( XW_UtilCtxt* uc, const XP_UCHAR** tiles, XP_U16 nTiles ) -{ - CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; - formatConfirmTrade( &globals->cGlobals, tiles, nTiles ); - (void)g_idle_add( ask_trade, globals ); -} - -static void -curses_util_trayHiddenChange( XW_UtilCtxt* XP_UNUSED(uc), - XW_TrayVisState XP_UNUSED(state), - XP_U16 XP_UNUSED(nVisibleRows) ) -{ - /* nothing to do if we don't have a scrollbar */ -} /* curses_util_trayHiddenChange */ - -static void -cursesShowFinalScores( CursesAppGlobals* globals ) -{ - XWStreamCtxt* stream; - XP_UCHAR* text; - - stream = mem_stream_make_raw( MPPARM(globals->cGlobals.util->mpool) - globals->cGlobals.params->vtMgr ); - server_writeFinalScores( globals->cGlobals.game.server, stream ); - - text = strFromStream( stream ); - - const char* buttons[] = { "Ok" }; - (void)cursesask( globals, text, VSIZE(buttons), buttons ); - - free( text ); - stream_destroy( stream ); -} /* cursesShowFinalScores */ - -static void -curses_util_informMove( XW_UtilCtxt* uc, XP_S16 XP_UNUSED(turn), - XWStreamCtxt* expl, XWStreamCtxt* XP_UNUSED(words)) -{ - CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; - char* question = strFromStream( expl ); - const char* buttons[] = { "Ok" }; - (void)cursesask( globals, question, VSIZE(buttons), buttons ); - free( question ); -} - -static void -curses_util_informUndo( XW_UtilCtxt* XP_UNUSED(uc)) -{ - LOG_FUNC(); -} - -static void -curses_util_notifyGameOver( XW_UtilCtxt* uc, XP_S16 quitter ) -{ - CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; - board_draw( globals->cGlobals.game.board ); - - /* game belongs in cGlobals... */ - if ( globals->cGlobals.params->printHistory ) { - catGameHistory( &globals->cGlobals ); - } - - catFinalScores( &globals->cGlobals, quitter ); - - if ( globals->cGlobals.params->quitAfter >= 0 ) { - sleep( globals->cGlobals.params->quitAfter ); - handleQuit( globals ); - } else if ( globals->cGlobals.params->undoWhenDone ) { - server_handleUndo( globals->cGlobals.game.server, 0 ); - } else if ( !globals->cGlobals.params->skipGameOver ) { - /* This is modal. Don't show if quitting */ - cursesShowFinalScores( globals ); - } -} /* curses_util_notifyGameOver */ - -static void -curses_util_informNetDict( XW_UtilCtxt* uc, XP_LangCode XP_UNUSED(lang), - const XP_UCHAR* XP_UNUSED_DBG(oldName), - const XP_UCHAR* XP_UNUSED_DBG(newName), - const XP_UCHAR* XP_UNUSED_DBG(newSum), - XWPhoniesChoice phoniesAction ) -{ - XP_USE(uc); - XP_USE(phoniesAction); - XP_LOGF( "%s: %s => %s (cksum: %s)", __func__, oldName, newName, newSum ); -} - -static void -curses_util_setIsServer( XW_UtilCtxt* uc, XP_Bool isServer ) -{ - LOG_FUNC(); - CommonGlobals* cGlobals = (CommonGlobals*)uc->closure; - linuxSetIsServer( cGlobals, isServer ); -} - -#ifdef XWFEATURE_HILITECELL -static XP_Bool -curses_util_hiliteCell( XW_UtilCtxt* uc, - XP_U16 XP_UNUSED(col), XP_U16 XP_UNUSED(row) ) -{ - CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; - if ( globals->cGlobals.params->sleepOnAnchor ) { - usleep( 10000 ); - } - return XP_TRUE; -} /* curses_util_hiliteCell */ -#endif - -static XP_Bool -curses_util_engineProgressCallback( XW_UtilCtxt* XP_UNUSED(uc) ) -{ - return XP_TRUE; -} /* curses_util_engineProgressCallback */ - -#ifdef USE_GLIBLOOP -static gboolean -timerFired( gpointer data ) -{ - TimerInfo* ti = (TimerInfo*)data; - CommonGlobals* globals = ti->globals; - XWTimerReason why = ti - globals->timerInfo; - if ( linuxFireTimer( globals, why ) ) { - board_draw( globals->game.board ); - } - - return FALSE; -} -#endif - -static void -curses_util_setTimer( XW_UtilCtxt* uc, XWTimerReason why, XP_U16 when, - XWTimerProc proc, void* closure ) -{ - CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; - TimerInfo* ti = &globals->cGlobals.timerInfo[why]; - - ti->proc = proc; - ti->closure = closure; - -#ifdef USE_GLIBLOOP - ti->globals = &globals->cGlobals; - (void)g_timeout_add_seconds( when, timerFired, ti ); -#else - ti->when = util_getCurSeconds(uc) + when; -#endif -} /* curses_util_setTimer */ - -static void -curses_util_clearTimer( XW_UtilCtxt* uc, XWTimerReason why ) -{ - CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; - globals->cGlobals.timerInfo[why].proc = NULL; -} - -#ifdef USE_GLIBLOOP -static gboolean -onetime_idle( gpointer data ) -{ - CursesAppGlobals* globals = (CursesAppGlobals*)data; - if ( server_do( globals->cGlobals.game.server ) ) { - if ( !!globals->cGlobals.game.board ) { - board_draw( globals->cGlobals.game.board ); - } - saveGame( &globals->cGlobals ); - } - return FALSE; -} -#endif - -static void -curses_util_requestTime( XW_UtilCtxt* uc ) -{ - CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; -#ifdef USE_GLIBLOOP -# if 0 - (void)g_idle_add( onetime_idle, globals ); -# else - (void)g_timeout_add( 1,// interval, - onetime_idle, globals ); -# endif -#else - /* I've created a pipe whose read-only end is plugged into the array of - fds that my event loop polls so that I can write to it to simulate - post-event on a more familiar system. It works, so no complaints! */ - if ( 1 != write( globals->timepipe[1], "!", 1 ) ) { - XP_ASSERT(0); - } -#endif -} /* curses_util_requestTime */ - -static void -initCurses( CursesAppGlobals* globals, int* widthP, int* heightP ) -{ - WINDOW* mainWin; - WINDOW* menuWin; - WINDOW* boardWin; - - int width, height; - /* ncurses man page says most apps want this sequence */ - mainWin = initscr(); + aGlobals->mainWin = initscr(); cbreak(); noecho(); nonl(); intrflush(stdscr, FALSE); keypad(stdscr, TRUE); /* effects wgetch only? */ - getmaxyx( mainWin, height, width ); - XP_LOGF( "%s: getmaxyx()->w:%d; h:%d", __func__, width, height ); + getmaxyx( aGlobals->mainWin, aGlobals->winHeight, aGlobals->winWidth ); + XP_LOGF( "%s: getmaxyx()->w:%d; h:%d", __func__, aGlobals->winWidth, + aGlobals->winHeight ); - globals->statusLine = height - MENU_WINDOW_HEIGHT - 1; - menuWin = newwin( MENU_WINDOW_HEIGHT, width, - height-MENU_WINDOW_HEIGHT, 0 ); - nodelay(menuWin, 1); /* don't block on getch */ - boardWin = newwin( height-MENU_WINDOW_HEIGHT, width, 0, 0 ); + /* globals->statusLine = height - MENU_WINDOW_HEIGHT - 1; */ + /* globals->menuWin = newwin( MENU_WINDOW_HEIGHT, width, */ + /* height-MENU_WINDOW_HEIGHT, 0 ); */ + /* nodelay(globals->menuWin, 1); /\* don't block on getch *\/ */ - globals->menuWin = menuWin; - globals->boardWin = boardWin; - globals->mainWin = mainWin; - - *widthP = width; - *heightP = height; } /* initCurses */ #if 0 @@ -578,100 +234,133 @@ showStatus( CursesAppGlobals* globals ) } /* showStatus */ #endif -static XP_Bool -handleQuit( CursesAppGlobals* globals ) +bool +handleQuit( void* closure, int XP_UNUSED(key) ) { -#ifdef USE_GLIBLOOP + CursesAppGlobals* globals = (CursesAppGlobals*)closure; g_main_loop_quit( globals->loop ); -#else - globals->timeToExit = XP_TRUE; -#endif return XP_TRUE; } /* handleQuit */ -static XP_Bool -handleResend( CursesAppGlobals* globals ) -{ - if ( !!globals->cGlobals.game.comms ) { - comms_resendAll( globals->cGlobals.game.comms, COMMS_CONN_NONE, - XP_TRUE ); - } - return XP_TRUE; -} - -#ifdef KEYBOARD_NAV static void -checkAssignFocus( BoardCtxt* board ) +figureDims( CursesAppGlobals* aGlobals, int* widthP, int* topP, int* heightP ) { - if ( OBJ_NONE == board_getFocusOwner(board) ) { - board_focusChanged( board, OBJ_BOARD, XP_TRUE ); - } + LaunchParams* params = aGlobals->cag.params; + *widthP = aGlobals->winWidth; + *topP = params->cursesListWinHt; + *heightP = aGlobals->winHeight - params->cursesListWinHt - MENU_WINDOW_HEIGHT; } -#else -# define checkAssignFocus(b) -#endif -static XP_Bool -handleSpace( CursesAppGlobals* globals ) +static bool +handleOpenGame( void* closure, int XP_UNUSED(key) ) { - XP_Bool handled; - checkAssignFocus( globals->cGlobals.game.board ); - - globals->doDraw = board_handleKey( globals->cGlobals.game.board, - XP_RAISEFOCUS_KEY, &handled ); - return XP_TRUE; -} /* handleSpace */ - -static XP_Bool -handleRet( CursesAppGlobals* globals ) -{ - XP_Bool handled; - globals->doDraw = board_handleKey( globals->cGlobals.game.board, - XP_RETURN_KEY, &handled ); - return XP_TRUE; -} /* handleRet */ - -static XP_Bool -handleHint( CursesAppGlobals* globals ) -{ - XP_Bool redo; - globals->doDraw = board_requestHint( globals->cGlobals.game.board, -#ifdef XWFEATURE_SEARCHLIMIT - XP_FALSE, -#endif - XP_FALSE, &redo ); - return XP_TRUE; -} /* handleHint */ - -static XP_Bool -handleCommit( CursesAppGlobals* globals ) -{ - globals->doDraw = board_commitTurn( globals->cGlobals.game.board, - XP_FALSE, XP_FALSE, NULL ); - return XP_TRUE; -} /* handleCommit */ - -static XP_Bool -handleJuggle( CursesAppGlobals* globals ) -{ - globals->doDraw = board_juggleTray( globals->cGlobals.game.board ); - return XP_TRUE; -} /* handleJuggle */ - -static XP_Bool -handleHide( CursesAppGlobals* globals ) -{ - XW_TrayVisState curState = - board_getTrayVisState( globals->cGlobals.game.board ); - - if ( curState == TRAY_REVEALED ) { - globals->doDraw = board_hideTray( globals->cGlobals.game.board ); - } else { - globals->doDraw = board_showTray( globals->cGlobals.game.board ); + LOG_FUNC(); + CursesAppGlobals* aGlobals = (CursesAppGlobals*)closure; + const GameInfo* gi = cgl_getSel( aGlobals->gameList ); + if ( !!gi ) { + int width, top, height; + figureDims( aGlobals, &width, &top, &height ); + cb_open( aGlobals->cbState, gi->rowid, width, top, height ); } - return XP_TRUE; -} /* handleJuggle */ +} + +static bool +handleNewGame( void* closure, int XP_UNUSED(key) ) +{ + LOG_FUNC(); + CursesAppGlobals* aGlobals = (CursesAppGlobals*)closure; + + // aGlobals->cag.params->needsNewGame = XP_FALSE; + + int width, top, height; + figureDims( aGlobals, &width, &top, &height ); + cb_new( aGlobals->cbState, width, top, height ); + return XP_TRUE; +} + +static bool +handleDeleteGame( void* closure, int XP_UNUSED(key) ) +{ + CursesAppGlobals* aGlobals = (CursesAppGlobals*)closure; + const char* question = "Are you sure you want to delete the " + "selected game? This action cannot be undone"; + const char* buttons[] = { "Cancel", "Ok", }; + if ( 1 == cursesask( aGlobals->mainWin, question, /* ?? */ + VSIZE(buttons), buttons ) ) { + + const GameInfo* gib = cgl_getSel( aGlobals->gameList ); + if ( !!gib ) { + deleteGame( aGlobals->cag.params->pDb, gib->rowid ); + cgl_remove( aGlobals->gameList, gib->rowid ); + } + } + return XP_TRUE; +} + +static bool +handleSel( void* closure, int key ) +{ + CursesAppGlobals* aGlobals = (CursesAppGlobals*)closure; + XP_ASSERT( key == 'K' || key == 'J' ); + bool down = key == 'J'; + cgl_moveSel( aGlobals->gameList, down ); + return true; +} + +/* static XP_Bool */ +/* handleResend( CursesAppGlobals* globals ) */ +/* { */ +/* if ( !!globals->cGlobals.game.comms ) { */ +/* comms_resendAll( globals->cGlobals.game.comms, COMMS_CONN_NONE, */ +/* XP_TRUE ); */ +/* } */ +/* return XP_TRUE; */ +/* } */ + +/* #ifdef KEYBOARD_NAV */ +/* static void */ +/* checkAssignFocus( BoardCtxt* board ) */ +/* { */ +/* if ( OBJ_NONE == board_getFocusOwner(board) ) { */ +/* board_focusChanged( board, OBJ_BOARD, XP_TRUE ); */ +/* } */ +/* } */ +/* #else */ +/* # define checkAssignFocus(b) */ +/* #endif */ + +/* static XP_Bool */ +/* handleSpace( CursesAppGlobals* globals ) */ +/* { */ +/* XP_Bool handled; */ +/* checkAssignFocus( globals->cGlobals.game.board ); */ + +/* globals->doDraw = board_handleKey( globals->cGlobals.game.board, */ +/* XP_RAISEFOCUS_KEY, &handled ); */ +/* return XP_TRUE; */ +/* } /\* handleSpace *\/ */ + +/* static XP_Bool */ +/* handleRet( CursesAppGlobals* globals ) */ +/* { */ +/* XP_Bool handled; */ +/* globals->doDraw = board_handleKey( globals->cGlobals.game.board, */ +/* XP_RETURN_KEY, &handled ); */ +/* return XP_TRUE; */ +/* } /\* handleRet *\/ */ + +/* static XP_Bool */ +/* handleHint( CursesAppGlobals* globals ) */ +/* { */ +/* XP_Bool redo; */ +/* globals->doDraw = board_requestHint( globals->cGlobals.game.board, */ +/* #ifdef XWFEATURE_SEARCHLIMIT */ +/* XP_FALSE, */ +/* #endif */ +/* XP_FALSE, &redo ); */ +/* return XP_TRUE; */ +/* } /\* handleHint *\/ */ #ifdef CURSES_SMALL_SCREEN static XP_Bool @@ -737,223 +426,114 @@ handleRootKeyHide( CursesAppGlobals* globals ) } #endif -static XP_Bool -handleAltLeft( CursesAppGlobals* XP_UNUSED_KEYBOARD_NAV(globals) ) -{ - return handleFocusKey( globals, XP_CURSOR_KEY_ALTLEFT ); -} -static XP_Bool -handleAltRight( CursesAppGlobals* XP_UNUSED_KEYBOARD_NAV(globals) ) -{ - return handleFocusKey( globals, XP_CURSOR_KEY_ALTRIGHT ); -} +/* static void */ +/* fmtMenuItem( const MenuList* item, char* buf, int maxLen ) */ +/* { */ +/* snprintf( buf, maxLen, "%s %s", item->keyDesc, item->desc ); */ +/* } */ -static XP_Bool -handleAltUp( CursesAppGlobals* XP_UNUSED_KEYBOARD_NAV(globals) ) -{ - return handleFocusKey( globals, XP_CURSOR_KEY_ALTUP ); -} +/* static void */ +/* countMenuLines( const MenuList** menuLists, int maxX, int padding, */ +/* int* nLinesP, int* nColsP ) */ +/* { */ +/* int nCols = 0; */ +/* /\* The menu space should be wider rather than taller, but line up by */ +/* column. So we want to use as many columns as possible to minimize the */ +/* number of lines. So start with one line and lay out. If that doesn't */ +/* fit, try two. Given the number of lines, get the max width of each */ +/* column. */ +/* *\/ */ -static XP_Bool -handleAltDown( CursesAppGlobals* XP_UNUSED_KEYBOARD_NAV(globals) ) -{ - return handleFocusKey( globals, XP_CURSOR_KEY_ALTDOWN ); -} +/* maxX -= padding * 2; /\* on left and right side *\/ */ -static XP_Bool -handleFlip( CursesAppGlobals* globals ) -{ - globals->doDraw = board_flip( globals->cGlobals.game.board ); - return XP_TRUE; -} /* handleFlip */ +/* int nLines; */ +/* for ( nLines = 1; ; ++nLines ) { */ +/* short line = 0; */ +/* XP_Bool tooFewLines = XP_FALSE; */ +/* int maxThisCol = 0; */ +/* int i; */ +/* nCols = 0; */ -static XP_Bool -handleToggleValues( CursesAppGlobals* globals ) -{ - globals->doDraw = board_toggle_showValues( globals->cGlobals.game.board ); - return XP_TRUE; -} /* handleToggleValues */ +/* for ( i = 0; !tooFewLines && (NULL != menuLists[i]); ++i ) { */ +/* const MenuList* entry; */ +/* for ( entry = menuLists[i]; !tooFewLines && !!entry->handler; */ +/* ++entry ) { */ +/* int width; */ +/* char buf[32]; */ -static XP_Bool -handleBackspace( CursesAppGlobals* globals ) -{ - XP_Bool handled; - globals->doDraw = board_handleKey( globals->cGlobals.game.board, - XP_CURSOR_KEY_DEL, &handled ); - return XP_TRUE; -} /* handleBackspace */ +/* /\* time to switch to new column? *\/ */ +/* if ( line == nLines ) { */ +/* nCols += maxThisCol; */ +/* if ( nCols > maxX ) { */ +/* tooFewLines = XP_TRUE; */ +/* break; */ +/* } */ +/* maxThisCol = 0; */ +/* line = 0; */ +/* } */ -static XP_Bool -handleUndo( CursesAppGlobals* globals ) -{ - globals->doDraw = server_handleUndo( globals->cGlobals.game.server, 0 ); - return XP_TRUE; -} /* handleUndo */ +/* fmtMenuItem( entry, buf, sizeof(buf) ); */ +/* width = strlen(buf) + 2; /\* padding *\/ */ -static XP_Bool -handleReplace( CursesAppGlobals* globals ) -{ - globals->doDraw = board_replaceTiles( globals->cGlobals.game.board ); - return XP_TRUE; -} /* handleReplace */ +/* if ( maxThisCol < width ) { */ +/* maxThisCol = width; */ +/* } */ -#ifdef KEYBOARD_NAV -static XP_Bool -handleFocusKey( CursesAppGlobals* globals, XP_Key key ) -{ - XP_Bool handled; - XP_Bool draw; - - checkAssignFocus( globals->cGlobals.game.board ); - - draw = board_handleKey( globals->cGlobals.game.board, key, &handled ); - if ( !handled ) { - BoardObjectType nxt; - BoardObjectType order[] = { OBJ_BOARD, OBJ_SCORE, OBJ_TRAY }; - draw = linShiftFocus( &globals->cGlobals, key, order, &nxt ) || draw; - if ( nxt != OBJ_NONE ) { - changeMenuForFocus( globals, nxt ); - } - } - - globals->doDraw = draw || globals->doDraw; - return XP_TRUE; -} /* handleFocusKey */ - -static XP_Bool -handleLeft( CursesAppGlobals* globals ) -{ - return handleFocusKey( globals, XP_CURSOR_KEY_LEFT ); -} /* handleLeft */ - -static XP_Bool -handleRight( CursesAppGlobals* globals ) -{ - return handleFocusKey( globals, XP_CURSOR_KEY_RIGHT ); -} /* handleRight */ - -static XP_Bool -handleUp( CursesAppGlobals* globals ) -{ - return handleFocusKey( globals, XP_CURSOR_KEY_UP ); -} /* handleUp */ - -static XP_Bool -handleDown( CursesAppGlobals* globals ) -{ - return handleFocusKey( globals, XP_CURSOR_KEY_DOWN ); -} /* handleDown */ -#endif - -static void -fmtMenuItem( const MenuList* item, char* buf, int maxLen ) -{ - snprintf( buf, maxLen, "%s %s", item->keyDesc, item->desc ); -} - - -static void -countMenuLines( const MenuList** menuLists, int maxX, int padding, - int* nLinesP, int* nColsP ) -{ - int nCols = 0; - /* The menu space should be wider rather than taller, but line up by - column. So we want to use as many columns as possible to minimize the - number of lines. So start with one line and lay out. If that doesn't - fit, try two. Given the number of lines, get the max width of each - column. - */ - - maxX -= padding * 2; /* on left and right side */ - - int nLines; - for ( nLines = 1; ; ++nLines ) { - short line = 0; - XP_Bool tooFewLines = XP_FALSE; - int maxThisCol = 0; - int i; - nCols = 0; - - for ( i = 0; !tooFewLines && (NULL != menuLists[i]); ++i ) { - const MenuList* entry; - for ( entry = menuLists[i]; !tooFewLines && !!entry->handler; - ++entry ) { - int width; - char buf[32]; - - /* time to switch to new column? */ - if ( line == nLines ) { - nCols += maxThisCol; - if ( nCols > maxX ) { - tooFewLines = XP_TRUE; - break; - } - maxThisCol = 0; - line = 0; - } - - fmtMenuItem( entry, buf, sizeof(buf) ); - width = strlen(buf) + 2; /* padding */ - - if ( maxThisCol < width ) { - maxThisCol = width; - } - - ++line; - } - } - /* If we get here without running out of space, we're done */ - nCols += maxThisCol; - if ( !tooFewLines && (nCols < maxX) ) { - break; - } - } +/* ++line; */ +/* } */ +/* } */ +/* /\* If we get here without running out of space, we're done *\/ */ +/* nCols += maxThisCol; */ +/* if ( !tooFewLines && (nCols < maxX) ) { */ +/* break; */ +/* } */ +/* } */ - *nColsP = nCols; - *nLinesP = nLines; -} /* countMenuLines */ +/* *nColsP = nCols; */ +/* *nLinesP = nLines; */ +/* } /\* countMenuLines *\/ */ -static void -drawMenuFromList( WINDOW* win, const MenuList** menuLists, - int nLines, int padding ) -{ - short line = 0, col, i; - int winMaxY, winMaxX; +/* static void */ +/* drawMenuFromList( WINDOW* win, const MenuList** menuLists, */ +/* int nLines, int padding ) */ +/* { */ +/* short line = 0, col, i; */ +/* int winMaxY, winMaxX; */ - getmaxyx( win, winMaxY, winMaxX ); - XP_USE(winMaxY); +/* getmaxyx( win, winMaxY, winMaxX ); */ +/* XP_USE(winMaxY); */ - int maxColWidth = 0; - if ( 0 == nLines ) { - int ignore; - countMenuLines( menuLists, winMaxX, padding, &nLines, &ignore ); - } - col = 0; +/* int maxColWidth = 0; */ +/* if ( 0 == nLines ) { */ +/* int ignore; */ +/* countMenuLines( menuLists, winMaxX, padding, &nLines, &ignore ); */ +/* } */ +/* col = 0; */ - for ( i = 0; NULL != menuLists[i]; ++i ) { - const MenuList* entry; - for ( entry = menuLists[i]; !!entry->handler; ++entry ) { - char buf[32]; +/* for ( i = 0; NULL != menuLists[i]; ++i ) { */ +/* const MenuList* entry; */ +/* for ( entry = menuLists[i]; !!entry->handler; ++entry ) { */ +/* char buf[32]; */ - fmtMenuItem( entry, buf, sizeof(buf) ); +/* fmtMenuItem( entry, buf, sizeof(buf) ); */ - mvwaddstr( win, line+padding, col+padding, buf ); +/* mvwaddstr( win, line+padding, col+padding, buf ); */ - int width = strlen(buf) + 2; - if ( width > maxColWidth ) { - maxColWidth = width; - } +/* int width = strlen(buf) + 2; */ +/* if ( width > maxColWidth ) { */ +/* maxColWidth = width; */ +/* } */ - if ( ++line == nLines ) { - line = 0; - col += maxColWidth; - maxColWidth = 0; - } +/* if ( ++line == nLines ) { */ +/* line = 0; */ +/* col += maxColWidth; */ +/* maxColWidth = 0; */ +/* } */ - } - } -} /* drawMenuFromList */ +/* } */ +/* } */ +/* } /\* drawMenuFromList *\/ */ static void SIGWINCH_handler( int signal ) @@ -970,7 +550,7 @@ SIGWINCH_handler( int signal ) XP_LOGF( "%s:, getmaxyx()->w:%d; h:%d", __func__, width, height ); wresize( g_globals.mainWin, height-MENU_WINDOW_HEIGHT, width ); - board_draw( g_globals.cGlobals.game.board ); + // board_draw( g_globals.cGlobals.game.board ); } /* SIGWINCH_handler */ static void @@ -981,102 +561,17 @@ SIGINTTERM_handler( int XP_UNUSED(signal) ) } } -static void -cursesListenOnSocket( void* closure, int newSock -#ifdef USE_GLIBLOOP - , GIOFunc func -#endif -) -{ -#ifdef USE_GLIBLOOP - GIOChannel* channel = g_io_channel_unix_new( newSock ); - XP_LOGF( "%s: created channel %p for socket %d", __func__, channel, newSock ); - XP_ASSERT( !!func ); - (void)g_io_add_watch( channel, - G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, - func, closure ); -#else - XP_ASSERT( globals->fdCount+1 < FD_MAX ); - - XP_WARNF( "%s: setting fd[%d] to %d", __func__, globals->fdCount, newSock ); - globals->fdArray[globals->fdCount].fd = newSock; - globals->fdArray[globals->fdCount].events = POLLIN | POLLERR | POLLHUP; - - ++globals->fdCount; - XP_LOGF( "%s: there are now %d sources to poll", - __func__, globals->fdCount ); -#endif -} /* cursesListenOnSocket */ - -static void -curses_socket_added( void* closure, int newSock, GIOFunc func ) -{ - // CursesAppGlobals* globals = (CursesAppGlobals*)closure; - if ( newSock != -1 ) { - cursesListenOnSocket( closure, newSock -#ifdef USE_GLIBLOOP - , func -#endif - ); - } - -#ifdef XWFEATURE_RELAY - /* XP_ASSERT( !globals->cGlobals.relaySocket ); */ - /* globals->cGlobals.relaySocket = newSock; */ -#endif -} /* curses_socket_added */ - -static void -curses_onGameSaved( void* closure, sqlite3_int64 rowid, - XP_Bool XP_UNUSED(firstTime) ) -{ - CursesAppGlobals* globals = (CursesAppGlobals*)closure; - /* May not be recorded */ - globals->cGlobals.selRow = rowid; -} - #ifdef USE_GLIBLOOP static gboolean handle_quitwrite( GIOChannel* XP_UNUSED(source), GIOCondition XP_UNUSED(condition), gpointer data ) { CursesAppGlobals* globals = (CursesAppGlobals*)data; - handleQuit( globals ); + handleQuit( globals, 0 ); return TRUE; } -static gboolean -fire_acceptor( GIOChannel* source, GIOCondition condition, gpointer data ) -{ - if ( 0 != (G_IO_IN & condition) ) { - CursesAppGlobals* globals = (CursesAppGlobals*)data; - - int fd = g_io_channel_unix_get_fd( source ); - XP_ASSERT( fd == globals->csInfo.server.serverSocket ); - (*globals->cGlobals.acceptor)( fd, globals ); - } - return TRUE; -} #endif -static void -curses_socket_acceptor( int listener, Acceptor func, CommonGlobals* cGlobals, - void** XP_UNUSED(storage) ) -{ - if ( -1 == listener ) { - XP_LOGF( "%s: removal of listener not implemented!!!!!", __func__ ); - } else { - CursesAppGlobals* globals = (CursesAppGlobals*)cGlobals; - XP_ASSERT( !cGlobals->acceptor || (func == cGlobals->acceptor) ); - cGlobals->acceptor = func; - globals->csInfo.server.serverSocket = listener; - cursesListenOnSocket( globals, listener -#ifdef USE_GLIBLOOP - , fire_acceptor -#endif - ); - } -} - #ifndef USE_GLIBLOOP #ifdef XWFEATURE_RELAY static int @@ -1280,71 +775,60 @@ fireCursesTimer( CursesAppGlobals* globals ) /* } /\* blocking_gotEvent *\/ */ #endif -static void -remapKey( int* kp ) -{ - /* There's what the manual says I should get, and what I actually do from - * a funky M$ keyboard.... - */ - int key = *kp; - switch( key ) { - case KEY_B2: /* "center of keypad" */ - key = '\r'; - break; - case KEY_DOWN: - case 526: - key = 'K'; - break; - case KEY_UP: - case 523: - key = 'J'; - break; - case KEY_LEFT: - case 524: - key = 'H'; - break; - case KEY_RIGHT: - case 525: - key = 'L'; - break; - default: - if ( key > 0x7F ) { - XP_LOGF( "%s(%d): no mapping", __func__, key ); - } - break; - } - *kp = key; -} /* remapKey */ +/* static void */ +/* remapKey( int* kp ) */ +/* { */ +/* /\* There's what the manual says I should get, and what I actually do from */ +/* * a funky M$ keyboard.... */ +/* *\/ */ +/* int key = *kp; */ +/* switch( key ) { */ +/* case KEY_B2: /\* "center of keypad" *\/ */ +/* key = '\r'; */ +/* break; */ +/* case KEY_DOWN: */ +/* case 526: */ +/* key = 'K'; */ +/* break; */ +/* case KEY_UP: */ +/* case 523: */ +/* key = 'J'; */ +/* break; */ +/* case KEY_LEFT: */ +/* case 524: */ +/* key = 'H'; */ +/* break; */ +/* case KEY_RIGHT: */ +/* case 525: */ +/* key = 'L'; */ +/* break; */ +/* default: */ +/* if ( key > 0x7F ) { */ +/* XP_LOGF( "%s(%d): no mapping", __func__, key ); */ +/* } */ +/* break; */ +/* } */ +/* *kp = key; */ +/* } /\* remapKey *\/ */ -static void -drawMenuLargeOrSmall( CursesAppGlobals* globals, const MenuList* menuList ) -{ -#ifdef CURSES_SMALL_SCREEN - const MenuList* lists[] = { g_rootMenuListShow, NULL }; -#else - const MenuList* lists[] = { g_sharedMenuList, menuList, NULL }; -#endif - wclear( globals->menuWin ); - drawMenuFromList( globals->menuWin, lists, 0, 0 ); - wrefresh( globals->menuWin ); -} +typedef struct _MenuEntry { + MenuList* menuItem; + void* closure; +} MenuEntry; -#ifdef KEYBOARD_NAV -static void -changeMenuForFocus( CursesAppGlobals* globals, BoardObjectType focussed ) -{ - if ( focussed == OBJ_TRAY ) { - globals->menuList = g_trayMenuList; - } else if ( focussed == OBJ_BOARD ) { - globals->menuList = g_boardMenuList; - } else if ( focussed == OBJ_SCORE ) { - globals->menuList = g_scoreMenuList; - } else { - XP_ASSERT(0); - } - drawMenuLargeOrSmall( globals, globals->menuList ); -} /* changeMenuForFocus */ -#endif +/* void */ +/* drawMenuLargeOrSmall( CursesAppGlobals* globals, const MenuList* menuList, */ +/* void* closure ) */ +/* { */ +/* #ifdef CURSES_SMALL_SCREEN */ +/* const MenuList* lists[] = { g_rootMenuListShow, NULL }; */ +/* #else */ +/* const MenuList* lists[] = { g_sharedMenuList, menuList, NULL }; */ +/* #endif */ +/* wclear( globals->menuWin ); */ +/* drawMenuFromList( globals->menuWin, lists, 0, 0 ); */ +/* wrefresh( globals->menuWin ); */ +/* } */ #if 0 static void @@ -1364,246 +848,45 @@ initClientSocket( CursesAppGlobals* globals, char* serverName ) } /* initClientSocket */ #endif -static void -curses_util_informNeedPassword( XW_UtilCtxt* XP_UNUSED(uc), - XP_U16 XP_UNUSED_DBG(playerNum), - const XP_UCHAR* XP_UNUSED_DBG(name) ) -{ - XP_WARNF( "curses_util_informNeedPassword(num=%d, name=%s", playerNum, name ); -} /* curses_util_askPassword */ -static void -curses_util_yOffsetChange( XW_UtilCtxt* XP_UNUSED(uc), - XP_U16 XP_UNUSED(maxOffset), - XP_U16 XP_UNUSED(oldOffset), XP_U16 XP_UNUSED(newOffset) ) -{ - /* if ( oldOffset != newOffset ) { */ - /* XP_WARNF( "curses_util_yOffsetChange(%d,%d,%d) not implemented", */ - /* maxOffset, oldOffset, newOffset ); */ - /* } */ -} /* curses_util_yOffsetChange */ -#ifdef XWFEATURE_TURNCHANGENOTIFY -static void -curses_util_turnChanged( XW_UtilCtxt* XP_UNUSED(uc), XP_S16 XP_UNUSED_DBG(newTurn) ) -{ - XP_LOGF( "%s(turn=%d)", __func__, newTurn ); -} -#endif +/* static const MenuList* */ +/* getHandlerForKey( const MenuList* list, char ch ) */ +/* { */ +/* MenuList* handler = NULL; */ +/* while ( list->handler != NULL ) { */ +/* if ( list->key == ch ) { */ +/* handler = list->handler; */ +/* break; */ +/* } */ +/* ++list; */ +/* } */ +/* return handler; */ +/* } */ -static void -curses_util_notifyIllegalWords( XW_UtilCtxt* XP_UNUSED(uc), - BadWordInfo* XP_UNUSED(bwi), - XP_U16 XP_UNUSED(player), - XP_Bool XP_UNUSED(turnLost) ) -{ - XP_WARNF( "curses_util_notifyIllegalWords not implemented" ); -} /* curses_util_notifyIllegalWord */ +/* static XP_Bool */ +/* handleKeyEvent( CursesAppGlobals* globals, const MenuList* list, char ch ) */ +/* { */ +/* const MenuList* entry = getHandlerForKey( list, ch ); */ +/* XP_Bool result = XP_FALSE; */ +/* if ( !!handler ) { */ +/* result = (*entry->handler)(entry->closure); */ +/* } */ +/* return result; */ +/* } /\* handleKeyEvent *\/ */ -static void -curses_util_remSelected( XW_UtilCtxt* uc ) -{ - CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; - XWStreamCtxt* stream; - XP_UCHAR* text; - - stream = mem_stream_make_raw( MPPARM(globals->cGlobals.util->mpool) - globals->cGlobals.params->vtMgr ); - board_formatRemainingTiles( globals->cGlobals.game.board, stream ); - - text = strFromStream( stream ); - - const char* buttons[] = { "Ok" }; - (void)cursesask( globals, text, VSIZE(buttons), buttons ); - - free( text ); -} - -#ifndef XWFEATURE_STANDALONE_ONLY -static XWStreamCtxt* -curses_util_makeStreamFromAddr(XW_UtilCtxt* uc, XP_PlayerAddr channelNo ) -{ - CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; - LaunchParams* params = globals->cGlobals.params; - - XWStreamCtxt* stream = mem_stream_make( MPPARM(uc->mpool) params->vtMgr, - &globals->cGlobals, channelNo, - sendOnClose ); - return stream; -} /* curses_util_makeStreamFromAddr */ -#endif - -#ifdef XWFEATURE_CHAT -static void -curses_util_showChat( XW_UtilCtxt* uc, - const XP_UCHAR* const XP_UNUSED_DBG(msg), - XP_S16 XP_UNUSED_DBG(from), XP_U32 XP_UNUSED(timestamp) ) -{ - CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; - globals->nChatsSent = 0; -# ifdef DEBUG - const XP_UCHAR* name = ""; - if ( 0 <= from ) { - CommonGlobals* cGlobals = &globals->cGlobals; - name = cGlobals->gi->players[from].name; - } - XP_LOGF( "%s: got \"%s\" from %s", __func__, msg, name ); -# endif -} -#endif - -static void -setupCursesUtilCallbacks( CursesAppGlobals* globals, XW_UtilCtxt* util ) -{ - util->vtable->m_util_userError = curses_util_userError; - - util->vtable->m_util_informNeedPassword = curses_util_informNeedPassword; - util->vtable->m_util_yOffsetChange = curses_util_yOffsetChange; -#ifdef XWFEATURE_TURNCHANGENOTIFY - util->vtable->m_util_turnChanged = curses_util_turnChanged; -#endif - util->vtable->m_util_notifyIllegalWords = curses_util_notifyIllegalWords; - util->vtable->m_util_remSelected = curses_util_remSelected; -#ifndef XWFEATURE_STANDALONE_ONLY - util->vtable->m_util_makeStreamFromAddr = curses_util_makeStreamFromAddr; -#endif -#ifdef XWFEATURE_CHAT - util->vtable->m_util_showChat = curses_util_showChat; -#endif - - util->vtable->m_util_notifyMove = curses_util_notifyMove; - util->vtable->m_util_notifyTrade = curses_util_notifyTrade; - util->vtable->m_util_notifyPickTileBlank = curses_util_notifyPickTileBlank; - util->vtable->m_util_informNeedPickTiles = curses_util_informNeedPickTiles; - util->vtable->m_util_trayHiddenChange = curses_util_trayHiddenChange; - util->vtable->m_util_informMove = curses_util_informMove; - util->vtable->m_util_informUndo = curses_util_informUndo; - util->vtable->m_util_notifyGameOver = curses_util_notifyGameOver; - util->vtable->m_util_informNetDict = curses_util_informNetDict; - util->vtable->m_util_setIsServer = curses_util_setIsServer; - -#ifdef XWFEATURE_HILITECELL - util->vtable->m_util_hiliteCell = curses_util_hiliteCell; -#endif - util->vtable->m_util_engineProgressCallback = - curses_util_engineProgressCallback; - - util->vtable->m_util_setTimer = curses_util_setTimer; - util->vtable->m_util_clearTimer = curses_util_clearTimer; - util->vtable->m_util_requestTime = curses_util_requestTime; - - util->closure = globals; -} /* setupCursesUtilCallbacks */ - -static CursesMenuHandler -getHandlerForKey( const MenuList* list, char ch ) -{ - CursesMenuHandler handler = NULL; - while ( list->handler != NULL ) { - if ( list->key == ch ) { - handler = list->handler; - break; - } - ++list; - } - return handler; -} - -static XP_Bool -handleKeyEvent( CursesAppGlobals* globals, const MenuList* list, char ch ) -{ - CursesMenuHandler handler = getHandlerForKey( list, ch ); - XP_Bool result = XP_FALSE; - if ( !!handler ) { - result = (*handler)(globals); - } - return result; -} /* handleKeyEvent */ - -static XP_Bool -passKeyToBoard( CursesAppGlobals* globals, char ch ) -{ - XP_Bool handled = ch >= 'a' && ch <= 'z'; - if ( handled ) { - ch += 'A' - 'a'; - globals->doDraw = board_handleKey( globals->cGlobals.game.board, - ch, NULL ); - } - return handled; -} /* passKeyToBoard */ - -static void -positionSizeStuff( CursesAppGlobals* globals, int width, int height ) -{ - CommonGlobals* cGlobals = &globals->cGlobals; - BoardCtxt* board = cGlobals->game.board; -#ifdef COMMON_LAYOUT - - BoardDims dims; - board_figureLayout( board, cGlobals->gi, - 0, 0, width, height, 100, - 150, 200, /* percents */ - width*75/100, 2, 1, - XP_FALSE, &dims ); - board_applyLayout( board, &dims ); - -#else - XP_U16 cellWidth, cellHt, scoreLeft, scoreWidth; - int remWidth = width; - int nRows = globals->cGlobals.gi->boardSize; - - cellWidth = CURSES_CELL_WIDTH; - cellHt = CURSES_CELL_HT; - board_setPos( board, BOARD_OFFSET, BOARD_OFFSET, - cellWidth * nRows, cellHt * nRows, - cellWidth, XP_FALSE ); - /* board_setScale( board, cellWidth, cellHt ); */ - scoreLeft = (cellWidth * nRows);// + BOARD_SCORE_PADDING; - remWidth -= cellWidth * nRows; - - /* If the scoreboard will right of the board, put it there. Otherwise try - to fit it below the boards. */ - int tileWidth = 3; - int trayWidth = (tileWidth*MAX_TRAY_TILES); - int trayLeft = scoreLeft; - int trayTop; - int trayHt = 4; - if ( trayWidth < remWidth ) { - trayLeft += XP_MIN(remWidth - trayWidth, BOARD_SCORE_PADDING ); - trayTop = 8; - } else { - trayLeft = BOARD_OFFSET; - trayTop = BOARD_OFFSET + (cellHt * nRows); - if ( trayTop + trayHt > height ) { - XP_ASSERT( height > trayTop ); - trayHt = height - trayTop; - } - } - board_setTrayLoc( board, trayLeft, trayTop, (3*MAX_TRAY_TILES)+1, - trayHt, 1 ); - - scoreWidth = remWidth; - if ( scoreWidth > 45 ) { - scoreWidth = 45; - scoreLeft += (remWidth - scoreWidth) / 2; - } - board_setScoreboardLoc( board, scoreLeft, 1, - scoreWidth, 5, /*4 players + rem*/ XP_FALSE ); - - /* no divider -- yet */ - /* board_setTrayVisible( globals.board, XP_TRUE, XP_FALSE ); */ -#endif - board_invalAll( board ); -} /* positionSizeStuff */ - -static XP_Bool -relay_sendNoConn_curses( const XP_U8* msg, XP_U16 len, - const XP_UCHAR* XP_UNUSED(msgNo), - const XP_UCHAR* relayID, void* closure ) -{ - CursesAppGlobals* globals = (CursesAppGlobals*)closure; - return storeNoConnMsg( &globals->cGlobals, msg, len, relayID ); -} /* relay_sendNoConn_curses */ +/* static XP_Bool */ +/* passKeyToBoard( CursesAppGlobals* XP_UNUSED(globals), char XP_UNUSED(ch) ) */ +/* { */ +/* XP_ASSERT(0); */ +/* /\* XP_Bool handled = ch >= 'a' && ch <= 'z'; *\/ */ +/* /\* if ( handled ) { *\/ */ +/* /\* ch += 'A' - 'a'; *\/ */ +/* /\* globals->doDraw = board_handleKey( globals->cGlobals.game.board, *\/ */ +/* /\* ch, NULL ); *\/ */ +/* /\* } *\/ */ +/* /\* return handled; *\/ */ +/* } /\* passKeyToBoard *\/ */ #ifdef RELAY_VIA_HTTP static void @@ -1615,140 +898,84 @@ onJoined( void* closure, const XP_UCHAR* connname, XWHostID hid ) comms_gameJoined( comms, connname, hid ); } -static void -relay_requestJoin_curses( void* closure, const XP_UCHAR* devID, const XP_UCHAR* room, - XP_U16 nPlayersHere, XP_U16 nPlayersTotal, - XP_U16 seed, XP_U16 lang ) -{ - CursesAppGlobals* globals = (CursesAppGlobals*)closure; - relaycon_join( globals->cGlobals.params, devID, room, nPlayersHere, nPlayersTotal, - seed, lang, onJoined, globals ); -} +/* static void */ +/* relay_requestJoin_curses( void* closure, const XP_UCHAR* devID, const XP_UCHAR* room, */ +/* XP_U16 nPlayersHere, XP_U16 nPlayersTotal, */ +/* XP_U16 seed, XP_U16 lang ) */ +/* { */ +/* CursesAppGlobals* globals = (CursesAppGlobals*)closure; */ +/* relaycon_join( globals->cGlobals.params, devID, room, nPlayersHere, nPlayersTotal, */ +/* seed, lang, onJoined, globals ); */ +/* } */ #endif static void -relay_status_curses( void* closure, CommsRelayState state ) -{ - CursesAppGlobals* globals = (CursesAppGlobals*)closure; - globals->commsRelayState = state; - XP_LOGF( "%s got status: %s", __func__, CommsRelayState2Str(state) ); -} - -static void -relay_connd_curses( void* XP_UNUSED(closure), XP_UCHAR* const XP_UNUSED(room), - XP_Bool XP_UNUSED(reconnect), XP_U16 XP_UNUSED(devOrder), - XP_Bool XP_UNUSED_DBG(allHere), - XP_U16 XP_UNUSED_DBG(nMissing) ) -{ - XP_LOGF( "%s got allHere: %s; nMissing: %d", __func__, - allHere?"true":"false", nMissing ); -} - -static void -relay_error_curses( void* XP_UNUSED(closure), XWREASON XP_UNUSED_DBG(relayErr) ) -{ -#ifdef DEBUG - XP_LOGF( "%s(%s)", __func__, XWREASON2Str( relayErr ) ); -#endif -} - -#ifdef USE_GLIBLOOP -static gboolean -handle_stdin( GIOChannel* XP_UNUSED_DBG(source), GIOCondition condition, - gpointer data ) -{ - if ( 0 != (G_IO_IN & condition) ) { -#ifdef DEBUG - gint fd = g_io_channel_unix_get_fd( source ); - XP_ASSERT( 0 == fd ); -#endif - CursesAppGlobals* globals = (CursesAppGlobals*)data; - int ch = wgetch( globals->mainWin ); - remapKey( &ch ); - if ( -#ifdef CURSES_SMALL_SCREEN - handleKeyEvent( globals, g_rootMenuListShow, ch ) || -#endif - handleKeyEvent( globals, globals->menuList, ch ) - || handleKeyEvent( globals, g_sharedMenuList, ch ) - || passKeyToBoard( globals, ch ) ) { - if ( g_globals.doDraw ) { - board_draw( globals->cGlobals.game.board ); - globals->doDraw = XP_FALSE; - } - } - } - return TRUE; -} -#endif - -#ifdef COMMS_XPORT_FLAGSPROC -static XP_U32 -curses_getFlags( void* XP_UNUSED(closure) ) -{ - return COMMS_XPORT_FLAGS_HASNOCONN; -} -#endif - -static void -cursesGotBuf( void* closure, const CommsAddrRec* addr, +cursesGotBuf( void* closure, const CommsAddrRec* addr, const XP_U8* buf, XP_U16 len ) { LOG_FUNC(); - CursesAppGlobals* globals = (CursesAppGlobals*)closure; + CursesAppGlobals* aGlobals = (CursesAppGlobals*)closure; XP_U32 clientToken; XP_ASSERT( sizeof(clientToken) < len ); XP_MEMCPY( &clientToken, &buf[0], sizeof(clientToken) ); buf += sizeof(clientToken); len -= sizeof(clientToken); - sqlite3_int64 ignore; - XP_U16 seed; - rowidFromToken( XP_NTOHL( clientToken ), &ignore, &seed ); - // XP_ASSERT( seed == comms_getChannelSeed( globals->cGlobals.game.comms ) ); - if ( seed == comms_getChannelSeed( globals->cGlobals.game.comms ) ) { - gameGotBuf( &globals->cGlobals, XP_TRUE, buf, len, addr ); - } else { - XP_LOGF( "%s: dropping packet; meant for a different device", - __func__ ); - } - LOG_RETURN_VOID(); + sqlite3_int64 rowid; + XP_U16 gotSeed; + rowidFromToken( XP_NTOHL( clientToken ), &rowid, &gotSeed ); + + /* Figure out if the device is live, or we need to open the game */ + XP_U16 seed = cb_feedBuffer( aGlobals->cbState, rowid, buf, len, addr ); + XP_ASSERT( seed == 0 || gotSeed == seed ); + XP_USE( seed ); + + /* if ( seed == comms_getChannelSeed( cGlobals->game.comms ) ) { */ + /* gameGotBuf( cGlobals, XP_TRUE, buf, len, addr ); */ + /* } else { */ + /* XP_LOGF( "%s(): dropping packet; meant for a different device", */ + /* __func__ ); */ + /* } */ + /* LOG_RETURN_VOID(); */ } static void -cursesGotForRow( void* closure, const CommsAddrRec* from, - sqlite3_int64 XP_UNUSED_DBG(rowid), const XP_U8* buf, - XP_U16 len ) +cursesGotForRow( void* XP_UNUSED(closure), const CommsAddrRec* XP_UNUSED(from), + sqlite3_int64 XP_UNUSED(rowid), const XP_U8* XP_UNUSED(buf), + XP_U16 XP_UNUSED(len) ) { + // CursesAppGlobals* aGlobals = (CursesAppGlobals*)closure; LOG_FUNC(); - CursesAppGlobals* globals = (CursesAppGlobals*)closure; - XP_ASSERT( globals->cGlobals.selRow == rowid ); - gameGotBuf( &globals->cGlobals, XP_TRUE, buf, len, from ); + /* gameGotBuf( &globals->cGlobals, XP_TRUE, buf, len, from ); */ + XP_ASSERT( 0 ); LOG_RETURN_VOID(); } static gint curses_requestMsgs( gpointer data ) { - CursesAppGlobals* globals = (CursesAppGlobals*)data; + CursesAppGlobals* aGlobals = (CursesAppGlobals*)data; XP_UCHAR devIDBuf[64] = {0}; - db_fetch_safe( globals->cGlobals.params->pDb, KEY_RDEVID, devIDBuf, + db_fetch_safe( aGlobals->cag.params->pDb, KEY_RDEVID, devIDBuf, sizeof(devIDBuf) ); if ( '\0' != devIDBuf[0] ) { - relaycon_requestMsgs( globals->cGlobals.params, devIDBuf ); + relaycon_requestMsgs( aGlobals->cag.params, devIDBuf ); } else { XP_LOGF( "%s: not requesting messages as don't have relay id", __func__ ); } return 0; /* don't run again */ } - -static void +static void cursesNoticeRcvd( void* closure ) { LOG_FUNC(); CursesAppGlobals* globals = (CursesAppGlobals*)closure; - (void)g_idle_add( curses_requestMsgs, globals ); +#ifdef DEBUG + guint res = +#endif + ADD_ONETIME_IDLE( curses_requestMsgs, globals ); + XP_ASSERT( res > 0 ); } static gboolean @@ -1760,12 +987,12 @@ keepalive_timer( gpointer data ) } static void -cursesDevIDReceived( void* closure, const XP_UCHAR* devID, +cursesDevIDReceived( void* closure, const XP_UCHAR* devID, XP_U16 maxInterval ) { - CursesAppGlobals* globals = (CursesAppGlobals*)closure; - CommonGlobals* cGlobals = &globals->cGlobals; - sqlite3* pDb = cGlobals->params->pDb; + CursesAppGlobals* aGlobals = (CursesAppGlobals*)closure; + // CommonGlobals* cGlobals = &globals->cGlobals; + sqlite3* pDb = aGlobals->cag.params->pDb; if ( !!devID ) { XP_LOGF( "%s(devID=%s)", __func__, devID ); @@ -1776,14 +1003,14 @@ cursesDevIDReceived( void* closure, const XP_UCHAR* devID, if ( !have ) { db_store( pDb, KEY_RDEVID, devID ); } - (void)g_timeout_add_seconds( maxInterval, keepalive_timer, globals ); + (void)g_timeout_add_seconds( maxInterval, keepalive_timer, aGlobals ); } else { XP_LOGF( "%s: bad relayid", __func__ ); db_remove( pDb, KEY_RDEVID ); DevIDType typ; - const XP_UCHAR* devID = linux_getDevID( cGlobals->params, &typ ); - relaycon_reg( cGlobals->params, NULL, typ, devID ); + const XP_UCHAR* devID = linux_getDevID( aGlobals->cag.params, &typ ); + relaycon_reg( aGlobals->cag.params, NULL, typ, devID ); } } @@ -1797,38 +1024,38 @@ cursesErrorMsgRcvd( void* closure, const XP_UCHAR* msg ) g_free( globals->lastErr ); globals->lastErr = g_strdup( msg ); const char* buttons[] = { "Ok" }; - (void)cursesask( globals, msg, VSIZE(buttons), buttons ); + (void)cursesask( globals->mainWin, msg, VSIZE(buttons), buttons ); } } -static gboolean -chatsTimerFired( gpointer data ) -{ - CursesAppGlobals* globals = (CursesAppGlobals*)data; - XWGame* game = &globals->cGlobals.game; - GameStateInfo gsi; +/* static gboolean */ +/* chatsTimerFired( gpointer data ) */ +/* { */ +/* CursesAppGlobals* globals = (CursesAppGlobals*)data; */ +/* XWGame* game = &globals->cGlobals.game; */ +/* GameStateInfo gsi; */ - game_getState( game, &gsi ); +/* game_getState( game, &gsi ); */ - if ( gsi.canChat && 3 > globals->nChatsSent ) { - XP_UCHAR msg[128]; - struct tm* timp; - struct timeval tv; - struct timezone tz; +/* if ( gsi.canChat && 3 > globals->nChatsSent ) { */ +/* XP_UCHAR msg[128]; */ +/* struct tm* timp; */ +/* struct timeval tv; */ +/* struct timezone tz; */ - gettimeofday( &tv, &tz ); - timp = localtime( &tv.tv_sec ); +/* gettimeofday( &tv, &tz ); */ +/* timp = localtime( &tv.tv_sec ); */ - snprintf( msg, sizeof(msg), "%x: Saying hi via chat at %.2d:%.2d:%.2d", - comms_getChannelSeed( game->comms ), - timp->tm_hour, timp->tm_min, timp->tm_sec ); - XP_LOGF( "%s: sending \"%s\"", __func__, msg ); - board_sendChat( game->board, msg ); - ++globals->nChatsSent; - } +/* snprintf( msg, sizeof(msg), "%x: Saying hi via chat at %.2d:%.2d:%.2d", */ +/* comms_getChannelSeed( game->comms ), */ +/* timp->tm_hour, timp->tm_min, timp->tm_sec ); */ +/* XP_LOGF( "%s: sending \"%s\"", __func__, msg ); */ +/* board_sendChat( game->board, msg ); */ +/* ++globals->nChatsSent; */ +/* } */ - return TRUE; -} +/* return TRUE; */ +/* } */ /* static XP_U16 */ /* feedBufferCurses( CommonGlobals* cGlobals, sqlite3_int64 rowid, */ @@ -1853,104 +1080,103 @@ chatsTimerFired( gpointer data ) /* /\* return seed; *\/ */ /* } */ -static void -smsMsgReceivedCurses( void* closure, const CommsAddrRec* from, - XP_U32 XP_UNUSED(gameID), - const XP_U8* buf, XP_U16 len ) -{ - LOG_FUNC(); - CommonGlobals* cGlobals = (CommonGlobals*)closure; - gameGotBuf( cGlobals, XP_TRUE, buf, len, from ); - LOG_RETURN_VOID(); - /* LaunchParams* params = cGlobals->params; */ +/* static void */ +/* smsMsgReceivedCurses( void* closure, const CommsAddrRec* from, */ +/* XP_U32 XP_UNUSED(gameID), */ +/* const XP_U8* buf, XP_U16 len ) */ +/* { */ +/* LOG_FUNC(); */ +/* CommonGlobals* cGlobals = (CommonGlobals*)closure; */ +/* gameGotBuf( cGlobals, XP_TRUE, buf, len, from ); */ +/* LOG_RETURN_VOID(); */ +/* /\* LaunchParams* params = cGlobals->params; *\/ */ - /* sqlite3_int64 rowids[4]; */ - /* int nRowIDs = VSIZE(rowids); */ - /* getRowsForGameID( params->pDb, gameID, rowids, &nRowIDs ); */ - /* for ( int ii = 0; ii < nRowIDs; ++ii ) { */ - /* gameGotBuf( cGlobals, XP_TRUE, buf, len, from ); */ - /* // feedBufferCurses( cGlobals, rowids[ii], buf, len, from ); */ - /* } */ -} +/* /\* sqlite3_int64 rowids[4]; *\/ */ +/* /\* int nRowIDs = VSIZE(rowids); *\/ */ +/* /\* getRowsForGameID( params->pDb, gameID, rowids, &nRowIDs ); *\/ */ +/* /\* for ( int ii = 0; ii < nRowIDs; ++ii ) { *\/ */ +/* /\* gameGotBuf( cGlobals, XP_TRUE, buf, len, from ); *\/ */ +/* /\* // feedBufferCurses( cGlobals, rowids[ii], buf, len, from ); *\/ */ +/* /\* } *\/ */ +/* } */ static void -curses_countChanged( void* XP_UNUSED(closure), XP_U16 newCount ) +onGameSaved( CursesAppGlobals* aGlobals, sqlite3_int64 rowid, bool isNew ) { - XP_LOGF( "%s(newCount=%d)", __func__, newCount ); + cgl_refreshOne( aGlobals->gameList, rowid, isNew ); } void -cursesmain( XP_Bool isServer, LaunchParams* params ) +cursesmain( XP_Bool XP_UNUSED(isServer), LaunchParams* params ) { - int width, height; - CommonGlobals* cGlobals = &g_globals.cGlobals; - memset( &g_globals, 0, sizeof(g_globals) ); + g_globals.cag.params = params; -#ifdef USE_GLIBLOOP - g_globals.loop = g_main_loop_new( NULL, FALSE ); -#endif - - g_globals.amServer = isServer; - g_globals.cGlobals.params = params; -#ifdef XWFEATURE_RELAY - g_globals.cGlobals.relaySocket = -1; -#endif - - g_globals.cGlobals.socketAdded = curses_socket_added; - g_globals.cGlobals.socketAddedClosure = &g_globals; - g_globals.cGlobals.onSave = curses_onGameSaved; - g_globals.cGlobals.onSaveClosure = &g_globals; - - g_globals.cGlobals.addAcceptor = curses_socket_acceptor; - - g_globals.cGlobals.cp.showBoardArrow = XP_TRUE; - g_globals.cGlobals.cp.showRobotScores = params->showRobotScores; - g_globals.cGlobals.cp.hideTileValues = params->hideValues; - g_globals.cGlobals.cp.skipCommitConfirm = params->skipCommitConfirm; - g_globals.cGlobals.cp.sortNewTiles = params->sortNewTiles; - g_globals.cGlobals.cp.showColors = params->showColors; - g_globals.cGlobals.cp.allowPeek = params->allowPeek; -#ifdef XWFEATURE_SLOW_ROBOT - g_globals.cGlobals.cp.robotThinkMin = params->robotThinkMin; - g_globals.cGlobals.cp.robotThinkMax = params->robotThinkMax; - g_globals.cGlobals.cp.robotTradePct = params->robotTradePct; -#endif - - g_globals.cGlobals.gi = ¶ms->pgi; - setupUtil( &g_globals.cGlobals ); - setupCursesUtilCallbacks( &g_globals, g_globals.cGlobals.util ); - - initFromParams( &g_globals.cGlobals, params ); - -#ifdef XWFEATURE_RELAY - if ( addr_hasType( ¶ms->addr, COMMS_CONN_RELAY ) ) { - g_globals.cGlobals.defaultServerName - = params->connInfo.relay.relayName; - } -#endif - -#ifdef USE_GLIBLOOP + initCurses( &g_globals ); if ( !params->closeStdin ) { - cursesListenOnSocket( &g_globals, 0, handle_stdin ); + g_globals.menuState = cmenu_init( g_globals.mainWin ); + cmenu_push( g_globals.menuState, &g_globals, g_sharedMenuList, NULL ); } - setOneSecondTimer( &g_globals.cGlobals ); + + g_globals.loop = g_main_loop_new( NULL, FALSE ); + + g_globals.cbState = cb_init( &g_globals, params, g_globals.menuState, + onGameSaved ); + + sqlite3* pDb = params->pDb; + g_globals.gameList = cgl_init( pDb, g_globals.winWidth, params->cursesListWinHt ); + cgl_refresh( g_globals.gameList ); + + // g_globals.amServer = isServer; +/* g_globals.cGlobals.params = params; */ +/* #ifdef XWFEATURE_RELAY */ +/* g_globals.cGlobals.relaySocket = -1; */ +/* #endif */ + +/* g_globals.cGlobals.socketAdded = curses_socket_added; */ +/* g_globals.cGlobals.socketAddedClosure = &g_globals; */ +/* g_globals.cGlobals.onSave = curses_onGameSaved; */ +/* g_globals.cGlobals.onSaveClosure = &g_globals; */ + +/* g_globals.cGlobals.addAcceptor = curses_socket_acceptor; */ + +/* g_globals.cGlobals.cp.showBoardArrow = XP_TRUE; */ +/* g_globals.cGlobals.cp.showRobotScores = params->showRobotScores; */ +/* g_globals.cGlobals.cp.hideTileValues = params->hideValues; */ +/* g_globals.cGlobals.cp.skipCommitConfirm = params->skipCommitConfirm; */ +/* g_globals.cGlobals.cp.sortNewTiles = params->sortNewTiles; */ +/* g_globals.cGlobals.cp.showColors = params->showColors; */ +/* g_globals.cGlobals.cp.allowPeek = params->allowPeek; */ +/* #ifdef XWFEATURE_SLOW_ROBOT */ +/* g_globals.cGlobals.cp.robotThinkMin = params->robotThinkMin; */ +/* g_globals.cGlobals.cp.robotThinkMax = params->robotThinkMax; */ +/* g_globals.cGlobals.cp.robotTradePct = params->robotTradePct; */ +/* #endif */ + + /* g_globals.cGlobals.gi = ¶ms->pgi; */ + /* setupUtil( &g_globals.cGlobals ); */ + /* setupCursesUtilCallbacks( &g_globals, g_globals.cGlobals.util ); */ + + // initFromParams( &g_globals.cGlobals, params ); + +#ifdef XWFEATURE_RELAY + /* if ( addr_hasType( ¶ms->addr, COMMS_CONN_RELAY ) ) { */ + /* g_globals.cGlobals.defaultServerName */ + /* = params->connInfo.relay.relayName; */ + /* } */ +#endif + + /* if ( !params->closeStdin ) { */ + /* cursesListenOnSocket( &g_globals, 0, handle_stdin ); */ + /* } */ + /* setOneSecondTimer( &g_globals.cGlobals ); */ # ifdef DEBUG int piperesult = # endif pipe( g_globals.quitpipe ); XP_ASSERT( piperesult == 0 ); - cursesListenOnSocket( &g_globals, g_globals.quitpipe[0], handle_quitwrite ); - -#else - cursesListenOnSocket( &g_globals, 0 ); /* stdin */ - - int piperesult = pipe( g_globals.timepipe ); - XP_ASSERT( piperesult == 0 ); - /* reader pipe */ - cursesListenOnSocket( &g_globals, g_globals.timepipe[0] ); -#endif + ADD_SOCKET( &g_globals, g_globals.quitpipe[0], handle_quitwrite ); struct sigaction act = { .sa_handler = SIGINTTERM_handler }; sigaction( SIGINT, &act, NULL ); @@ -1958,298 +1184,51 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) struct sigaction act2 = { .sa_handler = SIGWINCH_handler }; sigaction( SIGWINCH, &act2, NULL ); - TransportProcs procs = { - .closure = &g_globals, - .send = LINUX_SEND, -#ifdef COMMS_HEARTBEAT - .reset = linux_reset, -#endif -#ifdef XWFEATURE_RELAY - .rstatus = relay_status_curses, - .rconnd = relay_connd_curses, - .rerror = relay_error_curses, - .sendNoConn = relay_sendNoConn_curses, -#ifdef RELAY_VIA_HTTP - .requestJoin = relay_requestJoin_curses, -#endif - .countChanged = curses_countChanged, + if ( params->useUdp ) { + RelayConnProcs procs = { + .msgReceived = cursesGotBuf, + .msgForRow = cursesGotForRow, + .msgNoticeReceived = cursesNoticeRcvd, + .devIDReceived = cursesDevIDReceived, + .msgErrorMsg = cursesErrorMsgRcvd, + }; -# ifdef COMMS_XPORT_FLAGSPROC - .getFlags = curses_getFlags, -# endif -#endif - }; + relaycon_init( params, &procs, &g_globals, + params->connInfo.relay.relayName, + params->connInfo.relay.defaultSendPort ); - if ( !!params->pipe && !!params->fileName ) { - read_pipe_then_close( &g_globals.cGlobals, &procs ); - } else if ( !!params->nbs && !!params->fileName ) { - do_nbs_then_close( &g_globals.cGlobals, &procs ); - } else { - if ( 0 != params->chatsInterval ) { - (void)g_timeout_add_seconds( params->chatsInterval, chatsTimerFired, - &g_globals ); - } - - XP_Bool opened = XP_FALSE; - initCurses( &g_globals, &width, &height ); - - g_globals.draw = (struct CursesDrawCtx*) - cursesDrawCtxtMake( g_globals.boardWin ); - - XP_Bool idIsNew = XP_TRUE; - if ( !!params->dbName ) { - params->pDb = openGamesDB( params->dbName ); - - /* Check if we have a local ID already. If we do and it's - changed, we care. */ - idIsNew = linux_setupDevidParams( params ); - } - - if ( params->useUdp ) { - RelayConnProcs procs = { - .msgReceived = cursesGotBuf, - .msgForRow = cursesGotForRow, - .msgNoticeReceived = cursesNoticeRcvd, - .devIDReceived = cursesDevIDReceived, - .msgErrorMsg = cursesErrorMsgRcvd, - .socketAdded = curses_socket_added, - }; - - relaycon_init( params, &procs, &g_globals, - params->connInfo.relay.relayName, - params->connInfo.relay.defaultSendPort ); - - linux_doInitialReg( params, idIsNew ); - } - -#ifdef XWFEATURE_SMS - gchar buf[32]; - const gchar* myPhone = params->connInfo.sms.myPhone; - if ( !!myPhone ) { - db_store( params->pDb, KEY_SMSPHONE, myPhone ); - } else if ( !myPhone && db_fetch_safe( params->pDb, KEY_SMSPHONE, buf, VSIZE(buf) ) ) { - params->connInfo.sms.myPhone = myPhone = buf; - } - XP_U16 myPort = params->connInfo.sms.port; - gchar portbuf[8]; - if ( 0 < myPort ) { - sprintf( portbuf, "%d", myPort ); - db_store( params->pDb, KEY_SMSPORT, portbuf ); - } else if ( db_fetch_safe( params->pDb, KEY_SMSPORT, portbuf, VSIZE(portbuf) ) ) { - params->connInfo.sms.port = myPort = atoi( portbuf ); - } - - if ( !!myPhone && myPhone[0] && myPort ) { - SMSProcs smsProcs = { - .socketAdded = curses_socket_added, - .inviteReceived = NULL, - .msgReceived = smsMsgReceivedCurses, - }; - linux_sms_init( params, myPhone, myPort, &smsProcs, &g_globals.cGlobals ); - } - - if ( params->runSMSTest ) { - smsproto_runTests(g_globals.cGlobals.util->mpool, - g_globals.cGlobals.params->dutil ); - } -#endif - - XWStreamCtxt* stream = NULL; - if ( !!params->dbName ) { - GSList* games = listGames( params->pDb ); - if ( !!games ) { - XP_ASSERT( 1 == g_slist_length(games) ); /* for now */ - stream = mem_stream_make_raw( MEMPOOL params->vtMgr); - sqlite3_int64 selRow = *(sqlite3_int64*)games->data; - /* XP_UCHAR buf[32]; */ - /* XP_SNPRINTF( buf, sizeof(buf), "%lld", selRow ); */ - mpool_setTag( MEMPOOL params->dbName ); - if ( loadGame( stream, params->pDb, selRow ) ) { - g_globals.cGlobals.selRow = selRow; - } else { - stream_destroy( stream ); - stream = NULL; - } - freeGamesList( games ); - } - - } else if ( !!params->fileName && file_exists( params->fileName ) ) { - mpool_setTag( MEMPOOL "file" ); - stream = streamFromFile( &g_globals.cGlobals, params->fileName ); -#ifdef USE_SQLITE - } else if ( !!params->dbFileName && file_exists( params->dbFileName ) ) { - XP_UCHAR buf[32]; - XP_SNPRINTF( buf, sizeof(buf), "%d", params->dbFileID ); - mpool_setTag( MEMPOOL buf ); - stream = streamFromDB( &g_globals.cGlobals ); -#endif - } - - if ( NULL == cGlobals->dict ) { - if ( !!stream ) { - cGlobals->dict = makeDictForStream( cGlobals, stream ); - } else { - cGlobals->dict = - linux_dictionary_make( MEMPOOL params, - cGlobals->gi->dictName, XP_TRUE ); - } - } - cGlobals->gi->dictLang = dict_getLangCode( cGlobals->dict ); - - if ( !!stream ) { - (void)game_makeFromStream( MEMPOOL stream, &cGlobals->game, - cGlobals->gi, cGlobals->dict, - &cGlobals->dicts, cGlobals->util, - (DrawCtx*)g_globals.draw, - &g_globals.cGlobals.cp, &procs ); - - stream_destroy( stream ); - if ( !isServer && cGlobals->gi->serverRole == SERVER_ISSERVER ) { - isServer = XP_TRUE; - } - opened = XP_TRUE; - } - if ( !opened ) { - game_makeNewGame( MEMPOOL &cGlobals->game, cGlobals->gi, - cGlobals->util, (DrawCtx*)g_globals.draw, - &g_globals.cGlobals.cp, &procs -#ifdef SET_GAMESEED - ,params->gameSeed -#endif - ); - g_globals.cGlobals.selRow = -1; - saveGame( &g_globals.cGlobals ); - } - -#ifndef XWFEATURE_STANDALONE_ONLY - if ( cGlobals->game.comms ) { - CommsAddrRec addr = {0}; - - CommsConnType typ; - for ( XP_U32 st = 0; addr_iter( ¶ms->addr, &typ, &st ); ) { - switch( typ ) { -# ifdef XWFEATURE_RELAY - case COMMS_CONN_RELAY: - addr_addType( &addr, COMMS_CONN_RELAY ); - addr.u.ip_relay.ipAddr = 0; /* ??? */ - addr.u.ip_relay.port = params->connInfo.relay.defaultSendPort; - addr.u.ip_relay.seeksPublicRoom = params->connInfo.relay.seeksPublicRoom; - addr.u.ip_relay.advertiseRoom = params->connInfo.relay.advertiseRoom; - XP_STRNCPY( addr.u.ip_relay.hostName, params->connInfo.relay.relayName, - sizeof(addr.u.ip_relay.hostName) - 1 ); - XP_STRNCPY( addr.u.ip_relay.invite, params->connInfo.relay.invite, - sizeof(addr.u.ip_relay.invite) - 1 ); - break; -# endif -# ifdef XWFEATURE_SMS - case COMMS_CONN_SMS: - addr_addType( &addr, COMMS_CONN_SMS ); - XP_STRNCPY( addr.u.sms.phone, params->connInfo.sms.myPhone, - sizeof(addr.u.sms.phone) - 1 ); - addr.u.sms.port = params->connInfo.sms.port; - break; -# endif -# ifdef XWFEATURE_BLUETOOTH - case COMMS_CONN_BT: - addr_addType( &addr, COMMS_CONN_BT ); - XP_ASSERT( sizeof(addr.u.bt.btAddr) - >= sizeof(params->connInfo.bt.hostAddr)); - XP_MEMCPY( &addr.u.bt.btAddr, ¶ms->connInfo.bt.hostAddr, - sizeof(params->connInfo.bt.hostAddr) ); - break; -# endif -#ifdef XWFEATURE_DIRECTIP - case COMMS_CONN_IP_DIRECT: - addr_addType( &addr, COMMS_CONN_IP_DIRECT ); - XP_MEMCPY( addr.u.ip.hostName_ip, ¶ms->connInfo.ip.hostName, - sizeof(addr.u.ip.hostName_ip) ); - addr.u.ip.port_ip = params->connInfo.ip.hostPort; - break; -#endif - default: - break; - } - } - comms_setAddr( cGlobals->game.comms, &addr ); - } -#endif - - model_setDictionary( cGlobals->game.model, cGlobals->dict ); - setSquareBonuses( cGlobals ); - positionSizeStuff( &g_globals, width, height ); - -#ifndef XWFEATURE_STANDALONE_ONLY - /* send any events that need to get off before the event loop begins */ - if ( !!cGlobals->game.comms && !isServer ) { - (void)server_initClientConnection( cGlobals->game.server, - mem_stream_make( MEMPOOL - params->vtMgr, - cGlobals, - (XP_PlayerAddr)0, - sendOnClose ) ); - } -#endif - - server_do( g_globals.cGlobals.game.server ); - - g_globals.menuList = g_boardMenuList; - drawMenuLargeOrSmall( &g_globals, g_boardMenuList ); - board_draw( g_globals.cGlobals.game.board ); - -#ifdef USE_GLIBLOOP - g_main_loop_run( g_globals.loop ); -#else - while ( !g_globals.timeToExit ) { - int ch = 0; - if ( blocking_gotEvent( &g_globals, &ch ) ) { - remapKey( &ch ); - if ( -#ifdef CURSES_SMALL_SCREEN - handleKeyEvent( &g_globals, g_rootMenuListShow, ch ) || -#endif - handleKeyEvent( &g_globals, g_globals.menuList, ch ) - || handleKeyEvent( &g_globals, g_sharedMenuList, ch ) - || passKeyToBoard( &g_globals, ch ) ) { - if ( g_globals.doDraw ) { - board_draw( g_globals.cGlobals.game.board ); - g_globals.doDraw = XP_FALSE; - } - } - } - } -#endif + XP_Bool idIsNew = linux_setupDevidParams( params ); + linux_doInitialReg( params, idIsNew ); } - if ( !!g_globals.cGlobals.game.comms ) { - comms_stop( g_globals.cGlobals.game.comms ); - } - saveGame( &g_globals.cGlobals ); - game_dispose( &g_globals.cGlobals.game ); - gi_disposePlayerInfo( MEMPOOL cGlobals->gi ); - dict_unref( cGlobals->dict ); + if ( 0 == cgl_getNGames( g_globals.gameList ) ) { + handleNewGame( &g_globals, 0 ); + } + + g_main_loop_run( g_globals.loop ); + + cb_closeAll( g_globals.cbState ); #ifdef XWFEATURE_BLUETOOTH - linux_bt_close( &g_globals.cGlobals ); + // linux_bt_close( &g_globals.cGlobals ); #endif #ifdef XWFEATURE_SMS // linux_sms_close( &g_globals.cGlobals ); #endif #ifdef XWFEATURE_IP_DIRECT - linux_udp_close( &g_globals.cGlobals ); + // linux_udp_close( &g_globals.cGlobals ); #endif + cgl_destroy( g_globals.gameList ); + endwin(); device_store( params->dutil ); - if ( !!params->dbName ) { - closeGamesDB( params->pDb ); - params->pDb = NULL; + if ( params->useUdp ) { + relaycon_cleanup( params ); } - relaycon_cleanup( params ); linux_sms_cleanup( params ); - - linux_util_vt_destroy( g_globals.cGlobals.util ); } /* cursesmain */ #endif /* PLATFORM_NCURSES */ diff --git a/xwords4/linux/cursesmain.h b/xwords4/linux/cursesmain.h index 59443e652..940fb3d86 100644 --- a/xwords4/linux/cursesmain.h +++ b/xwords4/linux/cursesmain.h @@ -1,6 +1,6 @@ /* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ /* - * Copyright 1997-2000 by Eric House (xwords@eehouse.org). All rights reserved. + * Copyright 1997-2020 by Eric House (xwords@eehouse.org). All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -39,11 +39,17 @@ #include "server.h" #include "xwstate.h" #include "util.h" +#include "cursesmenu.h" +#include "cursesboard.h" /* #include "compipe.h" */ typedef struct CursesAppGlobals CursesAppGlobals; +typedef struct CursesBoardGlobals CursesBoardGlobals; typedef XP_Bool (*EventFunc)(CursesAppGlobals* globals, int ch); +void onCursesBoardClosing( CursesAppGlobals* aGlobals, CursesBoardGlobals* bGlobals ); +void onCursesGameSaved( CursesAppGlobals* aGlobals, sqlite3_int64 rowid ); + /* typedef void (*MenuDrawer)(CursesAppGlobals* globals); */ #define FD_MAX 6 @@ -51,56 +57,10 @@ typedef XP_Bool (*EventFunc)(CursesAppGlobals* globals, int ch); #define FD_TIMEEVT 1 #define FD_FIRSTSOCKET 2 -struct CursesAppGlobals { - CommonGlobals cGlobals; - - struct CursesDrawCtx* draw; - - DictionaryCtxt* dictionary; - EngineCtxt* engine; - - XP_Bool amServer; /* this process acting as server */ - - WINDOW* mainWin; - WINDOW* menuWin; - WINDOW* boardWin; - - XP_Bool doDraw; - const struct MenuList* menuList; - XP_U16 nLinesMenu; - gchar* lastErr; - - XP_U16 nChatsSent; - XP_U16 nextQueryTimeSecs; - - union { - struct { - XWStreamCtxt* stream; /* how we can reach the server */ - } client; - struct { - int serverSocket; - XP_Bool socketOpen; - } server; - } csInfo; - - short statusLine; - XWGameState state; - CommsRelayState commsRelayState; - - struct sockaddr_in listenerSockAddr; -#ifdef USE_GLIBLOOP - GMainLoop* loop; - GList* sources; - int quitpipe[2]; -#else - XP_Bool timeToExit; - short fdCount; - struct pollfd fdArray[FD_MAX]; /* one for stdio, one for listening socket */ - int timepipe[2]; /* for reading/writing "user events" */ -#endif -}; +// typedef struct CursesBoardGlobals; DrawCtx* cursesDrawCtxtMake( WINDOW* boardWin ); +void cursesDrawCtxtFree( DrawCtx* dctx ); /* Ports: Client and server pick a port at startup on which they'll listen. * If both are to be on the same device using localhost as their ip address, @@ -114,5 +74,6 @@ DrawCtx* cursesDrawCtxtMake( WINDOW* boardWin ); void cursesmain( XP_Bool isServer, LaunchParams* params ); +bool handleQuit( void* closure, int unused_key ); #endif diff --git a/xwords4/linux/cursesmenu.c b/xwords4/linux/cursesmenu.c new file mode 100644 index 000000000..890359be8 --- /dev/null +++ b/xwords4/linux/cursesmenu.c @@ -0,0 +1,213 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997-2020 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#include +#include + +#include "cursesmenu.h" +#include "xptypes.h" +#include "comtypes.h" +#include "cursesmain.h" +#include "linuxmain.h" +#include "gsrcwrap.h" + +struct CursesMenuState { + WINDOW* menuWin; + GSList* menuLists; +}; + +static gboolean +handle_stdin( GIOChannel* XP_UNUSED_DBG(source), GIOCondition condition, + gpointer data ) +{ + if ( 0 != (G_IO_IN & condition) ) { +#ifdef DEBUG + gint fd = g_io_channel_unix_get_fd( source ); + XP_ASSERT( 0 == fd ); +#endif + CursesMenuState* state = (CursesMenuState*)data; + int ch = wgetch( state->menuWin ); + cmenu_handleKeyEvent( state, ch ); + } + return TRUE; +} + + +CursesMenuState* +cmenu_init( WINDOW* mainWindow ) +{ + CursesMenuState* result = g_malloc0( sizeof(*result) ); + + int width, height; + getmaxyx( mainWindow, height, width ); + result->menuWin = newwin( MENU_WINDOW_HEIGHT, width, + height-MENU_WINDOW_HEIGHT, 0 ); + nodelay( result->menuWin, 1 ); /* don't block on getch */ + + ADD_SOCKET( result, 0, handle_stdin ); + + return result; +} + +void +cmenu_dispose( CursesMenuState* state ) +{ + XP_ASSERT( !!state ); + g_free( state ); +} + +typedef struct _MenuListElem { + MenuList* list; + void* closure; +} MenuListElem; +#define PUSH_TOKEN ((MenuListElem*)-1) + +void +cmenu_pop( CursesMenuState* state ) +{ + /* pop off the front of the list until we've popped a PUSH_TOKEN */ + for ( ; ; ) { + MenuListElem* elem = state->menuLists->data; + state->menuLists = g_slist_remove_link( state->menuLists, + state->menuLists ); + if ( PUSH_TOKEN == elem ) { + break; + } else { + g_free( elem ); + } + } + cmenu_draw( state ); +} + +static void +addMenus( CursesMenuState* state, void* closure, va_list ap ) +{ + for ( ; ; ) { + MenuList* param = va_arg(ap, MenuList*); + if ( !param ) { + break; + } + MenuListElem* elem = g_malloc0( sizeof( *elem ) ); + elem->closure = closure; + elem->list = param; + state->menuLists = g_slist_prepend( state->menuLists, elem ); + } + cmenu_draw( state ); +} + +void +cmenu_addMenus( CursesMenuState* state, void* closure, ... ) +{ + va_list ap; + va_start( ap, closure ); + addMenus( state, closure, ap ); + va_end(ap); +} + +void cmenu_push( CursesMenuState* state, void* closure, ... ) +{ + if ( !!state ) { + state->menuLists = g_slist_prepend( state->menuLists, PUSH_TOKEN ); + + va_list ap; + va_start( ap, closure ); + addMenus( state, closure, ap ); + va_end(ap); + } +} + +void +cmenu_removeMenus( CursesMenuState* state, ... ) +{ + va_list ap; + va_start( ap, state ); + for ( ; ; ) { + MenuList* param = va_arg(ap, MenuList*); + if ( !param ) { + break; + } + for ( GSList* iter = state->menuLists; !!iter; iter = iter->next ) { + MenuListElem* elem = iter->data; + if ( elem->list == param ) { + state->menuLists = g_slist_remove( state->menuLists, elem ); + break; + } + } + } + va_end( ap ); + + cmenu_draw( state ); +} + +bool +cmenu_handleKeyEvent( CursesMenuState* state, char ch ) +{ + bool result = false; + for ( GSList* iter = state->menuLists; !!iter; iter = iter->next ) { + const MenuListElem* elem = iter->data; + if ( PUSH_TOKEN == elem ) { + break; + } + for ( const MenuList* list = elem->list; !!list->handler; ++list ) { + if ( list->key == ch ) { + result = (*list->handler)(elem->closure, ch); + goto done; + } + } + } + done: + return result; +} + +static void +fmtMenuItem( const MenuList* item, char* buf, int maxLen ) +{ + snprintf( buf, maxLen, "%s %s", item->keyDesc, item->desc ); +} + +void +cmenu_draw( const CursesMenuState* state ) +{ + WINDOW* win = state->menuWin; + wclear( win ); + int line = 0; + int col = 0; + int maxLen = 0; + for ( GSList* iter = state->menuLists; !!iter; iter = iter->next ) { + const MenuListElem* elem = iter->data; + if ( PUSH_TOKEN == elem ) { + break; + } + for ( MenuList* list = elem->list; !!list->handler; ++list ) { + char buf[32]; + fmtMenuItem( list, buf, sizeof(buf) ); + int len = strlen(buf); + if ( maxLen < len ) { + maxLen = len; + } + + mvwaddstr( win, line, col, buf ); + if ( ++line >= MENU_WINDOW_HEIGHT ) { + line = 0; + col += maxLen + 1; + maxLen = 0; + } + } + } + wrefresh( win ); +} diff --git a/xwords4/linux/cursesmenu.h b/xwords4/linux/cursesmenu.h new file mode 100644 index 000000000..397db0dfa --- /dev/null +++ b/xwords4/linux/cursesmenu.h @@ -0,0 +1,54 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997-2020 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _CURSESMENU_H_ +#define _CURSESMENU_H_ + +#include + +#ifdef CURSES_SMALL_SCREEN +# define MENU_WINDOW_HEIGHT 1 +# define BOARD_OFFSET 0 +#else +# define MENU_WINDOW_HEIGHT 5 /* three lines plus borders */ +# define BOARD_OFFSET 1 +#endif + +typedef bool (*CursesMenuHandler)( void* closure, int key ); +typedef struct MenuList { + CursesMenuHandler handler; + char* desc; + char* keyDesc; + char key; +} MenuList; + +typedef struct CursesMenuState CursesMenuState; + +CursesMenuState* cmenu_init( WINDOW* mainWindow ); +void cmenu_dispose( CursesMenuState* state ); + +void cmenu_clearMenus( CursesMenuState* state ); +void cmenu_draw( const CursesMenuState* state ); +void cmenu_addMenus( CursesMenuState* state, void* closure, ... ); +void cmenu_push( CursesMenuState* state, void* closure, ... ); +void cmenu_pop( CursesMenuState* state ); +void cmenu_removeMenus( CursesMenuState* state, ... ); +bool cmenu_handleKeyEvent( CursesMenuState* state, char ch ); + +#endif diff --git a/xwords4/linux/gamesdb.c b/xwords4/linux/gamesdb.c index efe30f397..3c0b6d4c1 100644 --- a/xwords4/linux/gamesdb.c +++ b/xwords4/linux/gamesdb.c @@ -59,10 +59,14 @@ openGamesDB( const char* dbName ) ",local INT(1)" ",nmoves INT" ",seed INT" + ",nPending INT" + ",role INT" + ",dictlang INT" ",gameid INT" ",ntotal INT(2)" ",nmissing INT(2)" ",lastMoveTime INT" + ",scores TEXT" ",dupTimerExpires INT" ")"; result = sqlite3_exec( pDb, createGamesStr, NULL, NULL, NULL ); @@ -161,17 +165,17 @@ void writeToDB( XWStreamCtxt* stream, void* closure ) { CommonGlobals* cGlobals = (CommonGlobals*)closure; - sqlite3_int64 selRow = cGlobals->selRow; + sqlite3_int64 selRow = cGlobals->rowid; sqlite3* pDb = cGlobals->params->pDb; XP_Bool newGame = -1 == selRow; selRow = writeBlobColumnStream( stream, pDb, selRow, "game" ); if ( newGame ) { /* new row; need to insert blob first */ - cGlobals->selRow = selRow; + cGlobals->rowid = selRow; XP_LOGF( "%s(): new game at row %lld", __func__, selRow ); } else { - assert( selRow == cGlobals->selRow ); + assert( selRow == cGlobals->rowid ); } (*cGlobals->onSave)( cGlobals->onSaveClosure, selRow, newGame ); @@ -193,8 +197,8 @@ addSnapshot( CommonGlobals* cGlobals ) cGlobals->params->vtMgr ); getImage( dctx, stream ); removeSurface( dctx ); - cGlobals->selRow = writeBlobColumnStream( stream, cGlobals->params->pDb, - cGlobals->selRow, "snap" ); + cGlobals->rowid = writeBlobColumnStream( stream, cGlobals->params->pDb, + cGlobals->rowid, "snap" ); stream_destroy( stream ); } @@ -215,8 +219,11 @@ summarize( CommonGlobals* cGlobals ) XP_U32 lastMoveTime = server_getLastMoveTime( game->server ); XP_U16 seed = 0; XP_S16 nMissing = 0; - XP_U16 nTotal = cGlobals->gi->nPlayers; - XP_U32 gameID = cGlobals->gi->gameID; + XP_U16 nPending = 0; + const CurGameInfo* gi = cGlobals->gi; + XP_U16 nTotal = gi->nPlayers; + XP_U32 gameID = gi->gameID; + XP_LangCode dictLang = gi->dictLang; XP_ASSERT( 0 != gameID ); CommsAddrRec addr = {0}; gchar* room = ""; @@ -225,6 +232,24 @@ summarize( CommonGlobals* cGlobals ) gchar connvia[128] = {0}; XP_UCHAR relayID[32] = {0}; + ScoresArray scores = {0}; + if ( gameOver ) { + model_figureFinalScores( game->model, &scores, NULL ); + } else { + for ( int ii = 0; ii < nTotal; ++ii ) { + scores.arr[ii] = model_getPlayerScore( game->model, ii ); + } + } + gchar scoreBufs[MAX_NUM_PLAYERS][64] = {0}; + gchar* arr[MAX_NUM_PLAYERS+1] = {0}; + for ( int ii = 0; ii < nTotal; ++ii ) { + XP_SNPRINTF( scoreBufs[ii], VSIZE(scoreBufs[ii]), "%s: %d", + gi->players[ii].name, scores.arr[ii] ); + arr[ii] = scoreBufs[ii]; + } + gchar* scoresStr = g_strjoinv( "; ", arr ); + XP_LOGF( "%s(): scoresStr: %s", __func__, scoresStr ); + if ( !!game->comms ) { nMissing = server_getMissingPlayers( game->server ); comms_getAddr( game->comms, &addr ); @@ -255,19 +280,21 @@ summarize( CommonGlobals* cGlobals ) seed = comms_getChannelSeed( game->comms ); XP_U16 len = VSIZE(relayID); (void)comms_getRelayID( game->comms, relayID, &len ); + + nPending = comms_countPendingPackets( game->comms ); } else { strcat( connvia, "local" ); } const char* fmt = "UPDATE games " " SET room='%s', ended=%d, turn=%d, local=%d, ntotal=%d, " - " nmissing=%d, nmoves=%d, seed=%d, gameid=%d, connvia='%s', " - " relayid='%s', lastMoveTime=%d" + " nmissing=%d, nmoves=%d, seed=%d, dictlang=%d, gameid=%d, connvia='%s', " + " relayid='%s', lastMoveTime=%d, scores='%s', nPending=%d, role=%d" " WHERE rowid=%lld"; - XP_UCHAR buf[256]; + XP_UCHAR buf[2*256]; snprintf( buf, sizeof(buf), fmt, room, gameOver?1:0, turn, isLocal?1:0, - nTotal, nMissing, nMoves, seed, gameID, connvia, relayID, lastMoveTime, - cGlobals->selRow ); + nTotal, nMissing, nMoves, seed, dictLang, gameID, connvia, relayID, lastMoveTime, + scoresStr, nPending, gi->serverRole, cGlobals->rowid ); XP_LOGF( "query: %s", buf ); sqlite3_stmt* stmt = NULL; int result = sqlite3_prepare_v2( cGlobals->params->pDb, buf, -1, &stmt, NULL ); @@ -283,6 +310,7 @@ summarize( CommonGlobals* cGlobals ) if ( !cGlobals->params->useCurses ) { addSnapshot( cGlobals ); } + g_free( scoresStr ); } GSList* @@ -370,7 +398,7 @@ getGameInfo( sqlite3* pDb, sqlite3_int64 rowid, GameInfo* gib ) { XP_Bool success = XP_FALSE; const char* fmt = "SELECT room, ended, turn, local, nmoves, ntotal, nmissing, " - "seed, connvia, gameid, lastMoveTime, relayid, snap " + "dictlang, seed, connvia, gameid, lastMoveTime, relayid, scores, nPending, role, snap " "FROM games WHERE rowid = %lld"; XP_UCHAR query[256]; snprintf( query, sizeof(query), fmt, rowid ); @@ -384,12 +412,14 @@ getGameInfo( sqlite3* pDb, sqlite3_int64 rowid, GameInfo* gib ) int col = 0; int len = sizeof(gib->room); getColumnText( ppStmt, col++, gib->room, &len ); + gib->rowid = rowid; gib->gameOver = 1 == sqlite3_column_int( ppStmt, col++ ); gib->turn = sqlite3_column_int( ppStmt, col++ ); gib->turnLocal = 1 == sqlite3_column_int( ppStmt, col++ ); gib->nMoves = sqlite3_column_int( ppStmt, col++ ); gib->nTotal = sqlite3_column_int( ppStmt, col++ ); gib->nMissing = sqlite3_column_int( ppStmt, col++ ); + gib->dictLang = sqlite3_column_int( ppStmt, col++ ); gib->seed = sqlite3_column_int( ppStmt, col++ ); len = sizeof(gib->conn); getColumnText( ppStmt, col++, gib->conn, &len ); @@ -397,6 +427,10 @@ getGameInfo( sqlite3* pDb, sqlite3_int64 rowid, GameInfo* gib ) gib->lastMoveTime = sqlite3_column_int( ppStmt, col++ ); len = sizeof(gib->relayID); getColumnText( ppStmt, col++, gib->relayID, &len ); + len = sizeof(gib->scores); + getColumnText( ppStmt, col++, gib->scores, &len ); + gib->nPending = sqlite3_column_int( ppStmt, col++ ); + gib->role = sqlite3_column_int( ppStmt, col++ ); snprintf( gib->name, sizeof(gib->name), "Game %lld", rowid ); #ifdef PLATFORM_GTK @@ -533,8 +567,11 @@ db_fetch( sqlite3* pDb, const gchar* key, gchar* buf, gint* buflen ) XP_ASSERT( !!pDb ); FetchResult result = NOT_THERE; char query[256]; - int len = snprintf( query, sizeof(query), - "SELECT value from pairs where key = '%s'", key ); +#ifdef DEBUG + int len = +#endif + snprintf( query, sizeof(query), + "SELECT value from pairs where key = '%s'", key ); XP_ASSERT( len < sizeof(query) ); sqlite3_stmt *ppStmt; int sqlResult = sqlite3_prepare_v2( pDb, query, -1, &ppStmt, NULL ); diff --git a/xwords4/linux/gamesdb.h b/xwords4/linux/gamesdb.h index 7ba93d5a3..fa6a04745 100644 --- a/xwords4/linux/gamesdb.h +++ b/xwords4/linux/gamesdb.h @@ -28,13 +28,16 @@ #include "comtypes.h" typedef struct _GameInfo { + sqlite3_int64 rowid; XP_UCHAR name[128]; XP_UCHAR room[128]; XP_UCHAR conn[128]; + XP_UCHAR scores[128]; XP_UCHAR relayID[32]; #ifdef PLATFORM_GTK GdkPixbuf* snap; #endif + XP_LangCode dictLang; XP_U32 gameID; XP_S16 nMoves; XP_Bool gameOver; @@ -43,7 +46,9 @@ typedef struct _GameInfo { XP_U16 nTotal; XP_S16 nMissing; XP_U16 seed; + XP_U16 nPending; XP_U32 lastMoveTime; + XP_U16 role; } GameInfo; sqlite3* openGamesDB( const char* dbName ); diff --git a/xwords4/linux/gsrcwrap.c b/xwords4/linux/gsrcwrap.c new file mode 100644 index 000000000..41b38c297 --- /dev/null +++ b/xwords4/linux/gsrcwrap.c @@ -0,0 +1,183 @@ +/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ +/* + * Copyright 2020 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#include +#include +#include +#include "gsrcwrap.h" +#include "xptypes.h" +#include "comtypes.h" + +#define TAG __FILE__ ": " + +typedef struct _WrapperState { + union { + GSourceFunc srcProc; + GIOFunc ioProc; + } proc; + void* data; + const char* caller; + const char* procName; + struct timespec spec; +} WrapperState; + +static GSList* s_idleProcs = NULL; + +static void +printElapsedFor( struct timespec* first, struct timespec* second, + const char* XP_UNUSED_DBG(proc), + const char* XP_UNUSED_DBG(action) ) + /* time_t tv_sec; /\* seconds *\/ */ + /* long tv_nsec; /\* nanoseconds *\/ */ +{ + time_t secs = second->tv_sec - first->tv_sec; + long nsecs = second->tv_nsec - first->tv_nsec; + while ( nsecs < 0 ) { + ++secs; + nsecs += 1000000000; + } + + /* float firstSecs = (float)first->tv_sec + (((float)first->tv_nsec)/1000000000.0f); */ + /* float secondSecs = (float)second->tv_sec + (((float)second->tv_nsec)/1000000000.0f); */ + XP_LOGF( TAG "elapsed %s %s(): %ld.%ld", action, proc, secs, nsecs ); +} + + +static gint +idle_wrapper( gpointer data ) +{ + WrapperState* state = (WrapperState*)data; + XP_LOGF( TAG "%s(): CALLED for %s", __func__, state->procName ); + + struct timespec callTime; + clock_gettime(CLOCK_REALTIME, &callTime); + printElapsedFor( &state->spec, &callTime, state->procName, "scheduling" ); + + gint result = (*state->proc.srcProc)(state->data); + + struct timespec returnTime; + clock_gettime( CLOCK_REALTIME, &returnTime ); + + printElapsedFor( &callTime, &returnTime, state->procName, "running" ); + +#ifdef DEBUG + const char* procName = state->procName; +#endif + if ( 0 == result ) { /* won't be getting called again */ + s_idleProcs = g_slist_remove( s_idleProcs, state ); + g_free( state ); + } + + XP_LOGF( TAG "%s(): DONE for %s; now have %d", __func__, procName, + g_slist_length(s_idleProcs) ); + + return result; +} + +guint +_wrapIdle( GSourceFunc function, gpointer data, + const char* procName, const char* caller ) +{ + XP_LOGF( TAG "%s(): installing proc %s from caller %s", __func__, + procName, caller ); + WrapperState* state = g_malloc0( sizeof(*state) ); + s_idleProcs = g_slist_append( s_idleProcs, state ); + state->proc.srcProc = function; + state->data = data; + state->caller = caller; + state->procName = procName; + clock_gettime(CLOCK_REALTIME, &state->spec); + + guint res = g_idle_add( idle_wrapper, state ); + XP_LOGF( TAG "%s(): added idle for %s; now have %d", __func__, + procName, g_slist_length(s_idleProcs) ); + return res; +} + +#define TRY_ONE(FLAG) \ + if ((condition & FLAG) == FLAG) { \ + offset += snprintf( &buf[offset], len - offset, #FLAG " " ); \ + } +static void +formatFlags( char* buf, size_t len, GIOCondition condition ) +{ + int offset = 0; + TRY_ONE(G_IO_IN); + TRY_ONE(G_IO_HUP); + TRY_ONE(G_IO_ERR); + TRY_ONE(G_IO_PRI); +} + +static gboolean +watch_wrapper( GIOChannel* source, GIOCondition condition, gpointer data ) +{ + WrapperState* state = (WrapperState*)data; + + char buf[128] = {0}; + formatFlags( buf, VSIZE(buf), condition ); + XP_LOGF( TAG "%s(%s): CALLED; flags: %s", __func__, state->procName, buf ); + + struct timespec callTime; + clock_gettime(CLOCK_REALTIME, &callTime); + + bool keep = (*state->proc.ioProc)(source, condition, state->data); + + struct timespec returnTime; + clock_gettime( CLOCK_REALTIME, &returnTime ); + + printElapsedFor( &callTime, &returnTime, state->procName, "running" ); + + XP_LOGF( TAG "%s(%s): DONE", __func__, state->procName ); + + if ( 0 == keep ) { /* won't be getting called again */ + // g_source_destroy( source ); + g_free( state ); + } + return keep; +} + +guint +_wrapWatch( gpointer data, int socket, GIOFunc ioProc, + const char* procName, const char* caller ) +{ + XP_LOGF( TAG "%s(): installing proc %s from caller %s", __func__, + procName, caller ); + + WrapperState* state = g_malloc0( sizeof(*state) ); + state->proc.ioProc = ioProc; + state->data = data; + state->caller = caller; + state->procName = procName; + + GIOChannel* channel = g_io_channel_unix_new( socket ); + guint watch = g_io_add_watch( channel, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_PRI, + watch_wrapper, state ); + g_io_channel_unref( channel ); /* only main loop holds it now */ + + XP_LOGF( TAG "%s(): added watch for %s", __func__, procName ); + return watch; +} + +void +gsw_logIdles() +{ + XP_LOGF( TAG "%s(): %d idles pending", __func__, g_slist_length(s_idleProcs) ); +} diff --git a/xwords4/linux/gsrcwrap.h b/xwords4/linux/gsrcwrap.h new file mode 100644 index 000000000..692c183f7 --- /dev/null +++ b/xwords4/linux/gsrcwrap.h @@ -0,0 +1,37 @@ +/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ +/* + * Copyright 2020 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#ifndef _GSRCWRAP_H_ +#define _GSRCWRAP_H_ + +guint _wrapIdle( GSourceFunc function, gpointer data, const char* procName, + const char* caller ); +guint _wrapWatch( gpointer data, int socket, GIOFunc func, + const char* procName, const char* caller ); + +void gsw_logIdles(); + +#define ADD_ONETIME_IDLE( PROC, CLOSURE ) \ + _wrapIdle( PROC, CLOSURE, #PROC, __func__ ) + +#define ADD_SOCKET( CLOSURE, SOCKET, PROC ) \ + _wrapWatch( CLOSURE, SOCKET, PROC, #PROC, __func__ ) + +#endif diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index 0393bf9f5..d00704bff 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -323,10 +323,11 @@ relay_status_gtk( void* closure, CommsRelayState state ) { XP_LOGF( "%s got status: %s", __func__, CommsRelayState2Str(state) ); GtkGameGlobals* globals = (GtkGameGlobals*)closure; - if ( !!globals->draw ) { - globals->cGlobals.state = state; + CommonGlobals* cGlobals = &globals->cGlobals; + if ( !!cGlobals->draw ) { + cGlobals->state = state; globals->stateChar = 'A' + COMMS_RELAYSTATE_ALLCONNECTED - state; - draw_gtk_status( globals->draw, globals->stateChar ); + draw_gtk_status( (GtkDrawCtx*)cGlobals->draw, globals->stateChar ); } } @@ -419,10 +420,11 @@ relay_sendNoConn_gtk( const XP_U8* msg, XP_U16 len, { GtkGameGlobals* globals = (GtkGameGlobals*)closure; XP_Bool success = XP_FALSE; - LaunchParams* params = globals->cGlobals.params; - if ( params->useUdp && !globals->draw ) { - XP_U16 seed = comms_getChannelSeed( globals->cGlobals.game.comms ); - XP_U32 clientToken = makeClientToken( globals->cGlobals.selRow, seed ); + CommonGlobals* cGlobals = &globals->cGlobals; + LaunchParams* params = cGlobals->params; + if ( params->useUdp && !cGlobals->draw ) { + XP_U16 seed = comms_getChannelSeed( cGlobals->game.comms ); + XP_U32 clientToken = makeClientToken( cGlobals->rowid, seed ); XP_S16 nSent = relaycon_sendnoconn( params, msg, len, relayID, clientToken ); success = nSent == len; @@ -430,18 +432,6 @@ relay_sendNoConn_gtk( const XP_U8* msg, XP_U16 len, return success; } /* relay_sendNoConn_gtk */ -static void -tryConnectToServer(CommonGlobals* cGlobals) -{ - LaunchParams* params = cGlobals->params; - XWStreamCtxt* stream = - mem_stream_make( MPPARM(cGlobals->util->mpool) params->vtMgr, - cGlobals, CHANNEL_NONE, - sendOnClose ); - (void)server_initClientConnection( cGlobals->game.server, - stream ); -} - #ifdef RELAY_VIA_HTTP static void onJoined( void* closure, const XP_UCHAR* connname, XWHostID hid ) @@ -478,7 +468,7 @@ gtk_getFlags( void* closure ) XP_USE( globals ); return COMMS_XPORT_FLAGS_HASNOCONN; # else - return (!!globals->draw) ? COMMS_XPORT_FLAGS_NONE + return (!!globals->cGlobals.draw) ? COMMS_XPORT_FLAGS_NONE : COMMS_XPORT_FLAGS_HASNOCONN; # endif } @@ -584,167 +574,25 @@ addDropChecks( GtkGameGlobals* globals ) static void createOrLoadObjects( GtkGameGlobals* globals ) { - XWStreamCtxt* stream = NULL; - XP_Bool opened = XP_FALSE; - #ifndef XWFEATURE_STANDALONE_ONLY #endif CommonGlobals* cGlobals = &globals->cGlobals; LaunchParams* params = cGlobals->params; - globals->draw = (GtkDrawCtx*)gtkDrawCtxtMake( globals->drawing_area, - globals ); + cGlobals->draw = gtkDrawCtxtMake( globals->drawing_area, + globals ); TransportProcs procs; setTransportProcs( &procs, globals ); - - if ( !!params->fileName && file_exists( params->fileName ) ) { - stream = streamFromFile( cGlobals, params->fileName ); -#ifdef USE_SQLITE - } else if ( !!params->dbFileName && file_exists( params->dbFileName ) ) { - XP_UCHAR buf[32]; - XP_SNPRINTF( buf, sizeof(buf), "%d", params->dbFileID ); - mpool_setTag( MEMPOOL buf ); - stream = streamFromDB( cGlobals ); -#endif - } else if ( !!params->pDb && 0 <= cGlobals->selRow ) { - stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool) - params->vtMgr ); - if ( !loadGame( stream, params->pDb, cGlobals->selRow ) ) { - stream_destroy( stream ); - stream = NULL; - } - } - - if ( !!stream ) { - if ( NULL == cGlobals->dict ) { - cGlobals->dict = makeDictForStream( cGlobals, stream ); - } - - opened = game_makeFromStream( MEMPOOL stream, &cGlobals->game, - cGlobals->gi, cGlobals->dict, - &cGlobals->dicts, cGlobals->util, - (DrawCtx*)globals->draw, - &cGlobals->cp, &procs ); - XP_LOGF( "%s: loaded gi at %p", __func__, &cGlobals->gi ); - stream_destroy( stream ); - } - - if ( !opened ) { - CommsAddrRec addr = cGlobals->addr; - - /* XP_MEMSET( &addr, 0, sizeof(addr) ); */ - /* addr.conType = cGlobals->addr.conType; */ - -#ifdef XWFEATURE_RELAY - /* if ( addr.conType == COMMS_CONN_RELAY ) { */ - /* XP_ASSERT( !!params->connInfo.relay.relayName ); */ - /* globals->cGlobals.defaultServerName */ - /* = params->connInfo.relay.relayName; */ - /* } */ -#endif - game_makeNewGame( MEMPOOL &cGlobals->game, cGlobals->gi, - cGlobals->util, (DrawCtx*)globals->draw, - &cGlobals->cp, &procs -#ifdef SET_GAMESEED - , params->gameSeed -#endif - ); - - // addr.conType = params->conType; - CommsConnType typ; - for ( XP_U32 st = 0; addr_iter( &addr, &typ, &st ); ) { - if ( params->commsDisableds[typ][0] ) { - comms_setAddrDisabled( cGlobals->game.comms, typ, XP_FALSE, XP_TRUE ); - } - if ( params->commsDisableds[typ][1] ) { - comms_setAddrDisabled( cGlobals->game.comms, typ, XP_TRUE, XP_TRUE ); - } - switch( typ ) { -#ifdef XWFEATURE_RELAY - case COMMS_CONN_RELAY: - /* addr.u.ip_relay.ipAddr = 0; */ - /* addr.u.ip_relay.port = params->connInfo.relay.defaultSendPort; */ - /* addr.u.ip_relay.seeksPublicRoom = params->connInfo.relay.seeksPublicRoom; */ - /* addr.u.ip_relay.advertiseRoom = params->connInfo.relay.advertiseRoom; */ - /* XP_STRNCPY( addr.u.ip_relay.hostName, params->connInfo.relay.relayName, */ - /* sizeof(addr.u.ip_relay.hostName) - 1 ); */ - /* XP_STRNCPY( addr.u.ip_relay.invite, params->connInfo.relay.invite, */ - /* sizeof(addr.u.ip_relay.invite) - 1 ); */ - break; -#endif -#ifdef XWFEATURE_BLUETOOTH - case COMMS_CONN_BT: - XP_ASSERT( sizeof(addr.u.bt.btAddr) - >= sizeof(params->connInfo.bt.hostAddr)); - XP_MEMCPY( &addr.u.bt.btAddr, ¶ms->connInfo.bt.hostAddr, - sizeof(params->connInfo.bt.hostAddr) ); - break; -#endif -#ifdef XWFEATURE_IP_DIRECT - case COMMS_CONN_IP_DIRECT: - XP_STRNCPY( addr.u.ip.hostName_ip, params->connInfo.ip.hostName, - sizeof(addr.u.ip.hostName_ip) - 1 ); - addr.u.ip.port_ip = params->connInfo.ip.port; - break; -#endif -#ifdef XWFEATURE_SMS - case COMMS_CONN_SMS: - /* No! Don't overwrite what may be a return address with local - stuff */ - /* XP_STRNCPY( addr.u.sms.phone, params->connInfo.sms.phone, */ - /* sizeof(addr.u.sms.phone) - 1 ); */ - /* addr.u.sms.port = params->connInfo.sms.port; */ - break; -#endif - default: - break; - } - } - - /* Need to save in order to have a valid selRow for the first send */ - saveGame( cGlobals ); - -#ifndef XWFEATURE_STANDALONE_ONLY - /* This may trigger network activity */ - if ( !!cGlobals->game.comms ) { - comms_setAddr( cGlobals->game.comms, &addr ); - } -#endif - model_setDictionary( cGlobals->game.model, cGlobals->dict ); - setSquareBonuses( cGlobals ); - model_setPlayerDicts( cGlobals->game.model, &cGlobals->dicts ); - -#ifdef XWFEATURE_SEARCHLIMIT - cGlobals->gi->allowHintRect = params->allowHintRect; -#endif - - if ( params->needsNewGame ) { - new_game_impl( globals, XP_FALSE ); -#ifndef XWFEATURE_STANDALONE_ONLY - } else { - DeviceRole serverRole = cGlobals->gi->serverRole; - if ( serverRole == SERVER_ISCLIENT ) { - tryConnectToServer( cGlobals ); - } -#endif - } - } + linuxOpenGame( cGlobals, &procs ); if ( !params->fileName && !!params->dbName ) { XP_UCHAR buf[64]; snprintf( buf, sizeof(buf), "%s / %lld", params->dbName, - cGlobals->selRow ); + cGlobals->rowid ); gtk_window_set_title( GTK_WINDOW(globals->window), buf ); } -#ifndef XWFEATURE_STANDALONE_ONLY - if ( !!globals->cGlobals.game.comms ) { - comms_start( globals->cGlobals.game.comms ); - } -#endif - server_do( globals->cGlobals.game.server ); - saveGame( cGlobals ); /* again, to include address etc. */ addDropChecks( globals ); disenable_buttons( globals ); @@ -758,11 +606,11 @@ configure_event( GtkWidget* widget, GdkEventConfigure* XP_UNUSED(event), GtkGameGlobals* globals ) { globals->gridOn = XP_TRUE; - if ( globals->draw == NULL ) { + CommonGlobals* cGlobals = &globals->cGlobals; + if ( cGlobals->draw == NULL ) { createOrLoadObjects( globals ); } - CommonGlobals* cGlobals = &globals->cGlobals; BoardCtxt* board = cGlobals->game.board; GtkAllocation alloc; @@ -796,7 +644,7 @@ destroy_board_window( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) if ( !!globals->cGlobals.game.comms ) { comms_stop( globals->cGlobals.game.comms ); } - saveGame( &globals->cGlobals ); + linuxSaveGame( &globals->cGlobals ); windowDestroyed( globals ); } @@ -814,7 +662,7 @@ on_board_window_shown( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) /* If it has pending invite info, send the invitation! */ XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool) cGlobals->params->vtMgr ); - if ( loadInviteAddrs( stream, cGlobals->params->pDb, cGlobals->selRow ) ) { + if ( loadInviteAddrs( stream, cGlobals->params->pDb, cGlobals->rowid ) ) { CommsAddrRec addr = {0}; addrFromStream( &addr, stream ); comms_setAddr( cGlobals->game.comms, &addr ); @@ -840,7 +688,7 @@ static void cleanup( GtkGameGlobals* globals ) { CommonGlobals* cGlobals = &globals->cGlobals; - saveGame( cGlobals ); + linuxSaveGame( cGlobals ); if ( 0 < globals->idleID ) { g_source_remove( globals->idleID ); } @@ -955,7 +803,7 @@ new_game_impl( GtkGameGlobals* globals, XP_Bool fireConnDlg ) } CurGameInfo* gi = cGlobals->gi; - success = newGameDialog( globals, gi, &addr, XP_TRUE, fireConnDlg ); + success = gtkNewGameDialog( globals, gi, &addr, XP_TRUE, fireConnDlg ); if ( success ) { #ifndef XWFEATURE_STANDALONE_ONLY XP_Bool isClient = gi->serverRole == SERVER_ISCLIENT; @@ -1022,7 +870,7 @@ game_info( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) /* Anything to do if OK is clicked? Changed names etc. already saved. Try server_do in case one's become a robot. */ CurGameInfo* gi = globals->cGlobals.gi; - if ( newGameDialog( globals, gi, &addr, XP_FALSE, XP_FALSE ) ) { + if ( gtkNewGameDialog( globals, gi, &addr, XP_FALSE, XP_FALSE ) ) { if ( server_do( globals->cGlobals.game.server ) ) { board_draw( globals->cGlobals.game.board ); } @@ -1858,7 +1706,7 @@ static void gtk_util_turnChanged( XW_UtilCtxt* uc, XP_S16 XP_UNUSED(newTurn) ) { GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; - saveGame( &globals->cGlobals ); + linuxSaveGame( &globals->cGlobals ); } #endif @@ -1954,22 +1802,6 @@ gtk_util_informNetDict( XW_UtilCtxt* uc, XP_LangCode XP_UNUSED(lang), } } -static gint -changeRoles( gpointer data ) -{ - linuxChangeRoles( (CommonGlobals*)data ); - return 0; -} - -static void -gtk_util_setIsServer( XW_UtilCtxt* uc, XP_Bool isServer ) -{ - CommonGlobals* cGlobals = (CommonGlobals*)uc->closure; - linuxSetIsServer( cGlobals, isServer ); - - (void)g_idle_add( changeRoles, cGlobals ); -} - /* define this to prevent user events during debugging from stopping the engine */ /* #define DONT_ABORT_ENGINE */ @@ -2334,14 +2166,6 @@ gtk_util_notifyMove( XW_UtilCtxt* uc, XWStreamCtxt* stream ) XP_ASSERT( len <= VSIZE(cGlobals->question) ); stream_getBytes( stream, cGlobals->question, len ); (void)g_idle_add( ask_move, globals ); - - /* question = strFromStream( stream ); */ - /* strcpy( cGlobals->question, question ); */ - /* free( question ); */ - - /* /\*gint chosen = *\/gtkask( globals->window, question, buttons, NULL ); */ - // result = GTK_RESPONSE_OK == chosen || chosen == GTK_RESPONSE_YES; - } /* gtk_util_userQuery */ static gint @@ -2501,52 +2325,53 @@ makeButtons( GtkGameGlobals* globals ) static void setupGtkUtilCallbacks( GtkGameGlobals* globals, XW_UtilCtxt* util ) { - util->vtable->m_util_userError = gtk_util_userError; - util->vtable->m_util_notifyMove = gtk_util_notifyMove; - util->vtable->m_util_notifyTrade = gtk_util_notifyTrade; - util->vtable->m_util_notifyPickTileBlank = gtk_util_notifyPickTileBlank; - util->vtable->m_util_informNeedPickTiles = gtk_util_informNeedPickTiles; - util->vtable->m_util_informNeedPassword = gtk_util_informNeedPassword; - util->vtable->m_util_trayHiddenChange = gtk_util_trayHiddenChange; - util->vtable->m_util_yOffsetChange = gtk_util_yOffsetChange; + util->closure = globals; +#define SET_PROC(NAM) util->vtable->m_util_##NAM = gtk_util_##NAM + SET_PROC(userError); + SET_PROC(notifyMove); + SET_PROC(notifyTrade); + SET_PROC(notifyPickTileBlank); + SET_PROC(informNeedPickTiles); + SET_PROC(informNeedPassword); + SET_PROC(trayHiddenChange); + SET_PROC(yOffsetChange); #ifdef XWFEATURE_TURNCHANGENOTIFY - util->vtable->m_util_turnChanged = gtk_util_turnChanged; + SET_PROC(turnChanged); #endif - util->vtable->m_util_informMove = gtk_util_informMove; - util->vtable->m_util_informUndo = gtk_util_informUndo; - util->vtable->m_util_notifyGameOver = gtk_util_notifyGameOver; - util->vtable->m_util_informNetDict = gtk_util_informNetDict; - util->vtable->m_util_setIsServer = gtk_util_setIsServer; + SET_PROC(informMove); + SET_PROC(informUndo); + SET_PROC(notifyGameOver); + SET_PROC(informNetDict); + /* SET_PROC(setIsServer); */ #ifdef XWFEATURE_HILITECELL - util->vtable->m_util_hiliteCell = gtk_util_hiliteCell; + SET_PROC(hiliteCell); #endif - util->vtable->m_util_altKeyDown = gtk_util_altKeyDown; - util->vtable->m_util_engineProgressCallback = - gtk_util_engineProgressCallback; - util->vtable->m_util_setTimer = gtk_util_setTimer; - util->vtable->m_util_clearTimer = gtk_util_clearTimer; - util->vtable->m_util_requestTime = gtk_util_requestTime; - util->vtable->m_util_notifyIllegalWords = gtk_util_notifyIllegalWords; - util->vtable->m_util_remSelected = gtk_util_remSelected; + SET_PROC(altKeyDown); + SET_PROC(engineProgressCallback); + SET_PROC(setTimer); + SET_PROC(clearTimer); + SET_PROC(requestTime); + SET_PROC(notifyIllegalWords); + SET_PROC(remSelected); #ifndef XWFEATURE_STANDALONE_ONLY - util->vtable->m_util_makeStreamFromAddr = gtk_util_makeStreamFromAddr; + SET_PROC(makeStreamFromAddr); #endif #ifdef XWFEATURE_CHAT - util->vtable->m_util_showChat = gtk_util_showChat; + SET_PROC(showChat); #endif #ifdef XWFEATURE_SEARCHLIMIT - util->vtable->m_util_getTraySearchLimits = gtk_util_getTraySearchLimits; + SET_PROC(getTraySearchLimits); #endif #ifndef XWFEATURE_MINIWIN - util->vtable->m_util_bonusSquareHeld = gtk_util_bonusSquareHeld; - util->vtable->m_util_playerScoreHeld = gtk_util_playerScoreHeld; + SET_PROC(bonusSquareHeld); + SET_PROC(playerScoreHeld); #endif #ifdef XWFEATURE_BOARDWORDS - util->vtable->m_util_cellSquareHeld = gtk_util_cellSquareHeld; + SET_PROC(cellSquareHeld); #endif - - util->closure = globals; +#undef SET_PROC + assertAllCallbacksSet( util ); } /* setupGtkUtilCallbacks */ #ifndef XWFEATURE_STANDALONE_ONLY @@ -2556,33 +2381,6 @@ typedef struct _SockInfo { int socket; } SockInfo; -static void -gtk_socket_added( void* closure, int newSock, GIOFunc proc ) -{ - GtkGameGlobals* globals = (GtkGameGlobals*)closure; - - if ( newSock != -1 ) { - XP_ASSERT( !!proc ); - GIOChannel* channel = g_io_channel_unix_new( newSock ); - g_io_channel_set_close_on_unref( channel, TRUE ); -#ifdef DEBUG - guint result = -#endif - g_io_add_watch( channel, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_PRI, - proc, globals ); - XP_LOGF( "g_io_add_watch(%d) => %d", newSock, result ); - } - /* A hack for the bluetooth case. */ - CommsCtxt* comms = globals->cGlobals.game.comms; - - CommsAddrRec addr; - comms_getAddr( comms, &addr ); - if ( (comms != NULL) && (addr_hasType( &addr, COMMS_CONN_BT) ) ) { - comms_resendAll( comms, COMMS_CONN_NONE, XP_FALSE ); - } - LOG_RETURN_VOID(); -} /* gtk_socket_changed */ - static gboolean acceptorInput( GIOChannel* source, GIOCondition condition, gpointer data ) { @@ -2671,44 +2469,44 @@ initGlobalsNoDraw( GtkGameGlobals* globals, LaunchParams* params, { memset( globals, 0, sizeof(*globals) ); - globals->cGlobals.gi = &globals->gi; + CommonGlobals* cGlobals = &globals->cGlobals; + cGlobals->gi = &cGlobals->_gi; if ( !gi ) { gi = ¶ms->pgi; } - gi_copy( MPPARM(params->mpool) globals->cGlobals.gi, gi ); + gi_copy( MPPARM(params->mpool) cGlobals->gi, gi ); - globals->cGlobals.params = params; - globals->cGlobals.lastNTilesToUse = MAX_TRAY_TILES; + cGlobals->params = params; + cGlobals->lastNTilesToUse = MAX_TRAY_TILES; #ifndef XWFEATURE_STANDALONE_ONLY # ifdef XWFEATURE_RELAY - globals->cGlobals.relaySocket = -1; + cGlobals->relaySocket = -1; # endif - globals->cGlobals.socketAdded = gtk_socket_added; - globals->cGlobals.socketAddedClosure = globals; - globals->cGlobals.onSave = onGameSaved; - globals->cGlobals.onSaveClosure = globals; - globals->cGlobals.addAcceptor = gtk_socket_acceptor; + cGlobals->socketAddedClosure = globals; + cGlobals->onSave = gtkOnGameSaved; + cGlobals->onSaveClosure = globals; + cGlobals->addAcceptor = gtk_socket_acceptor; #endif - globals->cGlobals.cp.showBoardArrow = XP_TRUE; - globals->cGlobals.cp.hideTileValues = params->hideValues; - globals->cGlobals.cp.skipCommitConfirm = params->skipCommitConfirm; - globals->cGlobals.cp.sortNewTiles = params->sortNewTiles; - globals->cGlobals.cp.showColors = params->showColors; - globals->cGlobals.cp.allowPeek = params->allowPeek; - globals->cGlobals.cp.showRobotScores = params->showRobotScores; + cGlobals->cp.showBoardArrow = XP_TRUE; + cGlobals->cp.hideTileValues = params->hideValues; + cGlobals->cp.skipCommitConfirm = params->skipCommitConfirm; + cGlobals->cp.sortNewTiles = params->sortNewTiles; + cGlobals->cp.showColors = params->showColors; + cGlobals->cp.allowPeek = params->allowPeek; + cGlobals->cp.showRobotScores = params->showRobotScores; #ifdef XWFEATURE_SLOW_ROBOT - globals->cGlobals.cp.robotThinkMin = params->robotThinkMin; - globals->cGlobals.cp.robotThinkMax = params->robotThinkMax; - globals->cGlobals.cp.robotTradePct = params->robotTradePct; + cGlobals->cp.robotThinkMin = params->robotThinkMin; + cGlobals->cp.robotThinkMax = params->robotThinkMax; + cGlobals->cp.robotTradePct = params->robotTradePct; #endif #ifdef XWFEATURE_CROSSHAIRS - globals->cGlobals.cp.hideCrosshairs = params->hideCrosshairs; + cGlobals->cp.hideCrosshairs = params->hideCrosshairs; #endif - setupUtil( &globals->cGlobals ); - setupGtkUtilCallbacks( globals, globals->cGlobals.util ); + setupUtil( cGlobals ); + setupGtkUtilCallbacks( globals, cGlobals->util ); } /* This gets called all the time, e.g. when the mouse moves across @@ -2727,9 +2525,10 @@ on_draw_event( GtkWidget* widget, cairo_t* cr, gpointer user_data ) /* } */ GtkGameGlobals* globals = (GtkGameGlobals*)user_data; - board_invalAll( globals->cGlobals.game.board ); - board_draw( globals->cGlobals.game.board ); - draw_gtk_status( globals->draw, globals->stateChar ); + CommonGlobals* cGlobals = &globals->cGlobals; + board_invalAll( cGlobals->game.board ); + board_draw( cGlobals->game.board ); + draw_gtk_status( (GtkDrawCtx*)cGlobals->draw, globals->stateChar ); XP_USE(widget); XP_USE(cr); @@ -2737,7 +2536,8 @@ on_draw_event( GtkWidget* widget, cairo_t* cr, gpointer user_data ) } void -initGlobals( GtkGameGlobals* globals, LaunchParams* params, CurGameInfo* gi ) +initBoardGlobalsGtk( GtkGameGlobals* globals, LaunchParams* params, + CurGameInfo* gi ) { CommonGlobals* cGlobals = &globals->cGlobals; short width, height; @@ -2746,7 +2546,6 @@ initGlobals( GtkGameGlobals* globals, LaunchParams* params, CurGameInfo* gi ) GtkWidget* menubar; GtkWidget* vbox; GtkWidget* hbox; - gulong id; initGlobalsNoDraw( globals, params, gi ); if ( !!gi ) { @@ -2765,12 +2564,18 @@ initGlobals( GtkGameGlobals* globals, LaunchParams* params, CurGameInfo* gi ) gtk_container_add( GTK_CONTAINER(window), vbox ); gtk_widget_show( vbox ); - id = g_signal_connect( window, "destroy", G_CALLBACK(destroy_board_window), - globals ); +#ifdef DEBUG + gulong id = +#endif + g_signal_connect( window, "destroy", G_CALLBACK(destroy_board_window), + globals ); XP_ASSERT( id > 0 ); XP_ASSERT( !!globals ); - id = g_signal_connect( window, "show", G_CALLBACK( on_board_window_shown ), - globals ); +#ifdef DEBUG + id = +#endif + g_signal_connect( window, "show", G_CALLBACK( on_board_window_shown ), + globals ); XP_ASSERT( id > 0 ); menubar = makeMenus( globals ); @@ -2786,8 +2591,11 @@ initGlobals( GtkGameGlobals* globals, LaunchParams* params, CurGameInfo* gi ) drawing_area = gtk_drawing_area_new(); gtk_widget_add_events( drawing_area, GDK_ALL_EVENTS_MASK ); - id = g_signal_connect(G_OBJECT(drawing_area), "draw", - G_CALLBACK(on_draw_event), globals); +#ifdef DEBUG + id = +#endif + g_signal_connect(G_OBJECT(drawing_area), "draw", + G_CALLBACK(on_draw_event), globals); XP_ASSERT( id > 0 ); globals->drawing_area = drawing_area; @@ -2815,8 +2623,11 @@ initGlobals( GtkGameGlobals* globals, LaunchParams* params, CurGameInfo* gi ) gtk_adjustment_new( 0, 0, nRows, 1, 2, nRows - params->nHidden ); vscrollbar = gtk_scrollbar_new( GTK_ORIENTATION_VERTICAL, globals->adjustment ); - id = g_signal_connect( globals->adjustment, "value_changed", - G_CALLBACK(scroll_value_changed), globals ); +#ifdef DEBUG + id = +#endif + g_signal_connect( globals->adjustment, "value_changed", + G_CALLBACK(scroll_value_changed), globals ); XP_ASSERT( id > 0 ); gtk_widget_show( vscrollbar ); gtk_box_pack_start( GTK_BOX(hbox), vscrollbar, FALSE, FALSE, 0 ); @@ -2831,18 +2642,29 @@ initGlobals( GtkGameGlobals* globals, LaunchParams* params, CurGameInfo* gi ) GtkWidget* label = globals->countLabel = gtk_label_new( "" ); gtk_box_pack_start( GTK_BOX(vbox), label, TRUE, TRUE, 0); gtk_widget_show( label ); - - id = g_signal_connect( drawing_area, "configure-event", - G_CALLBACK(configure_event), globals ); +#ifdef DEBUG + id = +#endif + g_signal_connect( drawing_area, "configure-event", + G_CALLBACK(configure_event), globals ); XP_ASSERT( id > 0 ); - id = g_signal_connect( drawing_area, "button_press_event", - G_CALLBACK(button_press_event), globals ); +#ifdef DEBUG + id = +#endif + g_signal_connect( drawing_area, "button_press_event", + G_CALLBACK(button_press_event), globals ); XP_ASSERT( id > 0 ); - id = g_signal_connect( drawing_area, "motion_notify_event", - G_CALLBACK(motion_notify_event), globals ); +#ifdef DEBUG + id = +#endif + g_signal_connect( drawing_area, "motion_notify_event", + G_CALLBACK(motion_notify_event), globals ); XP_ASSERT( id > 0 ); - id = g_signal_connect( drawing_area, "button_release_event", - G_CALLBACK(button_release_event), globals ); +#ifdef DEBUG + id = +#endif + g_signal_connect( drawing_area, "button_release_event", + G_CALLBACK(button_release_event), globals ); XP_ASSERT( id > 0 ); setOneSecondTimer( cGlobals ); @@ -2889,7 +2711,7 @@ loadGameNoDraw( GtkGameGlobals* globals, LaunchParams* params, setTransportProcs( &procs, globals ); CommonGlobals* cGlobals = &globals->cGlobals; - cGlobals->selRow = rowid; + cGlobals->rowid = rowid; XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool) params->vtMgr ); XP_Bool loaded = loadGame( stream, pDb, rowid ); @@ -2924,7 +2746,7 @@ makeNewGame( GtkGameGlobals* globals ) if ( !!cGlobals->game.comms ) { comms_getAddr( cGlobals->game.comms, &cGlobals->addr ); } else { - LaunchParams* params = globals->cGlobals.params; + LaunchParams* params = cGlobals->params; const XP_UCHAR* relayName = params->connInfo.relay.relayName; if ( !relayName ) { relayName = RELAY_NAME_DEFAULT; @@ -2937,8 +2759,8 @@ makeNewGame( GtkGameGlobals* globals ) } CurGameInfo* gi = cGlobals->gi; - XP_Bool success = newGameDialog( globals, gi, &cGlobals->addr, - XP_TRUE, XP_FALSE ); + XP_Bool success = gtkNewGameDialog( globals, gi, &cGlobals->addr, + XP_TRUE, XP_FALSE ); if ( success && !!gi->dictName && !cGlobals->dict ) { cGlobals->dict = linux_dictionary_make( MEMPOOL cGlobals->params, diff --git a/xwords4/linux/gtkboard.h b/xwords4/linux/gtkboard.h index 72a1867f6..da1851f80 100644 --- a/xwords4/linux/gtkboard.h +++ b/xwords4/linux/gtkboard.h @@ -94,9 +94,7 @@ typedef struct _DropTypeData { typedef struct GtkGameGlobals { CommonGlobals cGlobals; - CurGameInfo gi; GtkWidget* window; - GtkDrawCtx* draw; GtkAppGlobals* apg; /* GdkPixmap* pixmap; */ GtkWidget* drawing_area; @@ -184,8 +182,8 @@ typedef struct GtkGameGlobals { #define GTK_BOTTOM_MARGIN GTK_TOP_MARGIN #define GTK_RIGHT_MARGIN GTK_BOARD_LEFT_MARGIN -void initGlobals( GtkGameGlobals* globals, LaunchParams* params, - CurGameInfo* gi ); +void initBoardGlobalsGtk( GtkGameGlobals* globals, LaunchParams* params, + CurGameInfo* gi ); void freeGlobals( GtkGameGlobals* globals ); XP_Bool makeNewGame( GtkGameGlobals* globals ); XP_Bool loadGameNoDraw( GtkGameGlobals* globals, LaunchParams* params, diff --git a/xwords4/linux/gtkdraw.c b/xwords4/linux/gtkdraw.c index a8e01b658..db8473bc4 100644 --- a/xwords4/linux/gtkdraw.c +++ b/xwords4/linux/gtkdraw.c @@ -1531,7 +1531,6 @@ removeSurface( GtkDrawCtx* dctx ) dctx->surface = NULL; } -#ifdef DEBUG static cairo_status_t write_func( void *closure, const unsigned char *data, unsigned int length ) @@ -1540,21 +1539,18 @@ write_func( void *closure, const unsigned char *data, stream_putBytes( stream, data, length ); return CAIRO_STATUS_SUCCESS; } -#endif void -getImage( GtkDrawCtx* XP_UNUSED_DBG(dctx), XWStreamCtxt* XP_UNUSED_DBG(stream) ) +getImage( GtkDrawCtx* dctx, XWStreamCtxt* stream ) { LOG_FUNC(); XP_ASSERT( !!dctx->surface ); #ifdef DEBUG cairo_status_t status = +#endif cairo_surface_write_to_png_stream( dctx->surface, write_func, stream ); XP_ASSERT( CAIRO_STATUS_SUCCESS == status ); -#else - error Will Robinson -#endif } void diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c index ee7451f91..eaba9f608 100644 --- a/xwords4/linux/gtkmain.c +++ b/xwords4/linux/gtkmain.c @@ -36,18 +36,19 @@ static void onNewData( GtkAppGlobals* apg, sqlite3_int64 rowid, XP_Bool isNew ); static void updateButtons( GtkAppGlobals* apg ); +static void open_row( GtkAppGlobals* apg, sqlite3_int64 row, XP_Bool isNew ); static void recordOpened( GtkAppGlobals* apg, GtkGameGlobals* globals ) { - apg->globalsList = g_slist_prepend( apg->globalsList, globals ); + apg->cag.globalsList = g_slist_prepend( apg->cag.globalsList, globals ); globals->apg = apg; } static void recordClosed( GtkAppGlobals* apg, GtkGameGlobals* globals ) { - apg->globalsList = g_slist_remove( apg->globalsList, globals ); + apg->cag.globalsList = g_slist_remove( apg->cag.globalsList, globals ); } static XP_Bool @@ -55,9 +56,9 @@ gameIsOpen( GtkAppGlobals* apg, sqlite3_int64 rowid ) { XP_Bool found = XP_FALSE; GSList* iter; - for ( iter = apg->globalsList; !!iter && !found; iter = iter->next ) { + for ( iter = apg->cag.globalsList; !!iter && !found; iter = iter->next ) { GtkGameGlobals* globals = (GtkGameGlobals*)iter->data; - found = globals->cGlobals.selRow == rowid; + found = globals->cGlobals.rowid == rowid; } return found; } @@ -67,10 +68,10 @@ findOpenGame( const GtkAppGlobals* apg, sqlite3_int64 rowid ) { GtkGameGlobals* result = NULL; GSList* iter; - for ( iter = apg->globalsList; !!iter; iter = iter->next ) { + for ( iter = apg->cag.globalsList; !!iter; iter = iter->next ) { GtkGameGlobals* globals = (GtkGameGlobals*)iter->data; CommonGlobals* cGlobals = &globals->cGlobals; - if ( cGlobals->selRow == rowid ) { + if ( cGlobals->rowid == rowid ) { result = globals; break; } @@ -78,7 +79,7 @@ findOpenGame( const GtkAppGlobals* apg, sqlite3_int64 rowid ) return result; } -enum { ROW_ITEM, ROW_THUMB, NAME_ITEM, ROOM_ITEM, GAMEID_ITEM, SEED_ITEM, +enum { ROW_ITEM, ROW_THUMB, NAME_ITEM, ROOM_ITEM, GAMEID_ITEM, SEED_ITEM, ROLE_ITEM, CONN_ITEM, RELAYID_ITEM, OVER_ITEM, TURN_ITEM, LOCAL_ITEM, NMOVES_ITEM, NTOTAL_ITEM, MISSING_ITEM, LASTTURN_ITEM, N_ITEMS }; @@ -169,6 +170,7 @@ init_games_list( GtkAppGlobals* apg ) addTextColumn( list, "Room", ROOM_ITEM ); addTextColumn( list, "GameID", GAMEID_ITEM ); addTextColumn( list, "Seed", SEED_ITEM ); + addTextColumn( list, "Role", ROLE_ITEM ); addTextColumn( list, "Conn. via", CONN_ITEM ); addTextColumn( list, "RelayID", RELAYID_ITEM ); addTextColumn( list, "Ended", OVER_ITEM ); @@ -186,6 +188,7 @@ init_games_list( GtkAppGlobals* apg ) G_TYPE_STRING, /* ROOM_ITEM */ G_TYPE_INT, /* GAMEID_ITEM */ G_TYPE_INT, /* SEED_ITEM */ + G_TYPE_INT, /* ROLE_ITEM */ G_TYPE_STRING, /* CONN_ITEM */ G_TYPE_STRING, /*RELAYID_ITEM */ G_TYPE_BOOLEAN, /* OVER_ITEM */ @@ -247,6 +250,7 @@ add_to_list( GtkWidget* list, sqlite3_int64 rowid, XP_Bool isNew, ROOM_ITEM, gib->room, GAMEID_ITEM, gib->gameID, SEED_ITEM, gib->seed, + ROLE_ITEM, gib->role, CONN_ITEM, gib->conn, RELAYID_ITEM, gib->relayID, TURN_ITEM, gib->turn, @@ -275,20 +279,20 @@ handle_newgame_button( GtkWidget* XP_UNUSED(widget), void* closure ) { GtkAppGlobals* apg = (GtkAppGlobals*)closure; XP_LOGF( "%s called", __func__ ); - GtkGameGlobals* globals = malloc( sizeof(*globals) ); - apg->params->needsNewGame = XP_FALSE; - initGlobals( globals, apg->params, NULL ); + GtkGameGlobals* globals = calloc( 1, sizeof(*globals) ); + apg->cag.params->needsNewGame = XP_FALSE; + initBoardGlobalsGtk( globals, apg->cag.params, NULL ); if ( !makeNewGame( globals ) ) { freeGlobals( globals ); } else { GtkWidget* gameWindow = globals->window; - globals->cGlobals.selRow = -1; + globals->cGlobals.rowid = -1; recordOpened( apg, globals ); gtk_widget_show( gameWindow ); } } -void +static void open_row( GtkAppGlobals* apg, sqlite3_int64 row, XP_Bool isNew ) { if ( -1 != row && !gameIsOpen( apg, row ) ) { @@ -296,10 +300,10 @@ open_row( GtkAppGlobals* apg, sqlite3_int64 row, XP_Bool isNew ) onNewData( apg, row, XP_TRUE ); } - apg->params->needsNewGame = XP_FALSE; + apg->cag.params->needsNewGame = XP_FALSE; GtkGameGlobals* globals = malloc( sizeof(*globals) ); - initGlobals( globals, apg->params, NULL ); - globals->cGlobals.selRow = row; + initBoardGlobalsGtk( globals, apg->cag.params, NULL ); + globals->cGlobals.rowid = row; recordOpened( apg, globals ); gtk_widget_show( globals->window ); } @@ -320,7 +324,7 @@ handle_open_button( GtkWidget* XP_UNUSED(widget), void* closure ) void make_rematch( GtkAppGlobals* apg, const CommonGlobals* cGlobals ) { - LaunchParams* params = apg->params; + LaunchParams* params = apg->cag.params; XP_ASSERT( params == cGlobals->params ); XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool) params->vtMgr ); @@ -383,7 +387,7 @@ handle_rematch_button( GtkWidget* XP_UNUSED(widget), void* closure ) for ( int ii = 0; ii < selRows->len; ++ii ) { sqlite3_int64 rowid = g_array_index( selRows, sqlite3_int64, ii ); GtkGameGlobals tmpGlobals; - if ( loadGameNoDraw( &tmpGlobals, apg->params, rowid ) ) { + if ( loadGameNoDraw( &tmpGlobals, apg->cag.params, rowid ) ) { make_rematch( apg, &tmpGlobals.cGlobals ); } freeGlobals( &tmpGlobals ); @@ -394,7 +398,7 @@ static void handle_delete_button( GtkWidget* XP_UNUSED(widget), void* closure ) { GtkAppGlobals* apg = (GtkAppGlobals*)closure; - LaunchParams* params = apg->params; + LaunchParams* params = apg->cag.params; guint len = apg->selRows->len; for ( guint ii = 0; ii < len; ++ii ) { sqlite3_int64 rowid = g_array_index( apg->selRows, sqlite3_int64, ii ); @@ -429,18 +433,18 @@ handle_destroy( GtkWidget* XP_UNUSED(widget), gpointer data ) LOG_FUNC(); GtkAppGlobals* apg = (GtkAppGlobals*)data; GSList* iter; - for ( iter = apg->globalsList; !!iter; iter = iter->next ) { + for ( iter = apg->cag.globalsList; !!iter; iter = iter->next ) { GtkGameGlobals* globals = (GtkGameGlobals*)iter->data; destroy_board_window( NULL, globals ); // freeGlobals( globals ); } - g_slist_free( apg->globalsList ); + g_slist_free( apg->cag.globalsList ); gchar buf[64]; sprintf( buf, "%d:%d:%d:%d", apg->lastConfigure.x, apg->lastConfigure.y, apg->lastConfigure.width, apg->lastConfigure.height ); - db_store( apg->params->pDb, KEY_WIN_LOC, buf ); + db_store( apg->cag.params->pDb, KEY_WIN_LOC, buf ); gtk_main_quit(); } @@ -477,7 +481,7 @@ static void setWindowTitle( GtkAppGlobals* apg ) { GtkWidget* window = apg->window; - LaunchParams* params = apg->params; + LaunchParams* params = apg->cag.params; gchar title[128] = {0}; if ( !!params->dbName ) { @@ -505,7 +509,7 @@ trySetWinConfig( GtkAppGlobals* apg ) int height = 400; gchar buf[64]; - if ( db_fetch_safe( apg->params->pDb, KEY_WIN_LOC, buf, sizeof(buf)) ) { + if ( db_fetch_safe( apg->cag.params->pDb, KEY_WIN_LOC, buf, sizeof(buf)) ) { sscanf( buf, "%d:%d:%d:%d", &xx, &yy, &width, &height ); } @@ -516,14 +520,14 @@ trySetWinConfig( GtkAppGlobals* apg ) static void handle_movescheck( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* apg ) { - LaunchParams* params = apg->params; + LaunchParams* params = apg->cag.params; relaycon_checkMsgs( params ); } static void handle_relayid_to_clip( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* apg ) { - LaunchParams* params = apg->params; + LaunchParams* params = apg->cag.params; XP_U32 relayID = linux_getDevIDRelay( params ); gchar str[32]; snprintf( &str[0], VSIZE(str), "%d", relayID ); @@ -535,7 +539,7 @@ static void makeGamesWindow( GtkAppGlobals* apg ) { GtkWidget* window; - LaunchParams* params = apg->params; + LaunchParams* params = apg->cag.params; apg->window = window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); g_signal_connect( G_OBJECT(window), "destroy", @@ -602,7 +606,7 @@ static GtkWidget* openDBFile( GtkAppGlobals* apg ) { GtkGameGlobals* globals = malloc( sizeof(*globals) ); - initGlobals( globals, apg->params, NULL ); + initBoardGlobalsGtk( globals, apg->cag.params, NULL ); GtkWidget* window = globals->window; gtk_widget_show( window ); @@ -633,7 +637,7 @@ static void onNewData( GtkAppGlobals* apg, sqlite3_int64 rowid, XP_Bool isNew ) { GameInfo gib; - if ( getGameInfo( apg->params->pDb, rowid, &gib ) ) { + if ( getGameInfo( apg->cag.params->pDb, rowid, &gib ) ) { add_to_list( apg->listWidget, rowid, isNew, &gib ); g_object_unref( gib.snap ); } @@ -651,31 +655,22 @@ feedBufferGTK( GtkAppGlobals* apg, sqlite3_int64 rowid, seed = comms_getChannelSeed( globals->cGlobals.game.comms ); } else { GtkGameGlobals tmpGlobals; - if ( loadGameNoDraw( &tmpGlobals, apg->params, rowid ) ) { + if ( loadGameNoDraw( &tmpGlobals, apg->cag.params, rowid ) ) { gameGotBuf( &tmpGlobals.cGlobals, XP_FALSE, buf, len, from ); seed = comms_getChannelSeed( tmpGlobals.cGlobals.game.comms ); - saveGame( &tmpGlobals.cGlobals ); + linuxSaveGame( &tmpGlobals.cGlobals ); } freeGlobals( &tmpGlobals ); } return seed; } -static void -gtkSocketAdded( void* closure, int newSock, GIOFunc proc ) -{ - GIOChannel* channel = g_io_channel_unix_new( newSock ); - (void)g_io_add_watch( channel, G_IO_IN | G_IO_ERR, proc, closure ); - LOG_RETURN_VOID(); -} /* gtk_socket_changed */ - - /* Stuff common to receiving invitations */ static void gameFromInvite( GtkAppGlobals* apg, const NetLaunchInfo* invite, const CommsAddrRec* returnAddr ) { - LaunchParams* params = apg->params; + LaunchParams* params = apg->cag.params; CurGameInfo gi = {0}; gi_copy( MPPARM(params->mpool) &gi, ¶ms->pgi ); @@ -688,7 +683,7 @@ gameFromInvite( GtkAppGlobals* apg, const NetLaunchInfo* invite, GtkGameGlobals* globals = malloc( sizeof(*globals) ); params->needsNewGame = XP_FALSE; - initGlobals( globals, params, &gi ); + initBoardGlobalsGtk( globals, params, &gi ); if ( !!returnAddr ) { globals->cGlobals.addr = *returnAddr; @@ -697,7 +692,7 @@ gameFromInvite( GtkAppGlobals* apg, const NetLaunchInfo* invite, } GtkWidget* gameWindow = globals->window; - globals->cGlobals.selRow = -1; + globals->cGlobals.rowid = -1; recordOpened( apg, globals ); gtk_widget_show( gameWindow ); @@ -712,7 +707,7 @@ relayInviteReceived( void* closure, NetLaunchInfo* invite ) XP_U32 gameID = invite->gameID; sqlite3_int64 rowids[1]; int nRowIDs = VSIZE(rowids); - getRowsForGameID( apg->params->pDb, gameID, rowids, &nRowIDs ); + getRowsForGameID( apg->cag.params->pDb, gameID, rowids, &nRowIDs ); if ( 0 < nRowIDs ) { gtktell( apg->window, "Duplicate invite rejected" ); @@ -748,7 +743,7 @@ gtkGotMsgForRow( void* closure, const CommsAddrRec* from, { XP_LOGF( "%s(): got msg of len %d for row %lld", __func__, len, rowid ); GtkAppGlobals* apg = (GtkAppGlobals*)closure; - // LaunchParams* params = apg->params; + // LaunchParams* params = apg->cag.params; (void)feedBufferGTK( apg, rowid, buf, len, from ); LOG_RETURN_VOID(); } @@ -758,9 +753,9 @@ requestMsgs( gpointer data ) { GtkAppGlobals* apg = (GtkAppGlobals*)data; XP_UCHAR devIDBuf[64] = {0}; - db_fetch_safe( apg->params->pDb, KEY_RDEVID, devIDBuf, sizeof(devIDBuf) ); + db_fetch_safe( apg->cag.params->pDb, KEY_RDEVID, devIDBuf, sizeof(devIDBuf) ); if ( '\0' != devIDBuf[0] ) { - relaycon_requestMsgs( apg->params, devIDBuf ); + relaycon_requestMsgs( apg->cag.params, devIDBuf ); } else { XP_LOGF( "%s: not requesting messages as don't have relay id", __func__ ); } @@ -794,7 +789,7 @@ smsMsgReceivedGTK( void* closure, const CommsAddrRec* from, XP_U32 gameID, { LOG_FUNC(); GtkAppGlobals* apg = (GtkAppGlobals*)closure; - LaunchParams* params = apg->params; + LaunchParams* params = apg->cag.params; sqlite3_int64 rowids[4]; int nRowIDs = VSIZE(rowids); @@ -817,7 +812,7 @@ static void gtkDevIDReceived( void* closure, const XP_UCHAR* devID, XP_U16 maxInterval ) { GtkAppGlobals* apg = (GtkAppGlobals*)closure; - LaunchParams* params = apg->params; + LaunchParams* params = apg->cag.params; if ( !!devID ) { XP_LOGF( "%s(devID=%s)", __func__, devID ); db_store( params->pDb, KEY_RDEVID, devID ); @@ -842,8 +837,7 @@ gtkErrorMsgRcvd( void* closure, const XP_UCHAR* msg ) } void -onGameSaved( void* closure, sqlite3_int64 rowid, - XP_Bool firstTime ) +gtkOnGameSaved( void* closure, sqlite3_int64 rowid, XP_Bool firstTime ) { GtkGameGlobals* globals = (GtkGameGlobals*)closure; GtkAppGlobals* apg = globals->apg; @@ -874,10 +868,10 @@ gtkmain( LaunchParams* params ) sigaction( SIGTERM, &act, NULL ); apg.selRows = g_array_new( FALSE, FALSE, sizeof( sqlite3_int64 ) ); - apg.params = params; + apg.cag.params = params; XP_ASSERT( !!params->dbName || params->dbFileName ); if ( !!params->dbName ) { - params->pDb = openGamesDB( params->dbName ); + /* params->pDb = openGamesDB( params->dbName ); */ /* Check if we have a local ID already. If we do and it's changed, we care. */ @@ -890,7 +884,6 @@ gtkmain( LaunchParams* params ) .msgNoticeReceived = gtkNoticeRcvd, .devIDReceived = gtkDevIDReceived, .msgErrorMsg = gtkErrorMsgRcvd, - .socketAdded = gtkSocketAdded, .inviteReceived = relayInviteReceived, }; @@ -919,7 +912,6 @@ gtkmain( LaunchParams* params ) } if ( !!myPhone && 0 < myPort ) { SMSProcs smsProcs = { - .socketAdded = gtkSocketAdded, .inviteReceived = smsInviteReceived, .msgReceived = smsMsgReceivedGTK, }; @@ -943,8 +935,8 @@ gtkmain( LaunchParams* params ) gtk_main(); device_store( params->dutil ); - closeGamesDB( params->pDb ); - params->pDb = NULL; + /* closeGamesDB( params->pDb ); */ + /* params->pDb = NULL; */ relaycon_cleanup( params ); #ifdef XWFEATURE_SMS linux_sms_cleanup( params ); diff --git a/xwords4/linux/gtkmain.h b/xwords4/linux/gtkmain.h index 20595be78..b7aaa8d0a 100644 --- a/xwords4/linux/gtkmain.h +++ b/xwords4/linux/gtkmain.h @@ -25,8 +25,7 @@ int gtkmain( LaunchParams* params ); void windowDestroyed( GtkGameGlobals* globals ); -void onGameSaved( void* closure, sqlite3_int64 rowid, XP_Bool firstTime ); -void open_row( GtkAppGlobals* apg, sqlite3_int64 row, XP_Bool isNew ); +void gtkOnGameSaved( void* closure, sqlite3_int64 rowid, XP_Bool firstTime ); void make_rematch( GtkAppGlobals* apg, const CommonGlobals* cGlobals ); #endif diff --git a/xwords4/linux/gtknewgame.c b/xwords4/linux/gtknewgame.c index a0514270a..c72adbfb2 100644 --- a/xwords4/linux/gtknewgame.c +++ b/xwords4/linux/gtknewgame.c @@ -639,8 +639,8 @@ setDefaults( CurGameInfo* gi ) } gboolean -newGameDialog( GtkGameGlobals* globals, CurGameInfo* gi, CommsAddrRec* addr, - XP_Bool isNewGame, XP_Bool fireConnDlg ) +gtkNewGameDialog( GtkGameGlobals* globals, CurGameInfo* gi, CommsAddrRec* addr, + XP_Bool isNewGame, XP_Bool fireConnDlg ) { GtkNewGameState state; XP_MEMSET( &state, 0, sizeof(state) ); diff --git a/xwords4/linux/gtknewgame.h b/xwords4/linux/gtknewgame.h index 0f2aed6fc..ed5ec826e 100644 --- a/xwords4/linux/gtknewgame.h +++ b/xwords4/linux/gtknewgame.h @@ -26,9 +26,9 @@ #include "gtkboard.h" -gboolean newGameDialog( GtkGameGlobals* globals, CurGameInfo* gi, - CommsAddrRec* addr, XP_Bool isNewGame, - XP_Bool fireConnDlg ); +gboolean gtkNewGameDialog( GtkGameGlobals* globals, CurGameInfo* gi, + CommsAddrRec* addr, XP_Bool isNewGame, + XP_Bool fireConnDlg ); #endif /* _GTKNEWGAME_H_ */ #endif /* PLATFORM_GTK */ diff --git a/xwords4/linux/lindutil.c b/xwords4/linux/lindutil.c index 1ea819945..922a2028f 100644 --- a/xwords4/linux/lindutil.c +++ b/xwords4/linux/lindutil.c @@ -94,7 +94,7 @@ dutils_init( MPFORMAL VTableMgr* vtMgr, void* closure ) return result; } -void dutils_free( XW_DUtilCtxt** ducp ) +void dutils_free( XW_DUtilCtxt** XP_UNUSED_DBG(ducp) ) { # ifdef MEM_DEBUG XP_FREEP( (*ducp)->mpool, ducp ); diff --git a/xwords4/linux/linuxbt.c b/xwords4/linux/linuxbt.c index f3fb2f86f..3f9979f6c 100644 --- a/xwords4/linux/linuxbt.c +++ b/xwords4/linux/linuxbt.c @@ -44,6 +44,7 @@ #include "comms.h" #include "strutils.h" #include "uuidhack.h" +#include "gsrcwrap.h" #define MAX_CLIENTS 1 @@ -186,7 +187,7 @@ lbt_connectSocket( LinBtStuff* btStuff, const CommsAddrRec* addrP ) // connect to server && (0 == connect( sock, (struct sockaddr *)&saddr, sizeof(saddr) )) ) { CommonGlobals* globals = btStuff->globals; - (*globals->socketAdded)( globals->socketAddedClosure, sock, bt_socket_proc ); + ADD_SOCKET( globals->socketAddedClosure, sock, bt_socket_proc ); btStuff->socket = sock; } else { XP_LOGF( "%s: connect->%s; closing socket %d", __func__, strerror(errno), sock ); @@ -215,7 +216,7 @@ lbt_accept( int listener, void* ctxt ) success = sock >= 0; if ( success ) { - (*globals->socketAdded)( globals->socketAddedClosure, sock, bt_socket_proc ); + ADD_SOCKET( globals->socketAddedClosure, sock, bt_socket_proc ); XP_ASSERT( btStuff->socket == -1 ); btStuff->socket = sock; } else { diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index 38555c936..76ec0c2a0 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -72,6 +72,7 @@ #include "strutils.h" #include "dbgutil.h" #include "dictiter.h" +#include "gsrcwrap.h" /* #include "commgr.h" */ /* #include "compipe.h" */ #include "memstream.h" @@ -80,6 +81,12 @@ #define DEFAULT_PORT 10997 #define DEFAULT_LISTEN_PORT 4998 +#ifdef MEM_DEBUG +# define MEMPOOL cGlobals->util->mpool, +#else +# define MEMPOOL +#endif + static int blocking_read( int fd, unsigned char* buf, const int len ); XP_Bool @@ -120,6 +127,191 @@ streamFromFile( CommonGlobals* cGlobals, char* name ) return stream; } /* streamFromFile */ +void +tryConnectToServer( CommonGlobals* cGlobals ) +{ + LaunchParams* params = cGlobals->params; + XWStreamCtxt* stream = + mem_stream_make( MPPARM(cGlobals->util->mpool) params->vtMgr, + cGlobals, CHANNEL_NONE, + sendOnClose ); + (void)server_initClientConnection( cGlobals->game.server, + stream ); +} + +static bool +canMakeFromGI( const CurGameInfo* gi ) +{ + LOG_FUNC(); + bool result = 0 < gi->nPlayers + && 0 < gi->dictLang + ; + bool haveDict = !!gi->dictName; + bool allHaveDicts = true; + for ( int ii = 0; result && ii < gi->nPlayers; ++ii ) { + const LocalPlayer* lp = &gi->players[ii]; + result = !lp->isLocal || '\0' != lp->name[0]; + if ( allHaveDicts ) { + allHaveDicts = !!lp->dictName; + } + } + + result = result && (haveDict || allHaveDicts); + + LOG_RETURNF( "%d", result ); + XP_ASSERT( result ); + return result; +} + +void +linuxOpenGame( CommonGlobals* cGlobals, const TransportProcs* procs ) +{ + LOG_FUNC(); + XWStreamCtxt* stream = NULL; + XP_Bool opened = XP_FALSE; + + LaunchParams* params = cGlobals->params; + if ( !!params->fileName && file_exists( params->fileName ) ) { + stream = streamFromFile( cGlobals, params->fileName ); +#ifdef USE_SQLITE + } else if ( !!params->dbFileName && file_exists( params->dbFileName ) ) { + XP_UCHAR buf[32]; + XP_SNPRINTF( buf, sizeof(buf), "%d", params->dbFileID ); + mpool_setTag( MEMPOOL buf ); + stream = streamFromDB( cGlobals ); +#endif + } else if ( !!params->pDb && 0 <= cGlobals->rowid ) { + stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool) + params->vtMgr ); + if ( !loadGame( stream, params->pDb, cGlobals->rowid ) ) { + stream_destroy( stream ); + stream = NULL; + } + } + + if ( !!stream ) { + if ( NULL == cGlobals->dict ) { + cGlobals->dict = makeDictForStream( cGlobals, stream ); + } + + opened = game_makeFromStream( MEMPOOL stream, &cGlobals->game, + cGlobals->gi, cGlobals->dict, + &cGlobals->dicts, cGlobals->util, + cGlobals->draw, + &cGlobals->cp, procs ); + XP_LOGF( "%s: loaded gi at %p", __func__, &cGlobals->gi ); + stream_destroy( stream ); + } + + if ( !opened && canMakeFromGI( cGlobals->gi ) ) { + +#ifdef XWFEATURE_RELAY + /* if ( addr.conType == COMMS_CONN_RELAY ) { */ + /* XP_ASSERT( !!params->connInfo.relay.relayName ); */ + /* globals->cGlobals.defaultServerName */ + /* = params->connInfo.relay.relayName; */ + /* } */ +#endif + game_makeNewGame( MEMPOOL &cGlobals->game, cGlobals->gi, + cGlobals->util, cGlobals->draw, + &cGlobals->cp, procs +#ifdef SET_GAMESEED + , params->gameSeed +#endif + ); + + CommsAddrRec addr = cGlobals->addr; + // addr.conType = params->conType; + CommsConnType typ; + for ( XP_U32 st = 0; addr_iter( &addr, &typ, &st ); ) { + if ( params->commsDisableds[typ][0] ) { + comms_setAddrDisabled( cGlobals->game.comms, typ, XP_FALSE, XP_TRUE ); + } + if ( params->commsDisableds[typ][1] ) { + comms_setAddrDisabled( cGlobals->game.comms, typ, XP_TRUE, XP_TRUE ); + } + switch( typ ) { +#ifdef XWFEATURE_RELAY + case COMMS_CONN_RELAY: + /* addr.u.ip_relay.ipAddr = 0; */ + /* addr.u.ip_relay.port = params->connInfo.relay.defaultSendPort; */ + /* addr.u.ip_relay.seeksPublicRoom = params->connInfo.relay.seeksPublicRoom; */ + /* addr.u.ip_relay.advertiseRoom = params->connInfo.relay.advertiseRoom; */ + /* XP_STRNCPY( addr.u.ip_relay.hostName, params->connInfo.relay.relayName, */ + /* sizeof(addr.u.ip_relay.hostName) - 1 ); */ + /* XP_STRNCPY( addr.u.ip_relay.invite, params->connInfo.relay.invite, */ + /* sizeof(addr.u.ip_relay.invite) - 1 ); */ + break; +#endif +#ifdef XWFEATURE_BLUETOOTH + case COMMS_CONN_BT: + XP_ASSERT( sizeof(addr.u.bt.btAddr) + >= sizeof(params->connInfo.bt.hostAddr)); + XP_MEMCPY( &addr.u.bt.btAddr, ¶ms->connInfo.bt.hostAddr, + sizeof(params->connInfo.bt.hostAddr) ); + break; +#endif +#ifdef XWFEATURE_IP_DIRECT + case COMMS_CONN_IP_DIRECT: + XP_STRNCPY( addr.u.ip.hostName_ip, params->connInfo.ip.hostName, + sizeof(addr.u.ip.hostName_ip) - 1 ); + addr.u.ip.port_ip = params->connInfo.ip.port; + break; +#endif +#ifdef XWFEATURE_SMS + case COMMS_CONN_SMS: + /* No! Don't overwrite what may be a return address with local + stuff */ + /* XP_STRNCPY( addr.u.sms.phone, params->connInfo.sms.phone, */ + /* sizeof(addr.u.sms.phone) - 1 ); */ + /* addr.u.sms.port = params->connInfo.sms.port; */ + break; +#endif + default: + break; + } + } + + model_setDictionary( cGlobals->game.model, cGlobals->dict ); + setSquareBonuses( cGlobals ); + model_setPlayerDicts( cGlobals->game.model, &cGlobals->dicts ); + + /* Need to save in order to have a valid selRow for the first send */ + linuxSaveGame( cGlobals ); + +#ifndef XWFEATURE_STANDALONE_ONLY + /* This may trigger network activity */ + if ( !!cGlobals->game.comms ) { + comms_setAddr( cGlobals->game.comms, &addr ); + } +#endif + +#ifdef XWFEATURE_SEARCHLIMIT + cGlobals->gi->allowHintRect = params->allowHintRect; +#endif + + if ( params->needsNewGame ) { + XP_ASSERT(0); + // new_game_impl( globals, XP_FALSE ); +#ifndef XWFEATURE_STANDALONE_ONLY + } else { + DeviceRole serverRole = cGlobals->gi->serverRole; + if ( serverRole == SERVER_ISCLIENT ) { + tryConnectToServer( cGlobals ); + } +#endif + } + } + +#ifndef XWFEATURE_STANDALONE_ONLY + if ( !!cGlobals->game.comms ) { + comms_start( cGlobals->game.comms ); + } +#endif + server_do( cGlobals->game.server ); + linuxSaveGame( cGlobals ); /* again, to include address etc. */ +} + #ifdef USE_SQLITE XWStreamCtxt* streamFromDB( CommonGlobals* cGlobals ) @@ -189,7 +381,7 @@ gameGotBuf( CommonGlobals* cGlobals, XP_Bool hasDraw, const XP_U8* buf, if ( !!stream ) { redraw = game_receiveMessage( game, stream, from ); if ( redraw ) { - saveGame( cGlobals ); + linuxSaveGame( cGlobals ); } stream_destroy( stream ); @@ -328,7 +520,7 @@ strFromStream( XWStreamCtxt* stream ) } /* strFromStream */ void -saveGame( CommonGlobals* cGlobals ) +linuxSaveGame( CommonGlobals* cGlobals ) { LOG_FUNC(); sqlite3* pDb = cGlobals->params->pDb; @@ -336,7 +528,7 @@ saveGame( CommonGlobals* cGlobals ) (!!cGlobals->params->fileName || !!pDb) ) { XP_Bool doSave = XP_TRUE; XP_Bool newGame = !file_exists( cGlobals->params->fileName ) - || -1 == cGlobals->selRow; + || -1 == cGlobals->rowid; /* don't fail to save first time! */ if ( 0 < cGlobals->params->saveFailPct && !newGame ) { XP_U16 pct = XP_RANDOM() % 100; @@ -550,7 +742,14 @@ secondTimerFired( gpointer data ) void setOneSecondTimer( CommonGlobals* cGlobals ) { - (void)g_timeout_add_seconds( 1, secondTimerFired, cGlobals ); + guint id = g_timeout_add_seconds( 1, secondTimerFired, cGlobals ); + cGlobals->secondsTimerID = id; +} + +void +clearOneSecondTimer( CommonGlobals* cGlobals ) +{ + g_source_remove( cGlobals->secondsTimerID ); } #endif @@ -603,6 +802,7 @@ typedef enum { ,CMD_NOHEARTBEAT ,CMD_HOSTNAME ,CMD_CLOSESTDIN + ,CMD_NOCLOSESTDIN ,CMD_QUITAFTER ,CMD_BOARDSIZE ,CMD_HIDEVALUES @@ -655,6 +855,7 @@ typedef enum { #if defined PLATFORM_GTK && defined PLATFORM_NCURSES ,CMD_GTK ,CMD_CURSES + ,CMD_CURSES_LIST_HT #endif #if defined PLATFORM_GTK ,CMD_ASKNEWGAME @@ -725,6 +926,7 @@ static CmdInfoRec CmdInfoRecs[] = { ,{ CMD_NOHEARTBEAT, false, "no-heartbeat", "don't send heartbeats" } ,{ CMD_HOSTNAME, true, "host", "name of remote host" } ,{ CMD_CLOSESTDIN, false, "close-stdin", "close stdin on start" } + ,{ CMD_NOCLOSESTDIN, false, "no-close-stdin", "do not close stdin on start" } ,{ CMD_QUITAFTER, true, "quit-after", "exit seconds after game's done" } ,{ CMD_BOARDSIZE, true, "board-size", "board is by cells" } ,{ CMD_HIDEVALUES, false, "hide-values", "show letters, not nums, on tiles" } @@ -784,6 +986,7 @@ static CmdInfoRec CmdInfoRecs[] = { #if defined PLATFORM_GTK && defined PLATFORM_NCURSES ,{ CMD_GTK, false, "gtk", "use GTK for display" } ,{ CMD_CURSES, false, "curses", "use curses for display" } + ,{ CMD_CURSES_LIST_HT, true, "curses-list-ht", "how many cols tall is the games list" } #endif #if defined PLATFORM_GTK ,{ CMD_ASKNEWGAME, false, "ask-new", "put up ui for new game params" } @@ -1121,7 +1324,8 @@ linux_relay_ioproc( GIOChannel* source, GIOCondition condition, gpointer data ) unsigned char buf[1024]; int sock = g_io_channel_unix_get_fd( source ); if ( cGlobals->relaySocket != sock ) { - XP_LOGF( "%s: changing relaySocket from %d to %d", __func__, cGlobals->relaySocket, sock ); + XP_LOGF( "%s: changing relaySocket from %d to %d", __func__, + cGlobals->relaySocket, sock ); cGlobals->relaySocket = sock; } int nBytes = linux_relay_receive( cGlobals, sock, buf, sizeof(buf) ); @@ -1164,9 +1368,9 @@ linux_relay_send( CommonGlobals* cGlobals, const XP_U8* buf, XP_U16 buflen, { XP_S16 result = 0; if ( cGlobals->params->useUdp ) { - XP_ASSERT( -1 != cGlobals->selRow ); + XP_ASSERT( -1 != cGlobals->rowid ); XP_U16 seed = comms_getChannelSeed( cGlobals->game.comms ); - XP_U32 clientToken = makeClientToken( cGlobals->selRow, seed ); + XP_U32 clientToken = makeClientToken( cGlobals->rowid, seed ); result = relaycon_send( cGlobals->params, buf, buflen, clientToken, addrRec ); } else { @@ -1176,7 +1380,7 @@ linux_relay_send( CommonGlobals* cGlobals, const XP_U8* buf, XP_U16 buflen, if ( sock == -1 ) { XP_LOGF( "%s: socket uninitialized", __func__ ); sock = linux_init_relay_socket( cGlobals, addrRec ); - (*cGlobals->socketAdded)( cGlobals, sock, linux_relay_ioproc ); + ADD_SOCKET( cGlobals, sock, linux_relay_ioproc ); } if ( sock != -1 ) { @@ -1262,7 +1466,7 @@ linux_send( const XP_U8* buf, XP_U16 buflen, const XP_UCHAR* XP_UNUSED_DBG(msgNo #endif #if defined XWFEATURE_BLUETOOTH case COMMS_CONN_BT: { - XP_Bool isServer = comms_getIsServer( cGlobals->game.comms ); + XP_Bool isServer = game_getIsServer( &cGlobals->game ); linux_bt_open( cGlobals, isServer ); nSent = linux_bt_send( buf, buflen, addrRec, cGlobals ); } @@ -1506,7 +1710,7 @@ linux_util_addrChange( XW_UtilCtxt* uc, switch ( typ ) { #ifdef XWFEATURE_BLUETOOTH case COMMS_CONN_BT: { - XP_Bool isServer = comms_getIsServer( cGlobals->game.comms ); + XP_Bool isServer = game_getIsServer( &cGlobals->game ); linux_bt_open( cGlobals, isServer ); } break; @@ -1530,18 +1734,10 @@ linux_util_addrChange( XW_UtilCtxt* uc, } } -void -linuxSetIsServer( CommonGlobals* cGlobals, XP_Bool isServer ) -{ - XP_LOGF( "%s(isServer=%d)", __func__, isServer ); - DeviceRole newRole = isServer? SERVER_ISSERVER : SERVER_ISCLIENT; - cGlobals->params->serverRole = newRole; - cGlobals->gi->serverRole = newRole; -} - -void -linuxChangeRoles( CommonGlobals* cGlobals ) +static gint +changeRolesIdle( gpointer data ) { + CommonGlobals* cGlobals = (CommonGlobals*)data; ServerCtxt* server = cGlobals->game.server; server_reset( server, cGlobals->game.comms ); if ( SERVER_ISCLIENT == cGlobals->gi->serverRole ) { @@ -1551,7 +1747,23 @@ linuxChangeRoles( CommonGlobals* cGlobals ) (void)server_initClientConnection( server, stream ); } (void)server_do( server ); + return 0; } + +static void +linux_util_setIsServer( XW_UtilCtxt* uc, XP_Bool isServer ) +{ + XP_LOGF( "%s(isServer=%d)", __func__, isServer ); + CommonGlobals* cGlobals = (CommonGlobals*)uc->closure; + + DeviceRole newRole = isServer? SERVER_ISSERVER : SERVER_ISCLIENT; + cGlobals->params->serverRole = newRole; + cGlobals->gi->serverRole = newRole; + + (void)ADD_ONETIME_IDLE( changeRolesIdle, cGlobals ); + XP_ASSERT( isServer == game_getIsServer( &cGlobals->game ) ); +} + #endif unsigned int @@ -1906,59 +2118,22 @@ setupLinuxUtilCallbacks( XW_UtilCtxt* util ) #ifndef XWFEATURE_STANDALONE_ONLY util->vtable->m_util_informMissing = linux_util_informMissing; util->vtable->m_util_addrChange = linux_util_addrChange; + util->vtable->m_util_setIsServer = linux_util_setIsServer; #endif } -/* Set up cGlobals->gi and cGlobals->addr based on params fields */ void -initFromParams( CommonGlobals* cGlobals, LaunchParams* params ) +assertAllCallbacksSet( XW_UtilCtxt* util ) { LOG_FUNC(); - /* CurGameInfo */ - cGlobals->gi = ¶ms->pgi; - - /* addr */ - CommsAddrRec* addr = &cGlobals->addr; - XP_MEMSET( addr, 0, sizeof(*addr) ); - - CommsConnType typ; - for ( XP_U32 st = 0; addr_iter( ¶ms->addr, &typ, &st ); ) { - switch( typ ) { -#ifdef XWFEATURE_RELAY - case COMMS_CONN_RELAY: - addr_addType( addr, COMMS_CONN_RELAY ); - addr->u.ip_relay.ipAddr = 0; /* ??? */ - addr->u.ip_relay.port = params->connInfo.relay.defaultSendPort; - addr->u.ip_relay.seeksPublicRoom = - params->connInfo.relay.seeksPublicRoom; - addr->u.ip_relay.advertiseRoom = params->connInfo.relay.advertiseRoom; - XP_STRNCPY( addr->u.ip_relay.hostName, - params->connInfo.relay.relayName, - sizeof(addr->u.ip_relay.hostName) - 1 ); - XP_STRNCPY( addr->u.ip_relay.invite, params->connInfo.relay.invite, - sizeof(addr->u.ip_relay.invite) - 1 ); - break; -#endif -#ifdef XWFEATURE_SMS - case COMMS_CONN_SMS: - addr_addType( addr, COMMS_CONN_SMS ); - XP_STRNCPY( addr->u.sms.phone, params->connInfo.sms.myPhone, - sizeof(addr->u.sms.phone) - 1 ); - addr->u.sms.port = params->connInfo.sms.port; - break; -#endif -#ifdef XWFEATURE_BLUETOOTH - case COMMS_CONN_BT: - addr_addType( addr, COMMS_CONN_BT ); - XP_ASSERT( sizeof(addr->u.bt.btAddr) - >= sizeof(params->connInfo.bt.hostAddr)); - XP_MEMCPY( &addr->u.bt.btAddr, ¶ms->connInfo.bt.hostAddr, - sizeof(params->connInfo.bt.hostAddr) ); - break; -#endif - default: - break; + XWStreamCtxt* (**proc)(XW_UtilCtxt*, XP_PlayerAddr ) = + &util->vtable->m_util_makeStreamFromAddr; + for ( int ii = 0; ii < sizeof(*util->vtable)/sizeof(*proc); ++ii ) { + if ( !*proc ) { + XP_LOGF( "%s(): null ptr at index %d", __func__, ii ); + XP_ASSERT( 0 ); } + ++proc; } } @@ -1972,6 +2147,12 @@ setupUtil( CommonGlobals* cGlobals ) setupLinuxUtilCallbacks( util ); } +void +disposeUtil( CommonGlobals* cGlobals ) +{ + linux_util_vt_destroy( cGlobals->util ); +} + static void initParams( LaunchParams* params ) { @@ -2000,6 +2181,9 @@ initParams( LaunchParams* params ) static void freeParams( LaunchParams* params ) { + closeGamesDB( params->pDb ); + params->pDb = NULL; + vtmgr_destroy( MPPARM(params->mpool) params->vtMgr ); dutils_free( ¶ms->dutil ); dmgr_destroy( params->dictMgr ); @@ -2116,6 +2300,7 @@ main( int argc, char** argv ) mainParams.useMmap = XP_TRUE; mainParams.useUdp = true; mainParams.dbName = "xwgames.sqldb"; + mainParams.cursesListWinHt = 5; char* envDictPath = getenv( "XW_DICTDIR" ); XP_LOGF( "%s: envDictPath=%s", __func__, envDictPath ); @@ -2180,6 +2365,7 @@ main( int argc, char** argv ) if ( !path ) { path = "."; } + XP_LOGF( "%s(): appending dict path: %s", __func__, path ); mainParams.dictDirs = g_slist_append( mainParams.dictDirs, path ); break; #ifdef XWFEATURE_WALKDICT @@ -2376,6 +2562,9 @@ main( int argc, char** argv ) case CMD_CLOSESTDIN: mainParams.closeStdin = XP_TRUE; break; + case CMD_NOCLOSESTDIN: + mainParams.closeStdin = XP_FALSE; + break; case CMD_QUITAFTER: mainParams.quitAfter = atoi(optarg); break; @@ -2481,6 +2670,9 @@ main( int argc, char** argv ) case CMD_CURSES: mainParams.useCurses = XP_TRUE; break; + case CMD_CURSES_LIST_HT: + mainParams.cursesListWinHt = atoi(optarg); + break; #endif #if defined PLATFORM_GTK case CMD_ASKNEWGAME: @@ -2539,17 +2731,22 @@ main( int argc, char** argv ) } } + XP_LOGF( "%s(): here: %s", __func__, mainParams.pgi.dictName ); if ( !!mainParams.pgi.dictName ) { + XP_LOGF( "%s(): there", __func__ ); /* char path[256]; */ /* getDictPath( &mainParams, mainParams.gi.dictName, path, VSIZE(path) ); */ - /* mainParams.dict = */ - /* linux_dictionary_make( MPPARM(mainParams.mpool) &mainParams, */ - /* mainParams.pgi.dictName, */ - /* mainParams.useMmap ); */ - /* XP_ASSERT( !!mainParams.dict ); */ - /* mainParams.pgi.dictLang = dict_getLangCode( mainParams.dict ); */ + DictionaryCtxt* dict = + linux_dictionary_make( MPPARM(mainParams.mpool) &mainParams, + mainParams.pgi.dictName, + mainParams.useMmap ); + XP_ASSERT( !!dict ); + mainParams.pgi.dictLang = dict_getLangCode( dict ); + XP_LOGF( "%s(): set lang code: %d", __func__, mainParams.pgi.dictLang ); + dict_unref( dict ); } else if ( isServer ) { #ifdef STUBBED_DICT + foo mainParams.dict = make_stubbed_dict( MPPARM_NOCOMMA(mainParams.util->mpool) ); XP_WARNF( "no dictionary provided: using English stub dict\n" ); @@ -2666,21 +2863,19 @@ main( int argc, char** argv ) mainParams.serverRole = SERVER_ISCLIENT; } - /* if ( mainParams.needsNewGame ) { */ - /* gi_disposePlayerInfo( MPPARM(mainParams.mpool) &mainParams.pgi ); */ - /* gi_initPlayerInfo( MPPARM(mainParams.mpool) &mainParams.pgi, NULL ); */ - /* } */ - + XP_ASSERT( !!mainParams.dbName ); + mainParams.pDb = openGamesDB( mainParams.dbName ); + if ( mainParams.useCurses ) { - if ( mainParams.needsNewGame ) { - /* curses doesn't have newgame dialog */ - usage( argv[0], "game params required for curses version, e.g. --name Eric --room MyRoom" - " --remote-player --dict-dir ../ --game-dict CollegeEng_2to8.xwd"); - } else { + /* if ( mainParams.needsNewGame ) { */ + /* /\* curses doesn't have newgame dialog *\/ */ + /* usage( argv[0], "game params required for curses version, e.g. --name Eric --room MyRoom" */ + /* " --remote-player --dict-dir ../ --game-dict CollegeEng_2to8.xwd"); */ + /* } else { */ #if defined PLATFORM_NCURSES - cursesmain( isServer, &mainParams ); + cursesmain( isServer, &mainParams ); #endif - } + /* } */ } else { #if defined PLATFORM_GTK gtk_init( &argc, &argv ); @@ -2694,6 +2889,8 @@ main( int argc, char** argv ) free( longopts ); g_slist_free( mainParams.dictDirs ); + gsw_logIdles(); + XP_LOGF( "%s exiting main, returning %d", argv[0], result ); return result; } /* main */ diff --git a/xwords4/linux/linuxmain.h b/xwords4/linux/linuxmain.h index e03fed979..5f5fd93cf 100644 --- a/xwords4/linux/linuxmain.h +++ b/xwords4/linux/linuxmain.h @@ -70,7 +70,7 @@ void writeToFile( XWStreamCtxt* stream, void* closure ); XP_Bool getDictPath( const LaunchParams *params, const char* name, char* result, int resultLen ); GSList* listDicts( const LaunchParams *params ); -void saveGame( CommonGlobals* cGlobals ); +void linuxSaveGame( CommonGlobals* cGlobals ); void linux_close_socket( CommonGlobals* cGlobals ); @@ -87,18 +87,18 @@ void do_nbs_then_close( CommonGlobals* cGlobals, #ifdef USE_GLIBLOOP void setOneSecondTimer( CommonGlobals* cGlobals ); +void clearOneSecondTimer( CommonGlobals* cGlobals ); #else # define setOneSecondTimer( cGlobals ) #endif void setupLinuxUtilCallbacks( XW_UtilCtxt* util ); -void initFromParams( CommonGlobals* cGlobals, LaunchParams* params ); +void assertAllCallbacksSet( XW_UtilCtxt* util ); void setupUtil( CommonGlobals* cGlobals ); +void disposeUtil( CommonGlobals* cGlobals ); DictionaryCtxt* makeDictForStream( CommonGlobals* cGlobals, XWStreamCtxt* stream ); -void linuxSetIsServer( CommonGlobals* cGlobals, XP_Bool isServer ); -void linuxChangeRoles( CommonGlobals* cGlobals ); void sendRelayReg( LaunchParams* params, sqlite3* pDb ); void gameGotBuf( CommonGlobals* globals, XP_Bool haveDraw, @@ -111,6 +111,8 @@ void linux_doInitialReg( LaunchParams* params, XP_Bool idIsNew ); XP_Bool linux_setupDevidParams( LaunchParams* params ); unsigned int makeRandomInt(); +void linuxOpenGame( CommonGlobals* cGlobals, const TransportProcs* procs ); +void tryConnectToServer( CommonGlobals* cGlobals ); /* void initParams( LaunchParams* params ); */ /* void freeParams( LaunchParams* params ); */ diff --git a/xwords4/linux/linuxsms.h b/xwords4/linux/linuxsms.h index 323b3e504..fffc41c94 100644 --- a/xwords4/linux/linuxsms.h +++ b/xwords4/linux/linuxsms.h @@ -34,7 +34,6 @@ typedef struct _SMSProcs { void (*devIDReceived)( void* closure, const XP_UCHAR* devID, XP_U16 maxInterval ); void (*msgErrorMsg)( void* closure, const XP_UCHAR* msg ); - SocketAddedFunc socketAdded; } SMSProcs; diff --git a/xwords4/linux/main.h b/xwords4/linux/main.h index 0ed10b6cf..74f01fc73 100644 --- a/xwords4/linux/main.h +++ b/xwords4/linux/main.h @@ -114,6 +114,7 @@ typedef struct LaunchParams { XP_U16 splitPackets; XP_U16 chatsInterval; /* 0 means disabled */ XP_U16 askTimeout; + int cursesListWinHt; #ifdef XWFEATURE_SEARCHLIMIT XP_Bool allowHintRect; #endif @@ -170,7 +171,7 @@ typedef struct LaunchParams { typedef struct CommonGlobals CommonGlobals; -typedef void (*SocketAddedFunc)( void* closure, int newsock, GIOFunc func ); +typedef guint (*SocketAddedFunc)( void* closure, int newsock, GIOFunc func ); typedef XP_Bool (*Acceptor)( int sock, void* ctxt ); typedef void (*AddAcceptorFunc)(int listener, Acceptor func, CommonGlobals* globals, void** storage ); @@ -189,11 +190,13 @@ typedef void (*OnSaveFunc)( void* closure, sqlite3_int64 rowid, XP_Bool firstTime ); struct CommonGlobals { + CurGameInfo _gi; LaunchParams* params; CommonPrefs cp; XW_UtilCtxt* util; XWGame game; + DrawCtx* draw; CurGameInfo* gi; CommsAddrRec addr; DictionaryCtxt* dict; @@ -202,9 +205,8 @@ struct CommonGlobals { XP_U16 lastStreamSize; XP_U16 nMissing; XP_Bool manualFinal; /* use asked for final scores */ - sqlite3_int64 selRow; + sqlite3_int64 rowid; - SocketAddedFunc socketAdded; void* socketAddedClosure; OnSaveFunc onSave; void* onSaveClosure; @@ -248,10 +250,16 @@ struct CommonGlobals { #endif TimerInfo timerInfo[NUM_TIMERS_PLUS_ONE]; + guint secondsTimerID; XP_U16 curSaveToken; }; +typedef struct _CommonAppGlobals { + LaunchParams* params; + GSList* globalsList; +} CommonAppGlobals; + typedef struct _SourceData { GIOChannel* channel; gint watch; @@ -261,9 +269,8 @@ typedef struct _SourceData { #ifdef PLATFORM_GTK typedef struct _GtkAppGlobals { + CommonAppGlobals cag; GArray* selRows; - LaunchParams* params; - GSList* globalsList; GList* sources; GtkWidget* window; GtkWidget* listWidget; diff --git a/xwords4/linux/relaycon.c b/xwords4/linux/relaycon.c index 7ffbeab9f..659ee38b5 100644 --- a/xwords4/linux/relaycon.c +++ b/xwords4/linux/relaycon.c @@ -28,6 +28,7 @@ #include "linuxmain.h" #include "comtypes.h" #include "gamesdb.h" +#include "gsrcwrap.h" #define MAX_MOVE_CHECK_MS ((XP_U16)(1000 * 60 * 60 * 24)) #define RELAY_API_PROTO "http" @@ -265,7 +266,7 @@ relaycon_init( LaunchParams* params, const RelayConnProcs* procs, XP_MEMCPY( storage->host, host, XP_STRLEN(host) + 1 ); } else { storage->socket = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP ); - (*procs->socketAdded)( storage, storage->socket, relaycon_receive ); + ADD_SOCKET( storage, storage->socket, relaycon_receive ); XP_MEMSET( &storage->saddr, 0, sizeof(storage->saddr) ); storage->saddr.sin_family = PF_INET; @@ -705,6 +706,7 @@ process( RelayConStorage* storage, XP_U8* buf, ssize_t nRead ) XP_LOGF( "%s: error reading udp socket: %d (%s)", __func__, errno, strerror(errno) ); } + LOG_RETURNF( "%d", TRUE ); return TRUE; } @@ -719,21 +721,24 @@ relaycon_receive( GIOChannel* source, GIOCondition XP_UNUSED_DBG(condition), gpo socklen_t fromlen = sizeof(from); int socket = g_io_channel_unix_get_fd( source ); - XP_LOGF( "%s: calling recvfrom on socket %d", __func__, socket ); ssize_t nRead = recvfrom( socket, buf, sizeof(buf), 0, /* flags */ (struct sockaddr*)&from, &fromlen ); gchar* b64 = g_base64_encode( (const guchar*)buf, ((0 <= nRead)? nRead : 0) ); - XP_LOGF( "%s: read %zd bytes ('%s')", __func__, nRead, b64 ); #ifdef COMMS_CHECKSUM gchar* sum = g_compute_checksum_for_data( G_CHECKSUM_MD5, buf, nRead ); XP_LOGF( "%s: read %zd bytes ('%s')(sum=%s)", __func__, nRead, b64, sum ); g_free( sum ); +#else + XP_LOGF( "%s: read %zd bytes ('%s')", __func__, nRead, b64 ); #endif g_free( b64 ); - return process( storage, buf, nRead ); + + gboolean result = process( storage, buf, nRead ); + // LOG_RETURNF( "%d", result ); + return result; } void diff --git a/xwords4/linux/relaycon.h b/xwords4/linux/relaycon.h index aafab5133..7c2acd96b 100644 --- a/xwords4/linux/relaycon.h +++ b/xwords4/linux/relaycon.h @@ -34,7 +34,6 @@ typedef struct _Procs { XP_U16 maxInterval ); void (*msgErrorMsg)( void* closure, const XP_UCHAR* msg ); void (*inviteReceived)( void* closure, NetLaunchInfo* invit ); - SocketAddedFunc socketAdded; } RelayConnProcs; void relaycon_init( LaunchParams* params, const RelayConnProcs* procs, diff --git a/xwords4/linux/scripts/discon_ok2.py b/xwords4/linux/scripts/discon_ok2.py index 33c811c52..92797940c 100755 --- a/xwords4/linux/scripts/discon_ok2.py +++ b/xwords4/linux/scripts/discon_ok2.py @@ -163,7 +163,7 @@ class Device(): sTilesLeftTrayPat = re.compile('.*player \d+ now has (\d+) tiles') sRelayIDPat = re.compile('.*UPDATE games.*seed=(\d+),.*relayid=\'([^\']+)\'.*') - def __init__(self, args, game, indx, params, room, peers, db, log, nInGame): + def __init__(self, args, game, indx, params, room, peers, db, log, script, nInGame): self.game = game self.indx = indx self.args = args @@ -173,6 +173,7 @@ class Device(): self.room = room self.db = db self.logPath = log + self.script = script self.nInGame = nInGame # runtime stuff; init now self.app = args.APP_OLD @@ -198,6 +199,8 @@ class Device(): if pct >= random.randint(0, 99): print('launch(): upgrading from ', self.app, ' to ', self.args.APP_NEW) self.app = self.args.APP_NEW + # nuke script to force regeneration + os.unlink(self.script) def logReaderMain(self): assert self and self.proc @@ -234,19 +237,26 @@ class Device(): # print('logReaderMain done, wrote lines:', nLines, 'to', self.logPath); + def checkScript(self): + if not os.path.exists(self.script): + args = ['exec'] # without exec means terminate() won't work + if self.args.VALGRIND: + args += ['valgrind'] + # args += ['--leak-check=full'] + # args += ['--track-origins=yes'] + args += [self.app] + [str(p) for p in self.params] + if self.devID: args.extend( ' '.split(self.devID)) + args += [ '$*' ] + with open( self.script, 'w' ) as fil: + fil.write( "#!/bin/sh\n" ) + fil.write( ' '.join(args) + '\n' ) + os.chmod(self.script, 0o755) + def launch(self): - args = [] - if self.args.VALGRIND: - args += ['valgrind'] - # args += ['--leak-check=full'] - # args += ['--track-origins=yes'] - - # Upgrade if appropriate self.setApp(self.args.UPGRADE_PCT) - - args += [self.app] + [str(p) for p in self.params] - if self.devID: args.extend( ' '.split(self.devID)) + self.checkScript() self.launchCount += 1 + args = [ self.script ] self.proc = subprocess.Popen(args, stdout = subprocess.DEVNULL, stderr = subprocess.PIPE, universal_newlines = True) self.pid = self.proc.pid @@ -287,8 +297,8 @@ class Device(): def moveFiles(self): assert not self.running() - shutil.move(self.logPath, self.args.LOGDIR + '/done') - shutil.move(self.db, self.args.LOGDIR + '/done') + for fil in [ self.logPath, self.db, self.script ]: + shutil.move(fil, self.args.LOGDIR + '/done') def send_dead(self): if self.args.ADD_RELAY: @@ -393,6 +403,7 @@ def build_cmds(args): DEV += 1 DB = '{}/{:02d}_{:02d}_DB.sql3'.format(args.LOGDIR, GAME, DEV) LOG = '{}/{:02d}_{:02d}_LOG.txt'.format(args.LOGDIR, GAME, DEV) + SCRIPT = '{}/start_{:02d}_{:02d}.sh'.format(args.LOGDIR, GAME, DEV) PARAMS = player_params(args, NLOCALS, NPLAYERS, DEV) PARAMS += PLAT_PARMS @@ -442,7 +453,7 @@ def build_cmds(args): # print('PARAMS:', PARAMS) - dev = Device(args, GAME, COUNTER, PARAMS, ROOM, peers, DB, LOG, len(LOCALS)) + dev = Device(args, GAME, COUNTER, PARAMS, ROOM, peers, DB, LOG, SCRIPT, len(LOCALS)) peers.add(dev) dev.update_ldevid() devs.append(dev) @@ -515,7 +526,7 @@ def summarizeTileCounts(devs, endTime, state): state['lastChange'] = now state['tilesStr'] = tilesStr - return now - state['lastChange'] < datetime.timedelta(minutes = 1) + return now - state['lastChange'] < datetime.timedelta(seconds = 30) def countCores(): return len(glob.glob1('/tmp',"core*")) @@ -549,18 +560,7 @@ def run_cmds(args, devs): if dev.handleAllDone(): devs.remove(dev) else: -# if [ -n "$ONE_PER_ROOM" -a 0 -ne ${ROOM_PIDS[$ROOM]} ]; then -# continue -# fi -# try_upgrade $KEY -# try_upgrade_upd $KEY dev.launch() -# PID=$! -# # renice doesn't work on one of my machines... -# renice -n 1 -p $PID >/dev/null 2>&1 || /bin/true -# PIDS[$KEY]=$PID -# ROOM_PIDS[$ROOM]=$PID -# MINEND[$KEY]=$(($NOW + $MINRUN)) elif dev.minTimeExpired(): dev.kill() if dev.handleAllDone(): diff --git a/xwords4/linux/scripts/run-curses.sh b/xwords4/linux/scripts/run-curses.sh index 7aced164b..a5e9ea4a3 100755 --- a/xwords4/linux/scripts/run-curses.sh +++ b/xwords4/linux/scripts/run-curses.sh @@ -3,7 +3,26 @@ # This script just runs the curses app with a set of params known to # work. At least when it was last committed. :-) +usage() { + echo "usage: $0 [--help] [param-for-xwords]*" + exit 1 + } + +PARAMS='' +while [ $# -gt 0 ]; do + case $1 in + --help) + usage; + ;; + *) + PARAMS="$PARAMS $1" + ;; + esac + shift +done + WD=$(cd $(dirname $0)/.. && pwd) cd $WD -./obj_linux_memdbg/xwords --curses --name Eric --robot Kati --dict-dir ./ --game-dict ../dict.xwd 2>/dev/null - +./obj_linux_memdbg/xwords --curses --name Eric --name Kati \ + --dict-dir ./ --game-dict ../dict.xwd \ + $PARAMS \