From 598be04befcfd41cc85767deecf587bde2b17f3e Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 24 Jan 2020 09:05:16 -0800 Subject: [PATCH] make curses app more like the rest Lots of changes adding a games-list view to the app from which you create new games, open and delete existing ones, etc. There's still plenty that's unimplemented, but it's already more useful for testing and development. Which is the point. --- xwords4/common/comms.c | 27 +- xwords4/common/device.c | 2 +- xwords4/common/game.c | 8 +- xwords4/common/game.h | 1 + xwords4/common/server.c | 12 +- xwords4/common/smsproto.c | 8 +- xwords4/common/smsproto.h | 2 + xwords4/linux/Makefile | 10 +- xwords4/linux/curgamlistwin.c | 293 ++++ xwords4/linux/curgamlistwin.h | 43 + xwords4/linux/cursesask.c | 16 +- xwords4/linux/cursesask.h | 2 +- xwords4/linux/cursesboard.c | 1351 +++++++++++++++++ xwords4/linux/cursesboard.h | 42 + xwords4/linux/cursesdraw.c | 8 + xwords4/linux/cursesletterask.c | 8 +- xwords4/linux/cursesletterask.h | 2 +- xwords4/linux/cursesmain.c | 2109 +++++++-------------------- xwords4/linux/cursesmain.h | 59 +- xwords4/linux/cursesmenu.c | 213 +++ xwords4/linux/cursesmenu.h | 54 + xwords4/linux/gamesdb.c | 67 +- xwords4/linux/gamesdb.h | 5 + xwords4/linux/gsrcwrap.c | 183 +++ xwords4/linux/gsrcwrap.h | 37 + xwords4/linux/gtkboard.c | 432 ++---- xwords4/linux/gtkboard.h | 6 +- xwords4/linux/gtkdraw.c | 8 +- xwords4/linux/gtkmain.c | 104 +- xwords4/linux/gtkmain.h | 3 +- xwords4/linux/gtknewgame.c | 4 +- xwords4/linux/gtknewgame.h | 6 +- xwords4/linux/lindutil.c | 2 +- xwords4/linux/linuxbt.c | 5 +- xwords4/linux/linuxmain.c | 367 +++-- xwords4/linux/linuxmain.h | 10 +- xwords4/linux/linuxsms.h | 1 - xwords4/linux/main.h | 17 +- xwords4/linux/relaycon.c | 13 +- xwords4/linux/relaycon.h | 1 - xwords4/linux/scripts/discon_ok2.py | 52 +- xwords4/linux/scripts/run-curses.sh | 23 +- 42 files changed, 3437 insertions(+), 2179 deletions(-) create mode 100644 xwords4/linux/curgamlistwin.c create mode 100644 xwords4/linux/curgamlistwin.h create mode 100644 xwords4/linux/cursesboard.c create mode 100644 xwords4/linux/cursesboard.h create mode 100644 xwords4/linux/cursesmenu.c create mode 100644 xwords4/linux/cursesmenu.h create mode 100644 xwords4/linux/gsrcwrap.c create mode 100644 xwords4/linux/gsrcwrap.h 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 \