xwords/xwords4/linux/cursesmain.c
2024-02-02 21:51:37 -08:00

2059 lines
67 KiB
C

/* -*- compile-command: "make MEMDEBUG=TRUE -j5"; -*- */
/*
* 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.
*/
#ifdef PLATFORM_NCURSES
#include <ncurses.h>
#include <signal.h>
#include <assert.h>
#include <ctype.h>
#include <sys/time.h>
#include <time.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netdb.h> /* gethostbyname */
#include <errno.h>
//#include <net/netinet.h>
#include <sys/poll.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cjson/cJSON.h>
#include "linuxmain.h"
#include "linuxutl.h"
#include "linuxdict.h"
#include "cursesmain.h"
#include "cursesask.h"
#include "cursesletterask.h"
#include "linuxbt.h"
#include "model.h"
#include "draw.h"
#include "board.h"
#include "engine.h"
/* #include "compipe.h" */
#include "xwstream.h"
#include "xwstate.h"
#include "strutils.h"
#include "server.h"
#include "memstream.h"
#include "util.h"
#include "dbgutil.h"
#include "linuxsms.h"
#include "linuxudp.h"
#include "gamesdb.h"
#include "relaycon.h"
#include "mqttcon.h"
#include "smsproto.h"
#include "device.h"
#include "cursesmenu.h"
#include "cursesboard.h"
#include "curgamlistwin.h"
#include "gsrcwrap.h"
#ifndef CURSES_CELL_HT
# define CURSES_CELL_HT 1
#endif
#ifndef CURSES_CELL_WIDTH
# define CURSES_CELL_WIDTH 2
#endif
#define INFINITE_TIMEOUT -1
#define BOARD_SCORE_PADDING 3
struct CursesAppGlobals {
CommonAppGlobals cag;
CursesMenuState* menuState;
CursGameList* gameList;
CursesBoardState* cbState;
WINDOW* mainWin;
int winWidth, winHeight;
XP_U16 nLinesMenu;
gchar* lastErr;
short statusLine;
struct sockaddr_in listenerSockAddr;
#ifdef USE_GLIBLOOP
GMainLoop* loop;
GList* sources;
int quitpipe[2];
int winchpipe[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' },
{ 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", "<spc>", ' ' }, */
/* { handleRet, "Click/tap", "<ret>", '\r' }, */
/* { handleHint, "Hint", "?", '?' }, */
/* #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' }, */
/* { handleBackspace, "Remove from board", "<del>", 8 }, */
/* { handleUndo, "Undo prev", "U", 'U' }, */
/* { handleReplace, "uNdo cur", "N", 'N' }, */
{ NULL, NULL, NULL, '\0'}
};
#ifdef CURSES_SMALL_SCREEN
const MenuList g_rootMenuListShow[] = {
{ handleRootKeyShow, "Press . for menu", "", '.' },
{ NULL, NULL, NULL, '\0'}
};
const MenuList g_rootMenuListHide[] = {
{ handleRootKeyHide, "Clear menu", ".", '.' },
{ NULL, NULL, NULL, '\0'}
};
#endif
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 ); */
#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 ); */
#ifdef MEM_DEBUG
# define MEMPOOL cGlobals->util->mpool,
#else
# define MEMPOOL
#endif
/* extern int errno; */
static void
initCurses( CursesAppGlobals* aGlobals )
{
/* ncurses man page says most apps want this sequence */
if ( !aGlobals->cag.params->closeStdin ) {
aGlobals->mainWin = initscr();
cbreak();
noecho();
nonl();
intrflush(stdscr, FALSE);
keypad(stdscr, TRUE); /* effects wgetch only? */
getmaxyx( aGlobals->mainWin, aGlobals->winHeight, aGlobals->winWidth );
XP_LOGFF( "getmaxyx()->w:%d; h:%d", aGlobals->winWidth,
aGlobals->winHeight );
}
/* 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 *\/ */
} /* initCurses */
#if 0
static void
showStatus( CursesAppGlobals* globals )
{
char* str;
switch ( globals->state ) {
case XW_SERVER_WAITING_CLIENT_SIGNON:
str = "Waiting for client[s] to connnect";
break;
case XW_SERVER_READY_TO_PLAY:
str = "It's somebody's move";
break;
default:
str = "unknown state";
}
standout();
mvaddstr( globals->statusLine, 0, str );
/* clrtoeol(); */
standend();
refresh();
} /* showStatus */
#endif
bool
handleQuit( void* closure, int XP_UNUSED(key) )
{
CursesAppGlobals* globals = (CursesAppGlobals*)closure;
g_main_loop_quit( globals->loop );
return XP_TRUE;
} /* handleQuit */
static void
invokeQuit( void* data )
{
LaunchParams* params = (LaunchParams*)data;
CursesAppGlobals* globals = (CursesAppGlobals*)params->appGlobals;
handleQuit( globals, 0 );
}
static void
figureDims( CursesAppGlobals* aGlobals, cb_dims* dims )
{
LaunchParams* params = aGlobals->cag.params;
dims->width = aGlobals->winWidth;
dims->top = params->cursesListWinHt;
dims->height = aGlobals->winHeight - params->cursesListWinHt - MENU_WINDOW_HEIGHT;
}
static bool
handleOpenGame( void* closure, int XP_UNUSED(key) )
{
LOG_FUNC();
CursesAppGlobals* aGlobals = (CursesAppGlobals*)closure;
const GameInfo* gi = cgl_getSel( aGlobals->gameList );
XP_ASSERT( !!gi );
cb_dims dims;
figureDims( aGlobals, &dims );
cb_open( aGlobals->cbState, gi->rowid, &dims );
return XP_TRUE;
}
static bool
canMakeFromGI( const CurGameInfo* gi )
{
LOG_FUNC();
bool result = 0 < gi->nPlayers
&& !!gi->isoCodeStr[0]
;
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 || (!!lp->name && '\0' != lp->name[0]);
if ( allHaveDicts ) {
allHaveDicts = !!lp->dictName;
}
}
result = result && (haveDict || allHaveDicts);
LOG_RETURNF( "%s", boolToStr(result) );
return result;
}
static bool
handleNewGame( void* closure, int XP_UNUSED(key) )
{
LOG_FUNC();
CursesAppGlobals* aGlobals = (CursesAppGlobals*)closure;
// aGlobals->cag.params->needsNewGame = XP_FALSE;
cb_dims dims;
figureDims( aGlobals, &dims );
const CurGameInfo* gi = &aGlobals->cag.params->pgi;
if ( !canMakeFromGI(gi) ) {
ca_inform( aGlobals->mainWin, "Unable to create game (check params?)" );
} else if ( !cb_new( aGlobals->cbState, &dims, NULL, NULL ) ) {
XP_ASSERT(0);
}
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 ) {
gdb_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
handleRootKeyShow( CursesAppGlobals* globals )
{
WINDOW* win;
MenuList* lists[] = { g_sharedMenuList, globals->menuList,
g_rootMenuListHide, NULL };
int winMaxY, winMaxX;
wclear( globals->menuWin );
wrefresh( globals->menuWin );
getmaxyx( globals->boardWin, winMaxY, winMaxX );
int border = 2;
int width = winMaxX - (border * 2);
int padding = 1; /* for the box */
int nLines, nCols;
countMenuLines( lists, width, padding, &nLines, &nCols );
if ( width > nCols ) {
width = nCols;
}
win = newwin( nLines+(padding*2), width+(padding*2),
((winMaxY-nLines-padding-padding)/2), (winMaxX-width)/2 );
wclear( win );
box( win, '|', '-');
drawMenuFromList( win, lists, nLines, padding );
wrefresh( win );
CursesMenuHandler handler = NULL;
while ( !handler ) {
int ch = fgetc( stdin );
int i;
for ( i = 0; !!lists[i]; ++i ) {
handler = getHandlerForKey( lists[i], ch );
if ( !!handler ) {
break;
}
}
}
delwin( win );
touchwin( globals->boardWin );
wrefresh( globals->boardWin );
MenuList* ml[] = { g_rootMenuListShow, NULL };
drawMenuFromList( globals->menuWin, ml, 1, 0 );
wrefresh( globals->menuWin );
return handler != NULL && (*handler)(globals);
} /* handleRootKeyShow */
static XP_Bool
handleRootKeyHide( CursesAppGlobals* globals )
{
globals->doDraw = XP_TRUE;
return XP_TRUE;
}
#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; */
/* } */
/* } */
/* *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; */
/* getmaxyx( win, winMaxY, winMaxX ); */
/* XP_USE(winMaxY); */
/* 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]; */
/* fmtMenuItem( entry, buf, sizeof(buf) ); */
/* mvwaddstr( win, line+padding, col+padding, buf ); */
/* int width = strlen(buf) + 2; */
/* if ( width > maxColWidth ) { */
/* maxColWidth = width; */
/* } */
/* if ( ++line == nLines ) { */
/* line = 0; */
/* col += maxColWidth; */
/* maxColWidth = 0; */
/* } */
/* } */
/* } */
/* } /\* drawMenuFromList *\/ */
static void
writeToPipe( int pipe )
{
if ( 1 != write( pipe, "!", 1 ) ) {
XP_ASSERT(0);
}
}
static void
readFromPipe( GIOChannel* source )
{
int pipe = g_io_channel_unix_get_fd( source );
char ch;
#ifdef DEBUG
ssize_t nRead =
#endif
read( pipe, &ch, sizeof(ch) );
XP_ASSERT( nRead == sizeof(ch) && ch == '!' );
}
static void
SIGWINCH_handler( int signal )
{
assert( signal == SIGWINCH );
/* Write to pipe to force update */
writeToPipe( g_globals.winchpipe[1] );
} /* SIGWINCH_handler */
static void
SIGINTTERM_handler( int XP_UNUSED(signal) )
{
writeToPipe( g_globals.quitpipe[1] );
}
#ifdef USE_GLIBLOOP
static gboolean
handle_quitwrite( GIOChannel* source, GIOCondition XP_UNUSED(condition), gpointer data )
{
LOG_FUNC();
readFromPipe( source );
CursesAppGlobals* aGlobals = (CursesAppGlobals*)data;
handleQuit( aGlobals, 0 );
return TRUE;
}
static gboolean
handle_winchwrite( GIOChannel* source, GIOCondition XP_UNUSED_DBG(condition), gpointer data )
{
XP_LOGFF( "(condition=%x)", condition );
CursesAppGlobals* aGlobals = (CursesAppGlobals*)data;
/* Read from the pipe so it won't call again */
readFromPipe( source );
struct winsize ws;
ioctl( STDIN_FILENO, TIOCGWINSZ, &ws );
XP_LOGFF( "lines %d, columns %d", ws.ws_row, ws.ws_col );
aGlobals->winHeight = ws.ws_row;
aGlobals->winWidth = ws.ws_col;
resize_term( ws.ws_row, ws.ws_col );
cgl_resized( aGlobals->gameList, g_globals.winWidth,
g_globals.cag.params->cursesListWinHt );
if ( !!aGlobals->menuState ) {
cmenu_resized( aGlobals->menuState );
}
cb_dims dims;
figureDims( aGlobals, &dims );
cb_resized( aGlobals->cbState, &dims );
LOG_RETURN_VOID();
return TRUE;
}
#endif
#ifndef USE_GLIBLOOP
#ifdef XWFEATURE_RELAY
static int
figureTimeout( CursesAppGlobals* globals )
{
int result = INFINITE_TIMEOUT;
XWTimerReason ii;
XP_U32 now = util_getCurSeconds( globals->cGlobals.params->util );
now *= 1000;
for ( ii = 0; ii < NUM_TIMERS_PLUS_ONE; ++ii ) {
TimerInfo* tip = &globals->cGlobals.timerInfo[ii];
if ( !!tip->proc ) {
XP_U32 then = tip->when * 1000;
if ( now >= then ) {
result = 0;
break; /* if one's immediate, we're done */
} else {
then -= now;
if ( result == -1 || then < result ) {
result = then;
}
}
}
}
return result;
} /* figureTimeout */
#else
# define figureTimeout(g) INFINITE_TIMEOUT
#endif
#ifdef XWFEATURE_RELAY
static void
fireCursesTimer( CursesAppGlobals* globals )
{
XWTimerReason ii;
TimerInfo* smallestTip = NULL;
for ( ii = 0; ii < NUM_TIMERS_PLUS_ONE; ++ii ) {
TimerInfo* tip = &globals->cGlobals.timerInfo[ii];
if ( !!tip->proc ) {
if ( !smallestTip ) {
smallestTip = tip;
} else if ( tip->when < smallestTip->when ) {
smallestTip = tip;
}
}
}
if ( !!smallestTip ) {
XP_U32 now = util_getCurSeconds( globals->cGlobals.params->util ) ;
if ( now >= smallestTip->when ) {
if ( linuxFireTimer( &globals->cGlobals,
smallestTip - globals->cGlobals.timerInfo ) ){
board_draw( globals->cGlobals.game.board );
}
} else {
XP_LOGFF( "skipping timer: now (%ld) < when (%ld)",
now, smallestTip->when );
}
}
} /* fireCursesTimer */
#endif
#endif
/*
* Ok, so this doesn't block yet....
*/
#ifndef USE_GLIBLOOP
/* static XP_Bool */
/* blocking_gotEvent( CursesAppGlobals* globals, int* ch ) */
/* { */
/* XP_Bool result = XP_FALSE; */
/* int numEvents, ii; */
/* short fdIndex; */
/* XP_Bool redraw = XP_FALSE; */
/* int timeout = figureTimeout( globals ); */
/* numEvents = poll( globals->fdArray, globals->fdCount, timeout ); */
/* if ( timeout != INFINITE_TIMEOUT && numEvents == 0 ) { */
/* #ifdef XWFEATURE_RELAY */
/* fireCursesTimer( globals ); */
/* #endif */
/* } else if ( numEvents > 0 ) { */
/* /\* stdin first *\/ */
/* if ( (globals->fdArray[FD_STDIN].revents & POLLIN) != 0 ) { */
/* int evtCh = wgetch(globals->mainWin); */
/* XP_LOGF( "%s: got key: %x", __func__, evtCh ); */
/* *ch = evtCh; */
/* result = XP_TRUE; */
/* --numEvents; */
/* } */
/* if ( (globals->fdArray[FD_STDIN].revents & ~POLLIN ) ) { */
/* XP_LOGF( "some other events set on stdin" ); */
/* } */
/* if ( (globals->fdArray[FD_TIMEEVT].revents & POLLIN) != 0 ) { */
/* char ch; */
/* if ( 1 != read(globals->fdArray[FD_TIMEEVT].fd, &ch, 1 ) ) { */
/* XP_ASSERT(0); */
/* } */
/* } */
/* fdIndex = FD_FIRSTSOCKET; */
/* if ( numEvents > 0 ) { */
/* if ( (globals->fdArray[fdIndex].revents & ~POLLIN ) ) { */
/* XP_LOGF( "some other events set on socket %d", */
/* globals->fdArray[fdIndex].fd ); */
/* } */
/* if ( (globals->fdArray[fdIndex].revents & POLLIN) != 0 ) { */
/* --numEvents; */
/* if ( globals->fdArray[fdIndex].fd */
/* == globals->csInfo.server.serverSocket ) { */
/* /\* It's the listening socket: call platform's accept() */
/* wrapper *\/ */
/* (*globals->cGlobals.acceptor)( globals->fdArray[fdIndex].fd, */
/* globals ); */
/* } else { */
/* #ifndef XWFEATURE_STANDALONE_ONLY */
/* unsigned char buf[1024]; */
/* int nBytes; */
/* CommsAddrRec addrRec; */
/* CommsAddrRec* addrp = NULL; */
/* /\* It's a normal data socket *\/ */
/* switch ( globals->cGlobals.params->conType ) { */
/* #ifdef XWFEATURE_RELAY */
/* case COMMS_CONN_RELAY: */
/* nBytes = linux_relay_receive( &globals->cGlobals, buf, */
/* sizeof(buf) ); */
/* break; */
/* #endif */
/* #ifdef XWFEATURE_SMS */
/* case COMMS_CONN_SMS: */
/* addrp = &addrRec; */
/* nBytes = linux_sms_receive( &globals->cGlobals, */
/* globals->fdArray[fdIndex].fd, */
/* buf, sizeof(buf), addrp ); */
/* break; */
/* #endif */
/* #ifdef XWFEATURE_BLUETOOTH */
/* case COMMS_CONN_BT: */
/* nBytes = linux_bt_receive( globals->fdArray[fdIndex].fd, */
/* buf, sizeof(buf) ); */
/* break; */
/* #endif */
/* default: */
/* XP_ASSERT( 0 ); /\* fired *\/ */
/* } */
/* if ( nBytes != -1 ) { */
/* XWStreamCtxt* inboundS; */
/* redraw = XP_FALSE; */
/* inboundS = stream_from_msgbuf( &globals->cGlobals, */
/* buf, nBytes ); */
/* if ( !!inboundS ) { */
/* if ( comms_checkIncomingStream( */
/* globals->cGlobals.game.comms, */
/* inboundS, addrp ) ) { */
/* redraw = server_receiveMessage( */
/* globals->cGlobals.game.server, inboundS ); */
/* } */
/* stream_destroy( inboundS ); */
/* } */
/* /\* if there's something to draw resulting from the */
/* message, we need to give the main loop time to reflect */
/* that on the screen before giving the server another */
/* shot. So just call the idle proc. *\/ */
/* if ( redraw ) { */
/* curses_util_requestTime(globals->cGlobals.params->util); */
/* } */
/* } */
/* #else */
/* XP_ASSERT(0); /\* no socket activity in standalone game! *\/ */
/* #endif /\* #ifndef XWFEATURE_STANDALONE_ONLY *\/ */
/* } */
/* ++fdIndex; */
/* } */
/* } */
/* for ( ii = 0; ii < 5; ++ii ) { */
/* redraw = server_do( globals->cGlobals.game.server, NULL ) || redraw; */
/* } */
/* if ( redraw ) { */
/* /\* messages change a lot *\/ */
/* board_invalAll( globals->cGlobals.game.board ); */
/* board_draw( globals->cGlobals.game.board ); */
/* } */
/* saveGame( globals->cGlobals ); */
/* } */
/* return result; */
/* } /\* 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 *\/ */
typedef struct _MenuEntry {
MenuList* menuItem;
void* closure;
} MenuEntry;
/* 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
initClientSocket( CursesAppGlobals* globals, char* serverName )
{
struct hostent* hostinfo;
hostinfo = gethostbyname( serverName );
if ( !hostinfo ) {
userError( globals, "unable to get host info for %s\n", serverName );
} else {
char* hostName = inet_ntoa( *(struct in_addr*)hostinfo->h_addr );
XP_LOGFF( "gethostbyname returned %s", hostName );
globals->csInfo.client.serverAddr = inet_addr(hostName);
XP_LOGFF( "inet_addr returned %lu", globals->csInfo.client.serverAddr );
}
} /* initClientSocket */
#endif
/* <<<<<<< HEAD */
/* 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_notifyDupStatus( XW_UtilCtxt* XP_UNUSED(uc), */
/* XP_Bool amHost, */
/* const XP_UCHAR* msg ) */
/* { */
/* XP_LOGF( "%s(amHost=%d, msg=%s)", __func__, amHost, msg ); */
/* } */
/* 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 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 = "<unknown>"; */
/* 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_notifyDupStatus = curses_util_notifyDupStatus; */
/* 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 ); */
/* ======= */
/* >>>>>>> android_branch */
/* 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 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 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
onJoined( void* closure, const XP_UCHAR* connname, XWHostID hid )
{
LOG_FUNC();
CursesAppGlobals* globals = (CursesAppGlobals*)closure;
CommsCtxt* comms = globals->cGlobals.game.comms;
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 ); */
/* } */
#endif
void
inviteReceivedCurses( void* closure, const NetLaunchInfo* invite )
{
CursesAppGlobals* aGlobals = (CursesAppGlobals*)closure;
sqlite3_int64 rowids[1];
int nRowIDs = VSIZE(rowids);
gdb_getRowsForGameID( aGlobals->cag.params->pDb, invite->gameID, rowids, &nRowIDs );
bool doIt = 0 == nRowIDs;
if ( ! doIt && !!aGlobals->mainWin ) {
XP_LOGFF( "duplicate invite; not creating game" );
/* const gchar* question = "Duplicate invitation received. Accept anyway?"; */
/* const char* buttons[] = { "Yes", "No" }; */
/* doIt = 0 == cursesask( aGlobals->mainWin, question, VSIZE(buttons), buttons ); */
}
if ( doIt ) {
cb_dims dims;
figureDims( aGlobals, &dims );
cb_newFor( aGlobals->cbState, invite, &dims );
} else {
XP_LOGFF( "Not accepting duplicate invitation (nRowIDs(gameID=%X) was %d",
invite->gameID, nRowIDs );
}
}
#ifdef XWFEATURE_RELAY
static void
relayInviteReceivedCurses( void* closure, const NetLaunchInfo* invite )
{
CursesAppGlobals* aGlobals = (CursesAppGlobals*)closure;
inviteReceivedCurses( aGlobals, invite );
}
static void
cursesGotBuf( void* closure, const CommsAddrRec* addr,
const XP_U8* buf, XP_U16 len )
{
LOG_FUNC();
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 rowid;
XP_U16 gotSeed;
rowidFromToken( XP_NTOHL( clientToken ), &rowid, &gotSeed );
/* Figure out if the device is live, or we need to open the game */
cb_feedRow( aGlobals->cbState, rowid, gotSeed, buf, len, addr );
/* 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(); */
}
#endif
static void
smsInviteReceivedCurses( void* closure, const NetLaunchInfo* nli )
{
CursesAppGlobals* aGlobals = (CursesAppGlobals*)closure;
inviteReceivedCurses( aGlobals, nli );
}
static void
smsMsgReceivedCurses( void* closure, const CommsAddrRec* from, XP_U32 gameID,
const XP_U8* buf, XP_U16 len )
{
CursesAppGlobals* aGlobals = (CursesAppGlobals*)closure;
cb_feedGame( aGlobals->cbState, gameID, buf, len, from );
}
void
mqttMsgReceivedCurses( void* closure, const CommsAddrRec* from,
XP_U32 gameID, const XP_U8* buf, XP_U16 len )
{
CursesAppGlobals* aGlobals = (CursesAppGlobals*)closure;
cb_feedGame( aGlobals->cbState, gameID, buf, len, from );
}
void
gameGoneCurses( void* XP_UNUSED(closure), const CommsAddrRec* XP_UNUSED(from),
XP_U32 XP_UNUSED_DBG(gameID) )
{
XP_LOGFF( "(gameID=%d)", gameID );
}
#ifdef XWFEATURE_RELAY
static void
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();
/* gameGotBuf( &globals->cGlobals, XP_TRUE, buf, len, from ); */
XP_ASSERT( 0 );
LOG_RETURN_VOID();
}
static gint
curses_requestMsgs( gpointer data )
{
CursesAppGlobals* aGlobals = (CursesAppGlobals*)data;
XP_UCHAR devIDBuf[64] = {0};
gdb_fetch_safe( aGlobals->cag.params->pDb, KEY_RDEVID, NULL, devIDBuf,
sizeof(devIDBuf) );
if ( '\0' != devIDBuf[0] ) {
relaycon_requestMsgs( aGlobals->cag.params, devIDBuf );
} else {
XP_LOGFF( "not requesting messages as don't have relay id" );
}
return 0; /* don't run again */
}
static void
cursesNoticeRcvd( void* closure )
{
LOG_FUNC();
CursesAppGlobals* globals = (CursesAppGlobals*)closure;
#ifdef DEBUG
guint res =
#endif
ADD_ONETIME_IDLE( curses_requestMsgs, globals );
XP_ASSERT( res > 0 );
}
static gboolean
keepalive_timer( gpointer data )
{
LOG_FUNC();
curses_requestMsgs( data );
return TRUE;
}
static void
cursesDevIDReceived( void* closure, const XP_UCHAR* devID,
XP_U16 maxInterval )
{
CursesAppGlobals* aGlobals = (CursesAppGlobals*)closure;
sqlite3* pDb = aGlobals->cag.params->pDb;
if ( !!devID ) {
XP_LOGFF( "(devID='%s')", devID );
/* If we already have one, make sure it's the same! Else store. */
gchar buf[64];
XP_Bool have = gdb_fetch_safe( pDb, KEY_RDEVID, NULL, buf, sizeof(buf) )
&& 0 == strcmp( buf, devID );
if ( !have ) {
gdb_store( pDb, KEY_RDEVID, devID );
XP_LOGFF( "storing new devid: %s", devID );
cgl_draw( aGlobals->gameList );
}
(void)g_timeout_add_seconds( maxInterval, keepalive_timer, aGlobals );
} else {
XP_LOGFF( "bad relayid" );
gdb_remove( pDb, KEY_RDEVID );
DevIDType typ;
const XP_UCHAR* devID = linux_getDevID( aGlobals->cag.params, &typ );
relaycon_reg( aGlobals->cag.params, NULL, typ, devID );
}
}
static void
cursesErrorMsgRcvd( void* closure, const XP_UCHAR* msg )
{
CursesAppGlobals* globals = (CursesAppGlobals*)closure;
if ( !!globals->lastErr && 0 == strcmp( globals->lastErr, msg ) ) {
XP_LOGFF( "skipping error message from relay" );
} else {
g_free( globals->lastErr );
globals->lastErr = g_strdup( msg );
const char* buttons[] = { "Ok" };
(void)cursesask( globals->mainWin, msg, VSIZE(buttons), buttons );
}
}
#endif
/* static gboolean */
/* chatsTimerFired( gpointer data ) */
/* { */
/* CursesAppGlobals* globals = (CursesAppGlobals*)data; */
/* XWGame* game = &globals->cGlobals.game; */
/* GameStateInfo gsi; */
/* game_getState( game, &gsi ); */
/* 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 ); */
/* 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; */
/* } */
/* static XP_U16 */
/* feedBufferCurses( CommonGlobals* cGlobals, sqlite3_int64 rowid, */
/* const XP_U8* buf, XP_U16 len, const CommsAddrRec* from ) */
/* { */
/* gameGotBuf( cGlobals, XP_TRUE, buf, len, from ); */
/* /\* GtkGameGlobals* globals = findOpenGame( apg, rowid ); *\/ */
/* /\* if ( !!globals ) { *\/ */
/* /\* gameGotBuf( &globals->cGlobals, XP_TRUE, buf, len, from ); *\/ */
/* /\* seed = comms_getChannelSeed( globals->cGlobals.game.comms ); *\/ */
/* /\* } else { *\/ */
/* /\* GtkGameGlobals tmpGlobals; *\/ */
/* /\* if ( loadGameNoDraw( &tmpGlobals, apg->params, rowid ) ) { *\/ */
/* /\* gameGotBuf( &tmpGlobals.cGlobals, XP_FALSE, buf, len, from ); *\/ */
/* /\* seed = comms_getChannelSeed( tmpGlobals.cGlobals.game.comms ); *\/ */
/* /\* saveGame( &tmpGlobals.cGlobals ); *\/ */
/* /\* } *\/ */
/* /\* freeGlobals( &tmpGlobals ); *\/ */
/* /\* } *\/ */
/* /\* 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; *\/ */
/* /\* 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
onGameSaved( CursesAppGlobals* aGlobals, sqlite3_int64 rowid, bool isNew )
{
cgl_refreshOne( aGlobals->gameList, rowid, isNew );
}
static XP_U32
castGid( cJSON* obj )
{
XP_U32 gameID;
sscanf( obj->valuestring, "%X", &gameID );
return gameID;
}
static XP_U32
gidFromObject( const cJSON* obj )
{
cJSON* tmp = cJSON_GetObjectItem( obj, "gid" );
XP_ASSERT( !!tmp );
return castGid( tmp );
}
static void
makeObjIfNot( cJSON** objp )
{
if ( NULL == *objp ) {
*objp = cJSON_CreateObject();
}
}
static void
addStringToObject( cJSON** objp, const char* key, const char* value )
{
makeObjIfNot( objp );
cJSON_AddStringToObject( *objp, key, value );
}
static void
addGIDToObject( cJSON** objp, XP_U32 gid, const char* key )
{
char buf[16];
sprintf( buf, "%08X", gid );
addStringToObject( objp, key, buf );
}
static void
addObjectToObject( cJSON** objp, const char* key, cJSON* value )
{
makeObjIfNot( objp );
cJSON_AddItemToObject( *objp, key, value );
}
static void
addSuccessToObject( cJSON** objp, XP_Bool success )
{
makeObjIfNot( objp );
cJSON_AddBoolToObject( *objp, "success", success );
}
static XP_U32
makeGameFromArgs( CursesAppGlobals* aGlobals, cJSON* args )
{
LaunchParams* params = aGlobals->cag.params;
CurGameInfo gi = {0};
gi_copy( MPPARM(params->mpool) &gi, &params->pgi );
gi.boardSize = 15;
gi.traySize = 7;
cJSON* tmp = cJSON_GetObjectItem( args, "nPlayers" );
XP_ASSERT( !!tmp );
gi.nPlayers = tmp->valueint;
tmp = cJSON_GetObjectItem( args, "boardSize" );
if ( !!tmp ) {
gi.boardSize = tmp->valueint;
}
tmp = cJSON_GetObjectItem( args, "traySize" );
if ( !!tmp ) {
gi.traySize = tmp->valueint;
}
tmp = cJSON_GetObjectItem( args, "isSolo" );
XP_ASSERT( !!tmp );
XP_Bool isSolo = cJSON_IsTrue( tmp );
tmp = cJSON_GetObjectItem( args, "hostPosn" );
XP_ASSERT( !!tmp );
int hostPosn = tmp->valueint;
replaceStringIfDifferent( params->mpool, &gi.players[hostPosn].name,
params->localName );
for ( int ii = 0; ii < gi.nPlayers; ++ii ) {
LocalPlayer* lp = &gi.players[ii];
lp->isLocal = isSolo || ii == hostPosn;
if ( isSolo ) {
lp->robotIQ = ii == hostPosn ? 0 : 1;
}
}
gi.serverRole = isSolo ? SERVER_STANDALONE : SERVER_ISHOST;
tmp = cJSON_GetObjectItem( args, "dict" );
XP_ASSERT( tmp );
replaceStringIfDifferent( params->mpool, &gi.dictName, tmp->valuestring );
cb_dims dims;
figureDims( aGlobals, &dims );
XP_U32 newGameID;
bool success = cb_new( aGlobals->cbState, &dims, &gi, &newGameID );
XP_ASSERT( success );
gi_disposePlayerInfo( MPPARM(params->mpool) &gi );
return newGameID;
}
static XP_Bool
inviteFromArgs( CursesAppGlobals* aGlobals, cJSON* args )
{
XP_U32 gameID = gidFromObject( args );
cJSON* remotes = cJSON_GetObjectItem( args, "remotes" );
int nRemotes = cJSON_GetArraySize(remotes);
CommsAddrRec destAddrs[nRemotes];
XP_MEMSET( destAddrs, 0, sizeof(destAddrs) );
XP_U16 channels[nRemotes];
XP_MEMSET( channels, 0, sizeof(channels) );
for ( int ii = 0; ii < nRemotes; ++ii ) {
cJSON* item = cJSON_GetArrayItem( remotes, ii );
cJSON* tmp = cJSON_GetObjectItem( item, "channel" );
XP_ASSERT( !!tmp );
channels[ii] = tmp->valueint;
XP_LOGFF( "read channel: %X", channels[ii] );
cJSON* addr = cJSON_GetObjectItem( item, "addr" );
XP_ASSERT( !!addr );
tmp = cJSON_GetObjectItem( addr, "mqtt" );
if ( !!tmp ) {
XP_LOGFF( "parsing mqtt: %s", tmp->valuestring );
addr_addType( &destAddrs[ii], COMMS_CONN_MQTT );
XP_Bool success = strToMQTTCDevID( tmp->valuestring, &destAddrs[ii].u.mqtt.devID );
XP_ASSERT( success );
}
tmp = cJSON_GetObjectItem( addr, "sms" );
if ( !!tmp ) {
XP_LOGFF( "parsing sms: %s", tmp->valuestring );
addr_addType( &destAddrs[ii], COMMS_CONN_SMS );
XP_STRCAT( destAddrs[ii].u.sms.phone, tmp->valuestring );
destAddrs[ii].u.sms.port = 1;
}
}
cb_addInvites( aGlobals->cbState, gameID, nRemotes, channels, destAddrs );
LOG_RETURN_VOID();
return XP_TRUE;
}
static XP_Bool
moveifFromArgs( CursesAppGlobals* aGlobals, cJSON* args )
{
XP_U32 gameID = gidFromObject( args );
return cb_makeMoveIf( aGlobals->cbState, gameID );
}
static XP_Bool
chatFromArgs( CursesAppGlobals* aGlobals, cJSON* args )
{
XP_U32 gameID = gidFromObject( args );
cJSON* tmp = cJSON_GetObjectItem( args, "msg" );
const char* msg = tmp->valuestring;
return cb_sendChat( aGlobals->cbState, gameID, msg );
}
/* Return 'gid' of new game */
static XP_U32
rematchFromArgs( CursesAppGlobals* aGlobals, cJSON* args )
{
XP_U32 result = 0;
XP_U32 gameID = gidFromObject( args );
cJSON* tmp = cJSON_GetObjectItem( args, "rematchOrder" );
RematchOrder ro = roFromStr( tmp->valuestring );
XP_U32 newGameID = 0;
if ( cb_makeRematch( aGlobals->cbState, gameID, ro, &newGameID ) ) {
result = newGameID;
}
return result;
}
static XP_Bool
getGamesStateForArgs( CursesAppGlobals* aGlobals, cJSON* args,
cJSON** states, cJSON** orders )
{
LOG_FUNC();
LaunchParams* params = aGlobals->cag.params;
*states = cJSON_CreateArray();
cJSON* gids = cJSON_GetObjectItem( args, "gids" );
XP_Bool success = !!gids;
for ( int ii = 0 ; success && ii < cJSON_GetArraySize(gids) ; ++ii ) {
XP_U32 gameID = castGid( cJSON_GetArrayItem( gids, ii ) );
GameInfo gib;
if ( gdb_getGameInfoForGID( params->pDb, gameID, &gib ) ) {
cJSON* item = NULL;
addGIDToObject( &item, gameID, "gid" );
cJSON_AddBoolToObject( item, "gameOver", gib.gameOver );
cJSON_AddNumberToObject( item, "nPending", gib.nPending );
cJSON_AddNumberToObject( item, "nMoves", gib.nMoves );
cJSON_AddNumberToObject( item, "nTiles", gib.nTiles );
cJSON_AddItemToArray( *states, item );
}
}
XP_LOGFF( "done with states" ); /* got here */
if ( success && !!orders ) {
cJSON* gids = cJSON_GetObjectItem( args, "orders" );
if ( !gids ) {
*orders = NULL;
} else {
*orders = cJSON_CreateArray();
for ( int ii = 0 ; ii < cJSON_GetArraySize(gids) ; ++ii ) {
XP_U32 gameID = castGid( cJSON_GetArrayItem( gids, ii ) );
const CommonGlobals* cg = cb_getForGameID( aGlobals->cbState, gameID );
if ( !cg ) {
continue;
}
const XWGame* game = &cg->game;
if ( server_getGameIsConnected( game->server ) ) {
const CurGameInfo* gi = cg->gi;
LOGGI( gi, __func__ );
cJSON* order = NULL;
addGIDToObject( &order, gameID, "gid" );
cJSON* players = cJSON_CreateArray();
for ( int jj = 0; jj < gi->nPlayers; ++jj ) {
XP_LOGFF( "looking at player %d", jj );
const LocalPlayer* lp = &gi->players[jj];
XP_LOGFF( "adding player %d: %s", jj, lp->name );
cJSON* cName = cJSON_CreateString( lp->name );
cJSON_AddItemToArray( players, cName);
}
cJSON_AddItemToObject( order, "players", players );
cJSON_AddItemToArray( *orders, order );
}
}
}
}
LOG_RETURNF( "%s", boolToStr(success) );
return success;
}
/* Return for each gid and array of player names, in play order, and including
for each whether it's the host and if a robot. For now let's try by opening
each game (yeah! yuck!) to read the info directly. Later add to a the db
accessed by gamesdb.c
*/
static cJSON*
getPlayersForArgs( CursesAppGlobals* aGlobals, cJSON* args )
{
cJSON* result = cJSON_CreateArray();
cJSON* gids = cJSON_GetObjectItem( args, "gids" );
for ( int ii = 0 ; ii < cJSON_GetArraySize(gids) ; ++ii ) {
XP_U32 gameID = castGid( cJSON_GetArrayItem( gids, ii ) );
const CommonGlobals* cg = cb_getForGameID( aGlobals->cbState, gameID );
const CurGameInfo* gi = cg->gi;
LOGGI( gi, __func__ );
const XWGame* game = &cg->game;
cJSON* players = cJSON_CreateArray();
for ( int jj = 0; jj < gi->nPlayers; ++jj ) {
cJSON* playerObj = NULL;
const LocalPlayer* lp = &gi->players[jj];
XP_LOGFF( "adding player %d: %s", jj, lp->name );
addStringToObject( &playerObj, "name", lp->name );
XP_Bool isLocal = lp->isLocal;
cJSON_AddBoolToObject( playerObj, "isLocal", isLocal );
/* Roles: I don't think a guest in a 3- or 4-device game knows
which of the other players is host. Host is who it sends its
moves to, but is there an order there? */
XP_Bool isHost = game_getIsHost( game );
isHost = isHost && isLocal;
cJSON_AddBoolToObject( playerObj, "isHost", isHost );
cJSON_AddItemToArray( players, playerObj );
}
cJSON_AddItemToArray( result, players );
}
return result;
}
static gboolean
on_incoming_signal( GSocketService* XP_UNUSED(service),
GSocketConnection* connection,
GObject* XP_UNUSED(source_object), gpointer user_data )
{
XP_LOGFF( "called" );
CursesAppGlobals* aGlobals = (CursesAppGlobals*)user_data;
LaunchParams* params = aGlobals->cag.params;
XP_U32 startTime = dutil_getCurSeconds( params->dutil, NULL_XWE );
GInputStream* istream = g_io_stream_get_input_stream( G_IO_STREAM(connection) );
short len;
gssize nread = g_input_stream_read( istream, &len, sizeof(len), NULL, NULL );
XP_ASSERT( nread == sizeof(len) );
len = ntohs(len);
gchar buf[len+1];
nread = g_input_stream_read( istream, buf, len, NULL, NULL );
if ( 0 <= nread ) {
XP_ASSERT( nread == len );
buf[nread] = '\0';
XP_LOGFF( "Message: \"%s\"\n", buf );
cJSON* reply = cJSON_CreateArray();
cJSON* cmds = cJSON_Parse( buf );
XP_LOGFF( "got msg with array of len %d", cJSON_GetArraySize(cmds) );
for ( int ii = 0 ; ii < cJSON_GetArraySize(cmds) ; ++ii ) {
cJSON* item = cJSON_GetArrayItem( cmds, ii );
cJSON* cmd = cJSON_GetObjectItem( item, "cmd" );
cJSON* key = cJSON_GetObjectItem( item, "key" );
cJSON* args = cJSON_GetObjectItem( item, "args" );
const char* cmdStr = cmd->valuestring;
cJSON* response = NULL;
XP_Bool success = XP_TRUE;
if ( 0 == strcmp( cmdStr, "quit" ) ) {
cJSON* gids;
if ( getGamesStateForArgs( aGlobals, args, &gids, NULL ) ) {
addObjectToObject( &response, "states", gids );
}
handleQuit( aGlobals, 0 );
} else if ( 0 == strcmp( cmdStr, "getMQTTDevID" ) ) {
MQTTDevID devID;
dvc_getMQTTDevID( params->dutil, NULL_XWE, &devID );
char buf[64];
formatMQTTDevID( &devID, buf, sizeof(buf) );
cJSON* devid = cJSON_CreateString( buf );
addObjectToObject( &response, "mqtt", devid );
} else if ( 0 == strcmp( cmdStr, "makeGame" ) ) {
XP_U32 newGameID = makeGameFromArgs( aGlobals, args );
success = 0 != newGameID;
if ( success ) {
addGIDToObject( &response, newGameID, "newGid" );
}
} else if ( 0 == strcmp( cmdStr, "invite" ) ) {
success = inviteFromArgs( aGlobals, args );
} else if ( 0 == strcmp( cmdStr, "moveIf" ) ) {
success = moveifFromArgs( aGlobals, args );
} else if ( 0 == strcmp( cmdStr, "rematch" ) ) {
XP_U32 newGameID = rematchFromArgs( aGlobals, args );
success = 0 != newGameID;
if ( success ) {
addGIDToObject( &response, newGameID, "newGid" );
}
} else if ( 0 == strcmp( cmdStr, "getStates" ) ) {
cJSON* gids;
cJSON* orders;
success = getGamesStateForArgs( aGlobals, args, &gids, &orders );
if ( success ) {
addObjectToObject( &response, "states", gids );
addObjectToObject( &response, "orders", orders );
}
} else if ( 0 == strcmp( cmdStr, "getPlayers" ) ) {
cJSON* players = getPlayersForArgs( aGlobals, args );
addObjectToObject( &response, "players", players );
} else if ( 0 == strcmp( cmdStr, "sendChat" ) ) {
success = chatFromArgs( aGlobals, args );
} else {
success = XP_FALSE;
XP_ASSERT(0);
}
addSuccessToObject( &response, success );
cJSON* tmp = cJSON_CreateObject();
cJSON_AddStringToObject( tmp, "cmd", cmdStr );
cJSON_AddNumberToObject( tmp, "key", key->valueint );
cJSON_AddItemToObject( tmp, "response", response );
/*(void)*/cJSON_AddItemToArray( reply, tmp );
}
cJSON_Delete( cmds ); /* this apparently takes care of all children */
char* replyStr = cJSON_PrintUnformatted( reply );
short replyStrLen = strlen(replyStr);
XP_LOGFF( "len(%s): %d", replyStr, replyStrLen );
short replyStrNBOLen = htons(replyStrLen);
GOutputStream* ostream = g_io_stream_get_output_stream( G_IO_STREAM(connection) );
gsize nwritten;
gboolean wroteall = g_output_stream_write_all( ostream, &replyStrNBOLen, sizeof(replyStrNBOLen),
&nwritten, NULL, NULL );
XP_ASSERT( wroteall && nwritten == sizeof(replyStrNBOLen) );
wroteall = g_output_stream_write_all( ostream, replyStr, replyStrLen, &nwritten, NULL, NULL );
XP_ASSERT( wroteall && nwritten == replyStrLen );
GError* error = NULL;
g_output_stream_close( ostream, NULL, &error );
if ( !!error ) {
XP_LOGFF( "g_output_stream_close()=>%s", error->message );
g_error_free( error );
}
cJSON_Delete( reply );
free( replyStr );
}
XP_U32 consumed = dutil_getCurSeconds( params->dutil, NULL_XWE ) - startTime;
if ( 0 < consumed ) {
XP_LOGFF( "took %d seconds", consumed );
}
LOG_RETURN_VOID();
return FALSE;
}
static GSocketService*
addCmdListener( CursesAppGlobals* aGlobals )
{
LOG_FUNC();
LaunchParams* params = aGlobals->cag.params;
const XP_UCHAR* cmdsSocket = params->cmdsSocket;
GSocketService* service = NULL;
if ( !!cmdsSocket ) {
service = g_socket_service_new();
struct sockaddr_un addr = {0};
addr.sun_family = AF_UNIX;
strncpy( addr.sun_path, cmdsSocket, sizeof(addr.sun_path) - 1);
GSocketAddress* gsaddr
= g_socket_address_new_from_native (&addr, sizeof(addr) );
GError* error = NULL;
if ( g_socket_listener_add_address( (GSocketListener*)service, gsaddr, G_SOCKET_TYPE_STREAM,
G_SOCKET_PROTOCOL_DEFAULT, NULL, NULL, &error ) ) {
} else {
XP_LOGFF( "g_socket_listener_add_address() failed: %s", error->message );
}
g_object_unref( gsaddr );
g_signal_connect( service, "incoming", G_CALLBACK(on_incoming_signal), aGlobals );
}
LOG_RETURNF( "%p", service );
return service;
}
void
cursesmain( XP_Bool XP_UNUSED(isServer), LaunchParams* params )
{
memset( &g_globals, 0, sizeof(g_globals) );
g_globals.cag.params = params;
params->appGlobals = &g_globals;
params->cmdProcs.quit = invokeQuit;
initCurses( &g_globals );
if ( !params->closeStdin ) {
g_globals.menuState = cmenu_init( g_globals.mainWin );
cmenu_push( g_globals.menuState, &g_globals, g_sharedMenuList, NULL );
}
g_globals.loop = g_main_loop_new( NULL, FALSE );
g_globals.cbState = cb_init( &g_globals, params, g_globals.menuState,
onGameSaved );
g_globals.gameList = cgl_init( params, g_globals.winWidth, params->cursesListWinHt );
cgl_refresh( g_globals.gameList );
GSocketService* cmdService = addCmdListener( &g_globals );
// 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 = &params->pgi; */
/* setupUtil( &g_globals.cGlobals ); */
/* setupCursesUtilCallbacks( &g_globals, g_globals.cGlobals.util ); */
// initFromParams( &g_globals.cGlobals, params );
#ifdef XWFEATURE_RELAY
/* if ( addr_hasType( &params->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 );
ADD_SOCKET( &g_globals, g_globals.quitpipe[0], handle_quitwrite );
pipe( g_globals.winchpipe );
ADD_SOCKET( &g_globals, g_globals.winchpipe[0], handle_winchwrite );
struct sigaction act = { .sa_handler = SIGINTTERM_handler };
sigaction( SIGINT, &act, NULL );
sigaction( SIGTERM, &act, NULL );
struct sigaction act2 = { .sa_handler = SIGWINCH_handler };
sigaction( SIGWINCH, &act2, NULL );
#ifdef XWFEATURE_RELAY
if ( params->useUdp ) {
RelayConnProcs procs = {
.inviteReceived = relayInviteReceivedCurses,
.msgReceived = cursesGotBuf,
.msgForRow = cursesGotForRow,
.msgNoticeReceived = cursesNoticeRcvd,
.devIDReceived = cursesDevIDReceived,
.msgErrorMsg = cursesErrorMsgRcvd,
};
relaycon_init( params, &procs, &g_globals,
params->connInfo.relay.relayName,
params->connInfo.relay.defaultSendPort );
XP_Bool idIsNew = linux_setupDevidParams( params );
linux_doInitialReg( params, idIsNew );
}
#endif
mqttc_init( params );
#ifdef XWFEATURE_SMS
gchar* myPhone = NULL;
XP_U16 myPort = 0;
if ( parseSMSParams( params, &myPhone, &myPort ) ) {
SMSProcs smsProcs = {
.inviteReceived = smsInviteReceivedCurses,
.msgReceived = smsMsgReceivedCurses,
};
linux_sms_init( params, myPhone, myPort, &smsProcs, &g_globals );
}
#endif
if ( 0 == cgl_getNGames( g_globals.gameList ) ) {
if ( params->forceNewGame ) {
handleNewGame( &g_globals, 0 );
}
} else {
/* Always open a game (at random). Without that it won't attempt to
connect and stalls are likely in the test script case at least. If
that's annoying when running manually add a launch flag */
cgl_setSel( g_globals.gameList, -1 );
handleOpenGame( &g_globals, 0 );
}
g_main_loop_run( g_globals.loop );
g_object_unref( cmdService );
cb_closeAll( g_globals.cbState );
#ifdef XWFEATURE_BLUETOOTH
// 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 );
#endif
cgl_destroy( g_globals.gameList );
endwin();
dvc_store( params->dutil, NULL_XWE );
#ifdef XWFEATURE_RELAY
if ( params->useUdp ) {
relaycon_cleanup( params );
}
#endif
linux_sms_cleanup( params );
mqttc_cleanup( params );
} /* cursesmain */
#endif /* PLATFORM_NCURSES */