mirror of
git://xwords.git.sourceforge.net/gitroot/xwords/xwords
synced 2024-12-27 09:58:45 +01:00
2059 lines
67 KiB
C
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, ¶ms->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 = ¶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 );
|
|
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 */
|