diff --git a/xwords4/android/XWords4/jni/xwjni.c b/xwords4/android/XWords4/jni/xwjni.c index 535405ece..eb76f49ff 100644 --- a/xwords4/android/XWords4/jni/xwjni.c +++ b/xwords4/android/XWords4/jni/xwjni.c @@ -995,8 +995,21 @@ Java_org_eehouse_android_xw4_jni_XwJNI_comms_1start ( JNIEnv* env, jclass C, jint gamePtr ) { XWJNI_START(); - if ( !!state->game.comms ) { - comms_start( state->game.comms ); + CommsCtxt* comms = state->game.comms; + if ( !!comms ) { + comms_start( comms ); + } + XWJNI_END(); +} + +JNIEXPORT void JNICALL +Java_org_eehouse_android_xw4_jni_XwJNI_comms_1stop +( JNIEnv* env, jclass C, jint gamePtr ) +{ + XWJNI_START(); + CommsCtxt* comms = state->game.comms; + if ( !!comms ) { + comms_stop( comms ); } XWJNI_END(); } diff --git a/xwords4/android/XWords4/res/values/common_rsrc.xml b/xwords4/android/XWords4/res/values/common_rsrc.xml index 1d1adc7e4..945cca928 100644 --- a/xwords4/android/XWords4/res/values/common_rsrc.xml +++ b/xwords4/android/XWords4/res/values/common_rsrc.xml @@ -38,6 +38,8 @@ key_sms_port key_dict_host3 key_logging_on + key_udp_relay + key_drop_gcm key_show_sms key_init_hintsallowed key_init_nethintsallowed @@ -127,6 +129,9 @@ %1$s/%2$s Write DB to SD card Load DB from SD card + New/Experimental relay connection + Ignore GCM notices + (for testing relay proxy) diff --git a/xwords4/android/XWords4/res/xml/xwprefs.xml b/xwords4/android/XWords4/res/xml/xwprefs.xml index 2cb350166..b7eed6953 100644 --- a/xwords4/android/XWords4/res/xml/xwprefs.xml +++ b/xwords4/android/XWords4/res/xml/xwprefs.xml @@ -295,6 +295,15 @@ android:summary="@string/git_rev" android:enabled="false" /> + + (); m_bytesIn = ByteBuffer.allocate( 2048 ); @@ -377,13 +379,17 @@ public class CommsTransport implements TransportProcs, switch ( addr.conType ) { case COMMS_CONN_RELAY: - if ( NetStateCache.netAvail( m_context ) ) { - putOut( buf ); // add to queue - if ( null == m_thread ) { - m_thread = new CommsThread(); - m_thread.start(); + if ( XWPrefs.getUDPEnabled( m_context ) ) { + nSent = RelayService.sendPacket( m_context, m_rowid, buf ); + } else { + if ( NetStateCache.netAvail( m_context ) ) { + putOut( buf ); // add to queue + if ( null == m_thread ) { + m_thread = new CommsThread(); + m_thread.start(); + } + nSent = buf.length; } - nSent = buf.length; } break; case COMMS_CONN_SMS: diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DlgDelegate.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DlgDelegate.java index 01834e8dd..c46331b93 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DlgDelegate.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DlgDelegate.java @@ -265,6 +265,11 @@ public class DlgDelegate { (String)args[0] ); asToast = false; break; + case RELAY_ALERT: + msg = (String)args[0]; + asToast = false; + break; + default: DbgUtils.logf( "eventOccurred: unhandled event %s", event.toString() ); } @@ -452,9 +457,17 @@ public class DlgDelegate { private void addState( DlgState state ) { + // I'm getting serialization failures on devices pointing at + // DlgState but the code below says the object's fine (as it + // should be.) Just to have a record.... + // + // Bundle bundle = new Bundle(); + // DbgUtils.logf( "addState: testing serializable" ); + // bundle.putSerializable( "foo", state ); + // state = (DlgState)bundle.getSerializable( "foo" ); + // DbgUtils.logf( "addState: serializable is ok" ); + m_dlgStates.put( state.m_id, state ); - // DbgUtils.logf( "addState: there are now %d active dialogs", - // m_dlgStates.size() ); } } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java index c6038450a..7eb488c08 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java @@ -58,40 +58,44 @@ public class GCMIntentService extends GCMBaseIntentService { protected void onMessage( Context context, Intent intent ) { String value; + boolean ignoreIt = XWPrefs.getGCMIgnored( this ); + if ( ignoreIt ) { + DbgUtils.logf( "received GCM but ignoring it" ); + } else { + value = intent.getStringExtra( "checkUpdates" ); + if ( null != value && Boolean.parseBoolean( value ) ) { + UpdateCheckReceiver.checkVersions( context, true ); + } - value = intent.getStringExtra( "getMoves" ); - if ( null != value && Boolean.parseBoolean( value ) ) { - RelayReceiver.RestartTimer( context, true ); - } + value = intent.getStringExtra( "getMoves" ); + if ( null != value && Boolean.parseBoolean( value ) ) { + RelayReceiver.RestartTimer( context, true ); + } - value = intent.getStringExtra( "msgs64" ); - if ( null != value ) { - String connname = intent.getStringExtra( "connname" ); - if ( null != connname ) { - try { - JSONArray msgs64 = new JSONArray( value ); - String[] strs64 = new String[msgs64.length()]; - for ( int ii = 0; ii < strs64.length; ++ii ) { - strs64[ii] = msgs64.optString(ii); + value = intent.getStringExtra( "msgs64" ); + if ( null != value ) { + String connname = intent.getStringExtra( "connname" ); + if ( null != connname ) { + try { + JSONArray msgs64 = new JSONArray( value ); + String[] strs64 = new String[msgs64.length()]; + for ( int ii = 0; ii < strs64.length; ++ii ) { + strs64[ii] = msgs64.optString(ii); + } + RelayService.processMsgs( context, connname, strs64 ); + } catch (org.json.JSONException jse ) { + DbgUtils.loge( jse ); } - RelayService.processMsgs( context, connname, strs64 ); - } catch (org.json.JSONException jse ) { - DbgUtils.loge( jse ); } } - } - value = intent.getStringExtra( "checkUpdates" ); - if ( null != value && Boolean.parseBoolean( value ) ) { - UpdateCheckReceiver.checkVersions( context, true ); - } - - value = intent.getStringExtra( "msg" ); - if ( null != value ) { - String title = intent.getStringExtra( "title" ); - if ( null != title ) { - int code = value.hashCode() ^ title.hashCode(); - Utils.postNotification( context, null, title, value, code ); + value = intent.getStringExtra( "msg" ); + if ( null != value ) { + String title = intent.getStringExtra( "title" ); + if ( null != title ) { + int code = value.hashCode() ^ title.hashCode(); + Utils.postNotification( context, null, title, value, code ); + } } } } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java index a082fe1ac..36b274f85 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GamesList.java @@ -74,8 +74,9 @@ public class GamesList extends XWExpandableListActivity private static final String SAVE_DICTNAMES = "SAVE_DICTNAMES"; private static final String RELAYIDS_EXTRA = "relayids"; + private static final String ROWID_EXTRA = "rowid"; private static final String GAMEID_EXTRA = "gameid"; - private static final String REMATCH_ROWID_EXTRA = "rowid"; + private static final String REMATCH_ROWID_EXTRA = "rowid_rm"; private static final int NEW_NET_GAME_ACTION = 1; private static final int RESET_GAME_ACTION = 2; @@ -388,6 +389,7 @@ public class GamesList extends XWExpandableListActivity m_gameLaunched = false; Assert.assertNotNull( intent ); invalRelayIDs( intent.getStringArrayExtra( RELAYIDS_EXTRA ) ); + invalRowID( intent.getLongExtra( ROWID_EXTRA, -1 ) ); startFirstHasDict( intent ); startNewNetGame( intent ); startHasGameID( intent ); @@ -919,10 +921,18 @@ public class GamesList extends XWExpandableListActivity } } + private void invalRowID( long rowid ) + { + if ( -1 != rowid ) { + m_adapter.inval( rowid ); + } + } + // Launch the first of these for which there's a dictionary // present. - private void startFirstHasDict( String[] relayIDs ) + private boolean startFirstHasDict( String[] relayIDs ) { + boolean launched = false; if ( null != relayIDs ) { outer: for ( String relayID : relayIDs ) { @@ -931,19 +941,33 @@ public class GamesList extends XWExpandableListActivity for ( long rowid : rowids ) { if ( GameUtils.gameDictsHere( this, rowid ) ) { launchGame( rowid ); + launched = true; break outer; } } } } } + return launched; + } + + private void startFirstHasDict( long rowid ) + { + if ( -1 != rowid ) { + if ( GameUtils.gameDictsHere( this, rowid ) ) { + launchGame( rowid ); + } + } } private void startFirstHasDict( Intent intent ) { if ( null != intent ) { String[] relayIDs = intent.getStringArrayExtra( RELAYIDS_EXTRA ); - startFirstHasDict( relayIDs ); + if ( !startFirstHasDict( relayIDs ) ) { + long rowid = intent.getLongExtra( ROWID_EXTRA, -1 ); + startFirstHasDict( rowid ); + } } } @@ -1099,11 +1123,10 @@ public class GamesList extends XWExpandableListActivity return intent; } - public static Intent makeRelayIdsIntent( Context context, - String[] relayIDs ) + public static Intent makeRowidIntent( Context context, long rowid ) { Intent intent = makeSelfIntent( context ); - intent.putExtra( RELAYIDS_EXTRA, relayIDs ); + intent.putExtra( ROWID_EXTRA, rowid ); return intent; } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/MultiMsgSink.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/MultiMsgSink.java index 9041220bc..aa880a0a4 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/MultiMsgSink.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/MultiMsgSink.java @@ -36,7 +36,7 @@ public class MultiMsgSink implements TransportProcs { public int transportSend( byte[] buf, final CommsAddrRec addr, int gameID ) { - Assert.fail(); + Assert.fail(); // implement if this is getting called!!! return -1; } @@ -53,9 +53,9 @@ public class MultiMsgSink implements TransportProcs { { } - public boolean relayNoConnProc( byte[] buf, String relayID ) + public boolean relayNoConnProc( byte[] buf, String relayID ) { - Assert.fail(); + Assert.fail(); // implement if this is getting called!!! return false; } } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/MultiService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/MultiService.java index 5d2a776eb..17c123d73 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/MultiService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/MultiService.java @@ -62,6 +62,8 @@ public class MultiService { , SMS_SEND_OK , SMS_SEND_FAILED , SMS_SEND_FAILED_NORADIO + + , RELAY_ALERT }; public interface MultiEventListener { diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/PrefsActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/PrefsActivity.java index 2220b353e..60aed74dd 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/PrefsActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/PrefsActivity.java @@ -41,6 +41,7 @@ public class PrefsActivity extends PreferenceActivity private String m_keyLogging; private String m_smsToasting; private String m_smsEnable; + private String m_udpEnabled; private String m_downloadPath; @Override @@ -122,6 +123,7 @@ public class PrefsActivity extends PreferenceActivity m_keyLogging = getString( R.string.key_logging_on ); m_smsToasting = getString( R.string.key_show_sms ); m_smsEnable = getString( R.string.key_enable_sms ); + m_udpEnabled = getString( R.string.key_udp_relay ); m_downloadPath = getString( R.string.key_download_path ); Button button = (Button)findViewById( R.id.revert_colors ); @@ -161,6 +163,8 @@ public class PrefsActivity extends PreferenceActivity DbgUtils.logEnable( sp.getBoolean( key, false ) ); } else if ( key.equals( m_smsToasting ) ) { SMSService.smsToastEnable( sp.getBoolean( key, false ) ); + } else if ( key.equals( m_udpEnabled ) ) { + RelayService.udpChanged( this ); } else if ( key.equals( m_smsEnable ) ) { if ( sp.getBoolean( key, true ) ) { SMSService.checkForInvites( this ); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java index 8a97dc766..db2f6f268 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java @@ -25,24 +25,113 @@ import android.content.Context; import android.content.Intent; import android.os.AsyncTask; import android.os.IBinder; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; import java.io.DataOutputStream; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; import java.net.Socket; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; +import java.util.concurrent.LinkedBlockingQueue; +import junit.framework.Assert; + +import org.eehouse.android.xw4.MultiService.MultiEvent; +import org.eehouse.android.xw4.jni.CommsAddrRec; import org.eehouse.android.xw4.jni.GameSummary; +import org.eehouse.android.xw4.jni.UtilCtxt; import org.eehouse.android.xw4.jni.XwJNI; -public class RelayService extends Service { +public class RelayService extends XWService { private static final int MAX_SEND = 1024; private static final int MAX_BUF = MAX_SEND - 2; private static final String CMD_STR = "CMD"; + private static final int PROCESS_MSGS = 1; + private static final int UDP_CHANGED = 2; + private static final int SEND = 3; + private static final int RECEIVE = 4; + private static final String MSGS = "MSGS"; private static final String RELAY_ID = "RELAY_ID"; + private static final String ROWID = "ROWID"; + private static final String BINBUFFER = "BINBUFFER"; + + private static HashSet s_packetsSent = new HashSet(); + private static int s_nextPacketID = 1; + + private Thread m_fetchThread = null; + private Thread m_UDPReadThread = null; + private Thread m_UDPWriteThread = null; + private DatagramSocket m_UDPSocket; + private LinkedBlockingQueue m_queue = null; + + // These must match the enum XWRelayReg in xwrelay.h + private static final int XWPDEV_PROTO_VERSION = 0; + // private static final int XWPDEV_NONE = 0; + + private enum XWRelayReg { + XWPDEV_NONE + ,XWPDEV_ALERT + ,XWPDEV_REG + ,XWPDEV_REGRSP + ,XWPDEV_PING + ,XWPDEV_HAVEMSGS + ,XWPDEV_RQSTMSGS + ,XWPDEV_MSG + ,XWPDEV_MSGNOCONN + ,XWPDEV_MSGRSP + ,XWPDEV_BADREG + ,XWPDEV_ACK + }; + + // private static final int XWPDEV_ALERT = 1; + // private static final int XWPDEV_REG = 2; + // private static final int XWPDEV_REGRSP = 3; + // private static final int XWPDEV_PING = 4; + // private static final int XWPDEV_HAVEMSGS = 5; + // private static final int XWPDEV_RQSTMSGS = 6; + // private static final int XWPDEV_MSG = 7; + // private static final int XWPDEV_MSGNOCONN = 8; + // private static final int XWPDEV_MSGRSP = 9; + // private static final int XWPDEV_BADREG = 10; + + public static void startService( Context context ) + { + Intent intent = getIntentTo( context, UDP_CHANGED ); + context.startService( intent ); + } + + public static int sendPacket( Context context, long rowid, byte[] buf ) + { + Intent intent = getIntentTo( context, SEND ) + .putExtra( ROWID, rowid ) + .putExtra( BINBUFFER, buf ); + context.startService( intent ); + return buf.length; + } + + // Exists to get incoming data onto the main thread + private static void postData( Context context, long rowid, byte[] msg ) + { + DbgUtils.logf( "RelayService::postData: packet of length %d for token %d", + msg.length, rowid ); + Intent intent = getIntentTo( context, RECEIVE ) + .putExtra( ROWID, rowid ) + .putExtra( BINBUFFER, msg ); + context.startService( intent ); + } + + public static void udpChanged( Context context ) + { + startService( context ); + } public static void processMsgs( Context context, String relayId, String[] msgs64 ) @@ -64,45 +153,65 @@ public class RelayService extends Service { public void onCreate() { super.onCreate(); - - Thread thread = new Thread( null, new Runnable() { - public void run() { - fetchAndProcess(); - RelayService.this.stopSelf(); - } - }, getClass().getName() ); - thread.start(); - } - - @Override - public IBinder onBind( Intent intent ) - { - return null; + startFetchThreadIf(); } @Override public int onStartCommand( Intent intent, int flags, int startId ) { - int cmd = intent.getIntExtra( CMD_STR, -1 ); - switch( cmd ) { - case PROCESS_MSGS: - String[] relayIDs = new String[1]; - relayIDs[0] = intent.getStringExtra( RELAY_ID ); - long[] rowIDs = DBUtils.getRowIDsFor( this, relayIDs[0] ); - if ( 0 < rowIDs.length ) { - String[] msgs64 = intent.getStringArrayExtra( MSGS ); - int count = msgs64.length; + DbgUtils.logf( "RelayService::onStartCommand" ); + int result; + if ( null != intent ) { + int cmd = intent.getIntExtra( CMD_STR, -1 ); + switch( cmd ) { + case -1: + break; + case PROCESS_MSGS: + String[] relayIDs = new String[1]; + relayIDs[0] = intent.getStringExtra( RELAY_ID ); + long[] rowIDs = DBUtils.getRowIDsFor( this, relayIDs[0] ); + if ( 0 < rowIDs.length ) { + String[] msgs64 = intent.getStringArrayExtra( MSGS ); + int count = msgs64.length; - byte[][][] msgs = new byte[1][count][]; - for ( int ii = 0; ii < count; ++ii ) { - msgs[0][ii] = XwJNI.base64Decode( msgs64[ii] ); + byte[][][] msgs = new byte[1][count][]; + for ( int ii = 0; ii < count; ++ii ) { + msgs[0][ii] = XwJNI.base64Decode( msgs64[ii] ); + } + process( msgs, rowIDs, relayIDs ); } - process( msgs, rowIDs, relayIDs ); + break; + case UDP_CHANGED: + DbgUtils.logf( "RelayService::onStartCommand::UDP_CHANGED" ); + if ( XWPrefs.getUDPEnabled( this ) ) { + stopFetchThreadIf(); + startUDPThreadsIfNot(); + registerWithRelay(); + } else { + stopUDPThreadsIf(); + startFetchThreadIf(); + } + break; + case SEND: + case RECEIVE: + startUDPThreadsIfNot(); + long rowid = intent.getLongExtra( ROWID, -1 ); + byte[] msg = intent.getByteArrayExtra( BINBUFFER ); + if ( SEND == cmd ) { + sendMessage( rowid, msg ); + } else { + feedMessage( rowid, msg ); + } + break; + default: + Assert.fail(); } - break; + + result = Service.START_STICKY; + } else { + result = Service.START_STICKY_COMPATIBILITY; } - stopSelf( startId ); - return Service.START_NOT_STICKY; + return result; } private void setupNotification( String[] relayIDs ) @@ -111,18 +220,380 @@ public class RelayService extends Service { long[] rowids = DBUtils.getRowIDsFor( this, relayID ); if ( null != rowids ) { for ( long rowid : rowids ) { - Intent intent = - GamesList.makeRelayIdsIntent( this, - new String[] {relayID} ); - String msg = Utils.format( this, R.string.notify_bodyf, - GameUtils.getName( this, rowid ) ); - Utils.postNotification( this, intent, R.string.notify_title, - msg, (int)rowid ); + setupNotification( rowid ); } } } } + + private void setupNotification( long rowid ) + { + Intent intent = GamesList.makeRowidIntent( this, rowid ); + String msg = Utils.format( this, R.string.notify_bodyf, + GameUtils.getName( this, rowid ) ); + Utils.postNotification( this, intent, R.string.notify_title, + msg, (int)rowid ); + } + private void startFetchThreadIf() + { + DbgUtils.logf( "startFetchThreadIf()" ); + if ( !XWPrefs.getUDPEnabled( this ) && null == m_fetchThread ) { + m_fetchThread = new Thread( null, new Runnable() { + public void run() { + fetchAndProcess(); + m_fetchThread = null; + RelayService.this.stopSelf(); + } + }, getClass().getName() ); + m_fetchThread.start(); + } + } + + private void stopFetchThreadIf() + { + if ( null != m_fetchThread ) { + DbgUtils.logf( "2: m_fetchThread NOT NULL; WHAT TO DO???" ); + } + } + + private void startUDPThreadsIfNot() + { + if ( XWPrefs.getUDPEnabled( this ) ) { + if ( null == m_UDPSocket ) { + int port = XWPrefs.getDefaultRelayPort( RelayService.this ); + String host = XWPrefs.getDefaultRelayHost( RelayService.this ); + try { + m_UDPSocket = new DatagramSocket(); + InetAddress addr = InetAddress.getByName( host ); + m_UDPSocket.connect( addr, port ); // remember this address + } catch( java.net.SocketException se ) { + DbgUtils.loge( se ); + Assert.fail(); + } catch( java.net.UnknownHostException uhe ) { + DbgUtils.loge( uhe ); + } + } else { + Assert.assertTrue( m_UDPSocket.isConnected() ); + DbgUtils.logf( "m_UDPSocket not null" ); + } + + if ( null == m_UDPReadThread ) { + m_UDPReadThread = new Thread( null, new Runnable() { + public void run() { + DbgUtils.logf( "read thread running" ); + byte[] buf = new byte[1024]; + for ( ; ; ) { + DatagramPacket packet = + new DatagramPacket( buf, buf.length ); + try { + DbgUtils.logf( "UPD read thread blocking on receive" ); + m_UDPSocket.receive( packet ); + DbgUtils.logf( "UPD read thread: receive returned" ); + } catch( java.io.IOException ioe ) { + DbgUtils.loge( ioe ); + break; // ??? + } + DbgUtils.logf( "received %d bytes", packet.getLength() ); + gotPacket( packet ); + } + DbgUtils.logf( "read thread exiting" ); + } + }, getClass().getName() ); + m_UDPReadThread.start(); + } else { + DbgUtils.logf( "m_UDPReadThread not null and assumed to be running" ); + } + + if ( null == m_UDPWriteThread ) { + m_queue = new LinkedBlockingQueue(); + m_UDPWriteThread = new Thread( null, new Runnable() { + public void run() { + DbgUtils.logf( "write thread running" ); + for ( ; ; ) { + DatagramPacket outPacket; + try { + outPacket = m_queue.take(); + } catch ( InterruptedException ie ) { + DbgUtils.logf( "RelayService; write thread killed" ); + break; + } + if ( null == outPacket || 0 == outPacket.getLength() ) { + DbgUtils.logf( "stopping write thread" ); + break; + } + DbgUtils.logf( "Sending udp packet of length %d", + outPacket.getLength() ); + try { + m_UDPSocket.send( outPacket ); + } catch ( java.io.IOException ioe ) { + DbgUtils.loge( ioe ); + } + } + DbgUtils.logf( "write thread exiting" ); + } + }, getClass().getName() ); + m_UDPWriteThread.start(); + } else { + DbgUtils.logf( "m_UDPWriteThread not null and assumed to be running" ); + } + } + } + + private void stopUDPThreadsIf() + { + DbgUtils.logf( "stopUDPThreadsIf" ); + if ( null != m_queue && null != m_UDPWriteThread ) { + // can't add null + m_queue.add( new DatagramPacket( new byte[0], 0 ) ); + try { + DbgUtils.logf( "joining m_UDPWriteThread" ); + m_UDPWriteThread.join(); + DbgUtils.logf( "SUCCESSFULLY joined m_UDPWriteThread" ); + } catch( java.lang.InterruptedException ie ) { + DbgUtils.loge( ie ); + } + m_UDPWriteThread = null; + m_queue = null; + } + if ( null != m_UDPSocket && null != m_UDPReadThread ) { + m_UDPSocket.close(); + DbgUtils.logf( "waiting for read thread to exit" ); + try { + m_UDPReadThread.join(); + } catch( java.lang.InterruptedException ie ) { + DbgUtils.loge( ie ); + } + DbgUtils.logf( "read thread exited" ); + m_UDPReadThread = null; + m_UDPSocket = null; + } + DbgUtils.logf( "stopUDPThreadsIf DONE" ); + } + + // Running on reader thread + private void gotPacket( DatagramPacket packet ) + { + int packetLen = packet.getLength(); + byte[] data = new byte[packetLen]; + System.arraycopy( packet.getData(), 0, data, 0, packetLen ); + DbgUtils.logf( "RelayService::gotPacket: %d bytes of data", packetLen ); + ByteArrayInputStream bis = new ByteArrayInputStream( data ); + DataInputStream dis = new DataInputStream( bis ); + try { + PacketHeader header = readHeader( dis ); + if ( null != header ) { + sendAckIf( header ); + switch ( header.m_cmd ) { + case XWPDEV_ALERT: + String str = getStringWithLength( dis ); + sendResult( MultiEvent.RELAY_ALERT, str ); + break; + case XWPDEV_BADREG: + str = getStringWithLength( dis ); + DbgUtils.logf( "bad relayID \"%s\" reported", str ); + XWPrefs.clearRelayDevID( this ); + registerWithRelay(); + break; + case XWPDEV_REGRSP: + DbgUtils.logf( "got XWPDEV_REGRSP" ); + str = getStringWithLength( dis ); + DbgUtils.logf( "got relayid %s", str ); + XWPrefs.setRelayDevID( this, str ); + break; + case XWPDEV_HAVEMSGS: + requestMessages(); + break; + case XWPDEV_MSG: + DbgUtils.logf( "got XWPDEV_MSG" ); + int token = dis.readInt(); + byte[] msg = new byte[dis.available()]; + Assert.assertTrue( packet.getLength() >= msg.length ); + Assert.assertTrue( packetLen >= msg.length ); + dis.read( msg ); + postData( RelayService.this, token, msg ); + break; + case XWPDEV_ACK: + noteAck( dis.readInt() ); + break; + default: + DbgUtils.logf( "RelayService: Unhandled cmd: %d", + header.m_cmd ); + break; + } + } + } catch ( java.io.IOException ioe ) { + DbgUtils.loge( ioe ); + } + } // gotPacket + + private void registerWithRelay() + { + DbgUtils.logf( "registerWithRelay" ); + byte[] typ = new byte[1]; + String devid = getDevID(typ); + + ByteArrayOutputStream bas = new ByteArrayOutputStream(); + try { + DataOutputStream out = addProtoAndCmd( bas, XWRelayReg.XWPDEV_REG ); + out.writeByte( typ[0] ); + out.writeShort( devid.length() ); + out.writeBytes( devid ); + postPacket( bas ); + } catch ( java.io.IOException ioe ) { + DbgUtils.loge( ioe ); + } + } + + private void requestMessages() + { + DbgUtils.logf( "requestMessages" ); + ByteArrayOutputStream bas = new ByteArrayOutputStream(); + try { + DataOutputStream out = + addProtoAndCmd( bas, XWRelayReg.XWPDEV_RQSTMSGS ); + String devid = getDevID( null ); + out.writeShort( devid.length() ); + out.writeBytes( devid ); + postPacket( bas ); + } catch ( java.io.IOException ioe ) { + DbgUtils.loge( ioe ); + } + } + + private void sendMessage( long rowid, byte[] msg ) + { + ByteArrayOutputStream bas = new ByteArrayOutputStream(); + try { + DataOutputStream out = addProtoAndCmd( bas, XWRelayReg.XWPDEV_MSG ); + Assert.assertTrue( rowid < Integer.MAX_VALUE ); + out.writeInt( (int)rowid ); + out.write( msg, 0, msg.length ); + postPacket( bas ); + } catch ( java.io.IOException ioe ) { + DbgUtils.loge( ioe ); + } + } + + private void sendNoConnMessage( long rowid, String relayID, byte[] msg ) + { + ByteArrayOutputStream bas = new ByteArrayOutputStream(); + try { + DataOutputStream out = + addProtoAndCmd( bas, XWRelayReg.XWPDEV_MSGNOCONN ); + Assert.assertTrue( rowid < Integer.MAX_VALUE ); + out.writeInt( (int)rowid ); + out.writeBytes( relayID ); + out.write( '\n' ); + out.write( msg, 0, msg.length ); + postPacket( bas ); + } catch ( java.io.IOException ioe ) { + DbgUtils.loge( ioe ); + } + } + + private void sendAckIf( PacketHeader header ) + { + DbgUtils.logf( "sendAckIf" ); + if ( XWRelayReg.XWPDEV_ACK != header.m_cmd ) { + ByteArrayOutputStream bas = new ByteArrayOutputStream(); + try { + DataOutputStream out = + addProtoAndCmd( bas, XWRelayReg.XWPDEV_ACK ); + out.writeInt( header.m_packetID ); + postPacket( bas ); + } catch ( java.io.IOException ioe ) { + DbgUtils.loge( ioe ); + } + } + } + + private PacketHeader readHeader( DataInputStream dis ) + throws java.io.IOException + { + PacketHeader result = null; + byte proto = dis.readByte(); + if ( XWPDEV_PROTO_VERSION == proto ) { + int packetID = dis.readInt(); + DbgUtils.logf( "readHeader: got packetID %d", packetID ); + byte ordinal = dis.readByte(); + XWRelayReg cmd = XWRelayReg.values()[ordinal]; + result = new PacketHeader( cmd, packetID ); + } else { + DbgUtils.logf( "bad proto: %d", proto ); + } + DbgUtils.logf( "readHeader => %H", result ); + return result; + } + + private String getStringWithLength( DataInputStream dis ) + throws java.io.IOException + { + short len = dis.readShort(); + byte[] tmp = new byte[len]; + dis.read( tmp ); + return new String( tmp ); + } + + private DataOutputStream addProtoAndCmd( ByteArrayOutputStream bas, + XWRelayReg cmd ) + throws java.io.IOException + { + DataOutputStream out = new DataOutputStream( bas ); + out.writeByte( XWPDEV_PROTO_VERSION ); + out.writeInt( nextPacketID( cmd ) ); // packetID + out.writeByte( cmd.ordinal() ); + return out; + } + + private void postPacket( ByteArrayOutputStream bas ) + { + byte[] data = bas.toByteArray(); + m_queue.add( new DatagramPacket( data, data.length ) ); + } + + private String getDevID( byte[] typp ) + { + byte typ; + String devid = XWPrefs.getRelayDevID( this ); + if ( null != devid && 0 < devid.length() ) { + typ = UtilCtxt.ID_TYPE_RELAY; + } else { + devid = XWPrefs.getGCMDevID( this ); + if ( null != devid && 0 < devid.length() ) { + typ = UtilCtxt.ID_TYPE_ANDROID_GCM; + } else { + devid = ""; + typ = UtilCtxt.ID_TYPE_ANON; + } + } + if ( null != typp ) { + typp[0] = typ; + } else { + Assert.assertTrue( typ == UtilCtxt.ID_TYPE_RELAY ); + } + return devid; + } + + private void feedMessage( long rowid, byte[] msg ) + { + DbgUtils.logf( "RelayService::feedMessage: %d bytes for rowid %d", + msg.length, rowid ); + if ( BoardActivity.feedMessage( rowid, msg ) ) { + DbgUtils.logf( "feedMessage: board ate it" ); + // do nothing + } else { + RelayMsgSink sink = new RelayMsgSink(); + sink.setRowID( rowid ); + if ( GameUtils.feedMessage( this, rowid, msg, null, + sink ) ) { + setupNotification( rowid ); + } else { + DbgUtils.logf( "feedMessage: background dropped it" ); + } + } + } + private void fetchAndProcess() { long[][] rowIDss = new long[1][]; @@ -256,29 +727,79 @@ public class RelayService extends Service { private class RelayMsgSink extends MultiMsgSink { private HashMap> m_msgLists = null; + private long m_rowid = -1; + + public void setRowID( long rowid ) { m_rowid = rowid; } public void send( Context context ) { - sendToRelay( context, m_msgLists ); + if ( -1 == m_rowid ) { + sendToRelay( context, m_msgLists ); + } else { + Assert.assertNull( m_msgLists ); + } } /***** TransportProcs interface *****/ + public int transportSend( byte[] buf, final CommsAddrRec addr, + int gameID ) + { + Assert.assertTrue( -1 != m_rowid ); + sendPacket( RelayService.this, m_rowid, buf ); + return buf.length; + } + public boolean relayNoConnProc( byte[] buf, String relayID ) { - if ( null == m_msgLists ) { - m_msgLists = new HashMap>(); - } + if ( -1 != m_rowid ) { + sendNoConnMessage( m_rowid, relayID, buf ); + } else { + if ( null == m_msgLists ) { + m_msgLists = new HashMap>(); + } - ArrayList list = m_msgLists.get( relayID ); - if ( list == null ) { - list = new ArrayList(); - m_msgLists.put( relayID, list ); + ArrayList list = m_msgLists.get( relayID ); + if ( list == null ) { + list = new ArrayList(); + m_msgLists.put( relayID, list ); + } + list.add( buf ); } - list.add( buf ); - return true; } } + private static int nextPacketID( XWRelayReg cmd ) + { + int nextPacketID = 0; + synchronized( s_packetsSent ) { + if ( XWRelayReg.XWPDEV_ACK != cmd ) { + nextPacketID = s_nextPacketID++; + s_packetsSent.add( nextPacketID ); + } + } + DbgUtils.logf( "nextPacketID(%s)=>%d", cmd.toString(), nextPacketID ); + return nextPacketID; + } + + private static void noteAck( int packetID ) + { + synchronized( s_packetsSent ) { + s_packetsSent.remove( packetID ); + DbgUtils.logf( "Got ack for %d; there are %d unacked packets", + packetID, s_packetsSent.size() ); + } + } + + private class PacketHeader { + public int m_packetID; + public XWRelayReg m_cmd; + public PacketHeader( XWRelayReg cmd, int packetID ) { + DbgUtils.logf( "in PacketHeader contructor" ); + m_packetID = packetID; + m_cmd = cmd; + } + } + } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java index 3dfe97819..e32f63252 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java @@ -32,7 +32,6 @@ import android.content.SharedPreferences; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; -import android.os.IBinder; import android.preference.PreferenceManager; import android.telephony.SmsManager; import android.telephony.SmsMessage; @@ -52,7 +51,7 @@ import org.eehouse.android.xw4.jni.CommsAddrRec; import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType; import org.eehouse.android.xw4.jni.XwJNI; -public class SMSService extends Service { +public class SMSService extends XWService { private static final String INSTALL_URL = "http://eehouse.org/_/a.py/a "; private static final int MAX_SMS_LEN = 140; // ??? differs by network @@ -66,7 +65,7 @@ public class SMSService extends Service { private static final int INVITE = 2; private static final int SEND = 3; private static final int REMOVE = 4; - private static final int MESG_GAMEGONE = 5; + // private static final int MESG_GAMEGONE = 5; private static final int CHECK_MSGDB = 6; private static final int ADDED_MISSING = 7; private static final int STOP_SELF = 8; @@ -77,7 +76,6 @@ public class SMSService extends Service { private static final String PHONE = "PHONE"; private static Boolean s_showToasts = null; - private static MultiService s_srcMgr = null; // All messages are base64-encoded byte arrays. The first byte is // always one of these. What follows depends. @@ -196,16 +194,6 @@ public class SMSService extends Service { return result; } - public static void setListener( MultiService.MultiEventListener li ) - { - if ( XWApp.SMSSUPPORTED ) { - if ( null == s_srcMgr ) { - s_srcMgr = new MultiService(); - } - s_srcMgr.setListener( li ); - } - } - private static Intent getIntentTo( Context context, int cmd ) { if ( null == s_showToasts ) { @@ -303,12 +291,6 @@ public class SMSService extends Service { return result; } // onStartCommand - @Override - public IBinder onBind( Intent intent ) - { - return null; - } - private void inviteRemote( String phone, int gameID, String gameName, int lang, String dict, int nPlayersT, int nPlayersH ) @@ -662,13 +644,6 @@ public class SMSService extends Service { } } - private void sendResult( MultiEvent event, Object ... args ) - { - if ( null != s_srcMgr ) { - s_srcMgr.sendResult( event, args ); - } - } - private void registerReceivers() { registerReceiver( new BroadcastReceiver() { diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWActivity.java index 386ffbc55..358cfa50b 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWActivity.java @@ -54,8 +54,7 @@ public class XWActivity extends Activity protected void onResume() { DbgUtils.logf( "%s.onResume(this=%H)", getClass().getName(), this ); - BTService.setListener( this ); - SMSService.setListener( this ); + XWService.setListener( this ); super.onResume(); } @@ -63,8 +62,7 @@ public class XWActivity extends Activity protected void onPause() { DbgUtils.logf( "%s.onPause(this=%H)", getClass().getName(), this ); - BTService.setListener( null ); - SMSService.setListener( null ); + XWService.setListener( null ); super.onPause(); } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java index 1f0790181..32465060a 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java @@ -58,10 +58,10 @@ public class XWApp extends Application { RelayReceiver.RestartTimer( this ); UpdateCheckReceiver.restartTimer( this ); + BTService.startService( this ); - SMSService.checkForInvites( this ); - + RelayService.startService( this ); GCMIntentService.init( this ); } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWExpandableListActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWExpandableListActivity.java index c16d02762..aad00b807 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWExpandableListActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWExpandableListActivity.java @@ -41,6 +41,22 @@ public class XWExpandableListActivity extends ExpandableListActivity m_delegate = new DlgDelegate( this, this, savedInstanceState ); } + @Override + protected void onResume() + { + DbgUtils.logf( "%s.onResume(this=%H)", getClass().getName(), this ); + XWService.setListener( this ); + super.onResume(); + } + + @Override + protected void onPause() + { + DbgUtils.logf( "%s.onPause(this=%H)", getClass().getName(), this ); + XWService.setListener( null ); + super.onPause(); + } + @Override protected void onSaveInstanceState( Bundle outState ) { diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListActivity.java index 69ec78670..e93f3f926 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWListActivity.java @@ -51,8 +51,7 @@ public class XWListActivity extends ListActivity protected void onResume() { DbgUtils.logf( "%s.onResume(this=%H)", getClass().getName(), this ); - BTService.setListener( this ); - SMSService.setListener( this ); + XWService.setListener( this ); super.onResume(); } @@ -60,8 +59,7 @@ public class XWListActivity extends ListActivity protected void onPause() { DbgUtils.logf( "%s.onPause(this=%H)", getClass().getName(), this ); - BTService.setListener( null ); - SMSService.setListener( null ); + XWService.setListener( null ); super.onPause(); } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java index 1491c52ff..3ca696182 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWPrefs.java @@ -34,6 +34,16 @@ public class XWPrefs { return getPrefsBoolean( context, R.string.key_enable_sms, false ); } + public static boolean getUDPEnabled( Context context ) + { + return getPrefsBoolean( context, R.string.key_udp_relay, false ); + } + + public static boolean getGCMIgnored( Context context ) + { + return getPrefsBoolean( context, R.string.key_drop_gcm, false ); + } + public static boolean getDebugEnabled( Context context ) { return getPrefsBoolean( context, R.string.key_enable_debug, false ); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWService.java new file mode 100644 index 000000000..2f93a94e7 --- /dev/null +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWService.java @@ -0,0 +1,60 @@ +/* -*- compile-command: "cd ../../../../../; ant debug install"; -*- */ +/* + * Copyright 2010 - 2012 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. + */ + +package org.eehouse.android.xw4; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +import org.eehouse.android.xw4.MultiService.MultiEvent; + + +public class XWService extends Service { + + protected static MultiService s_srcMgr = null; + + @Override + public IBinder onBind( Intent intent ) + { + return null; + } + + public final static void setListener( MultiService.MultiEventListener li ) + { + if ( null == s_srcMgr ) { + DbgUtils.logf( "setListener: registering %s", li.getClass().getName() ); + s_srcMgr = new MultiService(); + } + s_srcMgr.setListener( li ); + } + + protected void sendResult( MultiEvent event, Object ... args ) + { + if ( null != s_srcMgr ) { + s_srcMgr.sendResult( event, args ); + } else { + DbgUtils.logf( "sendResult: dropping event" ); + } + } + + + +} diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java index 29c3ebdb0..752b8ea7d 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java @@ -286,6 +286,9 @@ public class JNIThread extends Thread { // state. In some cases it'll otherwise drop the move. XwJNI.server_do( m_jniGamePtr ); + // And let it tell the relay (if any) it's leaving + XwJNI.comms_stop( m_jniGamePtr ); + XwJNI.game_getGi( m_jniGamePtr, m_gi ); if ( null != m_newDict ) { m_gi.dictName = m_newDict; diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java index 5302f41f7..ebf8b9a53 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java @@ -60,6 +60,8 @@ public interface UtilCtxt { public static final int ID_TYPE_NONE = 0; public static final int ID_TYPE_RELAY = 1; public static final int ID_TYPE_ANDROID_GCM = 3; + public static final int ID_TYPE_ANDROID_OTHER = 4; + public static final int ID_TYPE_ANON = 5; String getDevID( /*out*/ byte[] typ ); void deviceRegistered( int devIDType, String idRelay ); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/XwJNI.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/XwJNI.java index 0d869ab06..e25e1ad35 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/XwJNI.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/XwJNI.java @@ -234,6 +234,7 @@ public class XwJNI { // Comms public static native void comms_start( int gamePtr ); + public static native void comms_stop( int gamePtr ); public static native void comms_resetSame( int gamePtr ); public static native void comms_getAddr( int gamePtr, CommsAddrRec addr ); public static native CommsAddrRec[] comms_getAddrs( int gamePtr ); diff --git a/xwords4/common/comms.c b/xwords4/common/comms.c index 72b7e9215..d735becc4 100644 --- a/xwords4/common/comms.c +++ b/xwords4/common/comms.c @@ -467,8 +467,11 @@ comms_transportFailed( CommsCtxt* comms ) void comms_destroy( CommsCtxt* comms ) { - CommsAddrRec aNew; - aNew.conType = COMMS_CONN_NONE; + /* did I call comms_stop()? */ + XP_ASSERT( COMMS_CONN_RELAY != comms->addr.conType + || COMMS_RELAYSTATE_UNCONNECTED == comms->r.relayState ); + + CommsAddrRec aNew = { .conType = COMMS_CONN_NONE }; util_addrChange( comms->util, &comms->addr, &aNew ); cleanupInternal( comms ); @@ -665,6 +668,14 @@ comms_start( CommsCtxt* comms ) sendConnect( comms, XP_FALSE ); } /* comms_start */ +void +comms_stop( CommsCtxt* comms ) +{ + if ( COMMS_CONN_RELAY == comms->addr.conType ) { + relayDisconnect( comms ); + } +} + static void sendConnect( CommsCtxt* comms, XP_Bool breakExisting ) { @@ -1185,6 +1196,14 @@ gameID( const CommsCtxt* comms ) gameID = comms->util->gameInfo->gameID; } + // XP_ASSERT( 0 != gameID ); + if ( 0 == gameID ) { + XP_LOGF( "%s: gameID STILL 0", __func__ ); + } else if ( 0 == comms->util->gameInfo->gameID ) { + XP_LOGF( "%s: setting gi's gameID to 0X%lX", __func__, gameID ); + comms->util->gameInfo->gameID = gameID; + } + /* Most of the time these will be the same, but early in a game they won't be. Would be nice not to have to use gameID. */ if ( 0 == gameID ) { diff --git a/xwords4/common/comms.h b/xwords4/common/comms.h index 04234c995..b7062e337 100644 --- a/xwords4/common/comms.h +++ b/xwords4/common/comms.h @@ -198,6 +198,7 @@ CommsCtxt* comms_makeFromStream( MPFORMAL XWStreamCtxt* stream, XW_UtilCtxt* util, const TransportProcs* procs ); void comms_start( CommsCtxt* comms ); +void comms_stop( CommsCtxt* comms ); void comms_writeToStream( CommsCtxt* comms, XWStreamCtxt* stream, XP_U16 saveToken ); void comms_saveSucceeded( CommsCtxt* comms, XP_U16 saveToken ); diff --git a/xwords4/common/comtypes.h b/xwords4/common/comtypes.h index 805694f1e..d841696f6 100644 --- a/xwords4/common/comtypes.h +++ b/xwords4/common/comtypes.h @@ -249,7 +249,7 @@ typedef struct _PlayerDicts { # define RELAY_ROOM_DEFAULT "Room 1" #endif #ifndef RELAY_PORT_DEFAULT -# define RELAY_PORT_DEFAULT 10999 +# define RELAY_PORT_DEFAULT 10997 #endif #ifdef MEM_DEBUG diff --git a/xwords4/common/game.c b/xwords4/common/game.c index ae73da301..d4604d4c9 100644 --- a/xwords4/common/game.c +++ b/xwords4/common/game.c @@ -133,62 +133,67 @@ game_makeNewGame( MPFORMAL XWGame* game, CurGameInfo* gi, board_prefsChanged( game->board, cp ); } /* game_makeNewGame */ -void +XP_Bool game_reset( MPFORMAL XWGame* game, CurGameInfo* gi, XW_UtilCtxt* XP_UNUSED_STANDALONE(util), CommonPrefs* cp, const TransportProcs* procs ) { + XP_Bool result = XP_FALSE; XP_U16 ii; - XP_ASSERT( !!game->model ); - XP_ASSERT( !!gi ); + if ( !!game->model ) { + XP_ASSERT( !!game->model ); + XP_ASSERT( !!gi ); - gi->gameID = makeGameID( util ); + gi->gameID = makeGameID( util ); #ifndef XWFEATURE_STANDALONE_ONLY - XP_U16 nPlayersHere = 0; - XP_U16 nPlayersTotal = 0; - checkServerRole( gi, &nPlayersHere, &nPlayersTotal ); + XP_U16 nPlayersHere = 0; + XP_U16 nPlayersTotal = 0; + checkServerRole( gi, &nPlayersHere, &nPlayersTotal ); - if ( !!game->comms ) { - if ( gi->serverRole == SERVER_STANDALONE ) { - comms_destroy( game->comms ); - game->comms = NULL; - } else { - comms_reset( game->comms, gi->serverRole != SERVER_ISCLIENT, - nPlayersHere, nPlayersTotal ); - } - } else if ( gi->serverRole != SERVER_STANDALONE ) { - game->comms = comms_make( MPPARM(mpool) util, - gi->serverRole != SERVER_ISCLIENT, - nPlayersHere, nPlayersTotal, procs + if ( !!game->comms ) { + if ( gi->serverRole == SERVER_STANDALONE ) { + comms_destroy( game->comms ); + game->comms = NULL; + } else { + comms_reset( game->comms, gi->serverRole != SERVER_ISCLIENT, + nPlayersHere, nPlayersTotal ); + } + } else if ( gi->serverRole != SERVER_STANDALONE ) { + game->comms = comms_make( MPPARM(mpool) util, + gi->serverRole != SERVER_ISCLIENT, + nPlayersHere, nPlayersTotal, procs #ifdef SET_GAMESEED - , 0 + , 0 #endif - ); - } + ); + } #else # ifdef DEBUG - mpool = mpool; /* quash unused formal warning */ + mpool = mpool; /* quash unused formal warning */ # endif #endif - model_setSize( game->model, gi->boardSize ); - server_reset( game->server, + model_setSize( game->model, gi->boardSize ); + server_reset( game->server, #ifndef XWFEATURE_STANDALONE_ONLY - game->comms + game->comms #else - NULL + NULL #endif - ); - board_reset( game->board ); + ); + board_reset( game->board ); - for ( ii = 0; ii < gi->nPlayers; ++ii ) { - gi->players[ii].secondsUsed = 0; + for ( ii = 0; ii < gi->nPlayers; ++ii ) { + gi->players[ii].secondsUsed = 0; + } + + server_prefsChanged( game->server, cp ); + board_prefsChanged( game->board, cp ); + result = XP_TRUE; } - - server_prefsChanged( game->server, cp ); - board_prefsChanged( game->board, cp ); + return result; } /* game_reset */ #ifdef XWFEATURE_CHANGEDICT @@ -335,6 +340,7 @@ game_dispose( XWGame* game ) #ifndef XWFEATURE_STANDALONE_ONLY if ( !!game->comms ) { + comms_stop( game->comms ); comms_destroy( game->comms ); game->comms = NULL; } diff --git a/xwords4/common/game.h b/xwords4/common/game.h index b5021af60..bc425308f 100644 --- a/xwords4/common/game.h +++ b/xwords4/common/game.h @@ -93,8 +93,8 @@ void game_makeNewGame( MPFORMAL XWGame* game, CurGameInfo* gi, ,XP_U16 gameSeed #endif ); -void game_reset( MPFORMAL XWGame* game, CurGameInfo* gi, XW_UtilCtxt* util, - CommonPrefs* cp, const TransportProcs* procs ); +XP_Bool game_reset( MPFORMAL XWGame* game, CurGameInfo* gi, XW_UtilCtxt* util, + CommonPrefs* cp, const TransportProcs* procs ); void game_changeDict( MPFORMAL XWGame* game, CurGameInfo* gi, DictionaryCtxt* dict ); diff --git a/xwords4/common/mempool.c b/xwords4/common/mempool.c index 799cce0a8..5a5768ac2 100644 --- a/xwords4/common/mempool.c +++ b/xwords4/common/mempool.c @@ -78,19 +78,21 @@ checkIsText( MemPoolEntry* entry ) { unsigned char* txt = (unsigned char*)entry->ptr; XP_U32 len = entry->size; + char* result = NULL; - while ( len-- ) { - unsigned char c = *txt++; - if ( c < 32 || c > 127 ) { - if ( len == 0 && c == '\0' ) { - return (char*)entry->ptr; - } else { - return (char*)NULL; + if ( 0 < len ) { + while ( len-- ) { + unsigned char c = *txt++; + if ( c < 32 || c > 127 ) { + if ( len == 0 && c == '\0' ) { + result = (char*)entry->ptr; + } + break; } } } - return (char*)NULL; + return result; } /* checkIsText */ #endif @@ -226,6 +228,7 @@ mpool_free( MemPoolCtx* mpool, void* ptr, const char* file, if ( !entry ) { XP_LOGF( "findEntryFor failed; called from %s, line %ld in %s", func, lineNo, file ); + XP_ASSERT( 0 ); } else { #ifdef MPOOL_DEBUG @@ -249,11 +252,7 @@ mpool_free( MemPoolCtx* mpool, void* ptr, const char* file, ++mpool->nFree; --mpool->nUsed; - - return; } - - XP_ASSERT( 0 ); } /* mpool_free */ void diff --git a/xwords4/linux/.gitignore b/xwords4/linux/.gitignore index 767e1d9ae..b77239cb6 100644 --- a/xwords4/linux/.gitignore +++ b/xwords4/linux/.gitignore @@ -6,3 +6,4 @@ obj_linux_rel log_*_*.txt discon_ok2.sh_logs test_backsend.sh_* +games.db diff --git a/xwords4/linux/Makefile b/xwords4/linux/Makefile index 75d4f46bc..1c1d4fd3e 100644 --- a/xwords4/linux/Makefile +++ b/xwords4/linux/Makefile @@ -101,6 +101,7 @@ DEFINES += -DXWFEATURE_WALKDICT DEFINES += -DXWFEATURE_WALKDICT_FILTER DEFINES += -DXWFEATURE_DICTSANITY DEFINES += -DHASH_STREAM +DEFINES += -DRELAY_NAME_DEFAULT="\"localhost\"" #DEFINES += -DXWFEATURE_SCOREONEPASS ### Enable zero or one of these two ### #DEFINES += -DXWFEATURE_TRAYUNDO_ALL @@ -112,6 +113,7 @@ DEFINES += -DXWFEATURE_HILITECELL DEFINES += -DXWFEATURE_CHANGEDICT DEFINES += -DXWFEATURE_DEVID DEFINES += -DXWFEATURE_COMMSACK +DEFINES += -DCOMMS_XPORT_FLAGSPROC # MAX_ROWS controls STREAM_VERS_BIGBOARD and with it move hashing DEFINES += -DMAX_ROWS=32 @@ -169,6 +171,7 @@ INCLUDES += ${EXTRAINCS} ifdef DO_GTK GTK_OBJS = \ $(BUILD_PLAT_DIR)/gtkmain.o \ + $(BUILD_PLAT_DIR)/gtkboard.o \ $(BUILD_PLAT_DIR)/gtkdraw.o \ $(BUILD_PLAT_DIR)/gtkask.o \ $(BUILD_PLAT_DIR)/gtkletterask.o \ @@ -201,6 +204,8 @@ OBJ = \ $(BUILD_PLAT_DIR)/linuxsms.o \ $(BUILD_PLAT_DIR)/linuxdict.o \ $(BUILD_PLAT_DIR)/linuxutl.o \ + $(BUILD_PLAT_DIR)/gamesdb.o \ + $(BUILD_PLAT_DIR)/relaycon.o \ $(CURSES_OBJS) $(GTK_OBJS) $(MAIN_OBJS) LIBS = -lm -luuid $(GPROFFLAG) diff --git a/xwords4/linux/cursesask.c b/xwords4/linux/cursesask.c index 463c622dc..ffca1a9a7 100644 --- a/xwords4/linux/cursesask.c +++ b/xwords4/linux/cursesask.c @@ -32,8 +32,8 @@ /* Figure out how many lines there are and how wide the widest is. */ short -cursesask( CursesAppGlobals* globals, char* question, short numButtons, - char* button1, ... ) +cursesask( CursesAppGlobals* globals, const char* question, short numButtons, + const char* button1, ... ) { WINDOW* confWin; int x, y, rows, row, nLines; diff --git a/xwords4/linux/cursesask.h b/xwords4/linux/cursesask.h index f4930a682..36f20fb35 100644 --- a/xwords4/linux/cursesask.h +++ b/xwords4/linux/cursesask.h @@ -22,8 +22,8 @@ #include "cursesmain.h" -short cursesask( CursesAppGlobals* globals, char* question, - short numButtons, char* button1, ... ); +short cursesask( CursesAppGlobals* globals, const char* question, + short numButtons, const char* button1, ... ); #endif diff --git a/xwords4/linux/cursesdlgutil.c b/xwords4/linux/cursesdlgutil.c index b7484691d..740bf27ec 100644 --- a/xwords4/linux/cursesdlgutil.c +++ b/xwords4/linux/cursesdlgutil.c @@ -70,7 +70,7 @@ measureAskText( const XP_UCHAR* question, int width, FormatInfo* fip ) void drawButtons( WINDOW* win, XP_U16 line, short spacePerButton, - short numButtons, short curSelButton, char** button1 ) + short numButtons, short curSelButton, const char** button1 ) { short i; for ( i = 0; i < numButtons; ++i ) { diff --git a/xwords4/linux/cursesdlgutil.h b/xwords4/linux/cursesdlgutil.h index 1f9199c7e..dd9a2403c 100644 --- a/xwords4/linux/cursesdlgutil.h +++ b/xwords4/linux/cursesdlgutil.h @@ -44,6 +44,6 @@ typedef struct FormatInfo { void measureAskText( const XP_UCHAR* question, int maxWidth, FormatInfo* fip ); void drawButtons( WINDOW* confWin, XP_U16 line, short spacePerButton, - short numButtons, short curSelButton, char** button1 ); + short numButtons, short curSelButton, const char** button1 ); #endif diff --git a/xwords4/linux/cursesletterask.c b/xwords4/linux/cursesletterask.c index 332d722eb..1dc5d0e19 100644 --- a/xwords4/linux/cursesletterask.c +++ b/xwords4/linux/cursesletterask.c @@ -57,7 +57,7 @@ curses_askLetter( CursesAppGlobals* globals, XP_UCHAR* query, short curSelButton = 1; /* force draw by being different */ short maxWidth; short numCtlButtons; - char* ctlButtons[] = { "Ok", "Cancel" }; + const char* ctlButtons[] = { "Ok", "Cancel" }; XP_Bool dismissed = XP_FALSE; FormatInfo fi; int len; @@ -138,7 +138,7 @@ curses_askLetter( CursesAppGlobals* globals, XP_UCHAR* query, MAX_TILE_BUTTON_WIDTH-1, nInRow, newSelButton - textsOffsets[i], - (char**)&textPtrs[textsOffsets[i]] ); + (const char**)&textPtrs[textsOffsets[i]] ); } diff --git a/xwords4/linux/cursesmain.c b/xwords4/linux/cursesmain.c index 84f73b0ec..938bc4cbc 100644 --- a/xwords4/linux/cursesmain.c +++ b/xwords4/linux/cursesmain.c @@ -42,6 +42,7 @@ #include "linuxmain.h" #include "linuxutl.h" +#include "linuxdict.h" #include "cursesmain.h" #include "cursesask.h" #include "cursesletterask.h" @@ -61,6 +62,8 @@ #include "dbgutil.h" #include "linuxsms.h" #include "linuxudp.h" +#include "gamesdb.h" +#include "relaycon.h" #ifdef CURSES_SMALL_SCREEN # define MENU_WINDOW_HEIGHT 1 @@ -208,7 +211,7 @@ static CursesMenuHandler getHandlerForKey( const MenuList* list, char ch ); #ifdef MEM_DEBUG -# define MEMPOOL params->util->mpool, +# define MEMPOOL cGlobals->util->mpool, #else # define MEMPOOL #endif @@ -237,7 +240,7 @@ curses_util_userPickTileBlank( XW_UtilCtxt* uc, XP_U16 playerNum, CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; char query[128]; XP_S16 index; - char* playerName = globals->cGlobals.params->gi.players[playerNum].name; + char* playerName = globals->cGlobals.gi->players[playerNum].name; snprintf( query, sizeof(query), "Pick tile for %s! (Tab or type letter to select " @@ -255,7 +258,7 @@ curses_util_userPickTileTray( XW_UtilCtxt* uc, const PickInfo* XP_UNUSED(pi), CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; char query[128]; XP_S16 index; - char* playerName = globals->cGlobals.params->gi.players[playerNum].name; + char* playerName = globals->cGlobals.gi->players[playerNum].name; snprintf( query, sizeof(query), "Pick tile for %s! (Tab or type letter to select " @@ -343,7 +346,7 @@ cursesShowFinalScores( CursesAppGlobals* globals ) XWStreamCtxt* stream; XP_UCHAR* text; - stream = mem_stream_make( MPPARM(globals->cGlobals.params->util->mpool) + stream = mem_stream_make( MPPARM(globals->cGlobals.util->mpool) globals->cGlobals.params->vtMgr, globals, CHANNEL_NONE, NULL ); server_writeFinalScores( globals->cGlobals.game.server, stream ); @@ -408,6 +411,14 @@ curses_util_informNetDict( XW_UtilCtxt* uc, XP_LangCode XP_UNUSED(lang), XP_LOGF( "%s: %s => %s (cksum: %s)", __func__, oldName, newName, newSum ); } +static void +curses_util_setIsServer( XW_UtilCtxt* uc, XP_Bool isServer ) +{ + LOG_FUNC(); + CommonGlobals* cGlobals = (CommonGlobals*)uc->closure; + linuxSetIsServer( cGlobals, isServer ); +} + #ifdef XWFEATURE_HILITECELL static XP_Bool curses_util_hiliteCell( XW_UtilCtxt* uc, @@ -1039,7 +1050,7 @@ data_socket_proc( GIOChannel* source, GIOCondition condition, gpointer data ) if ( 0 != (G_IO_IN & condition) ) { CursesAppGlobals* globals = (CursesAppGlobals*)data; int fd = g_io_channel_unix_get_fd( source ); - unsigned char buf[256]; + unsigned char buf[1024]; int nBytes; CommsAddrRec addrRec; CommsAddrRec* addrp = NULL; @@ -1087,7 +1098,7 @@ data_socket_proc( GIOChannel* source, GIOCondition condition, gpointer data ) 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 ); + curses_util_requestTime( globals->cGlobals.util ); } } } @@ -1116,6 +1127,15 @@ curses_socket_changed( void* closure, int oldSock, int newSock, #endif } /* curses_socket_changed */ +static void +curses_onGameSaved( void* closure, sqlite3_int64 rowid, + XP_Bool XP_UNUSED(firstTime) ) +{ + CursesAppGlobals* globals = (CursesAppGlobals*)closure; + /* May not be recorded */ + globals->cGlobals.selRow = rowid; +} + #ifdef USE_GLIBLOOP static gboolean handle_quitwrite( GIOChannel* XP_UNUSED(source), GIOCondition XP_UNUSED(condition), gpointer data ) @@ -1284,7 +1304,7 @@ blocking_gotEvent( CursesAppGlobals* globals, int* ch ) globals ); } else { #ifndef XWFEATURE_STANDALONE_ONLY - unsigned char buf[256]; + unsigned char buf[1024]; int nBytes; CommsAddrRec addrRec; CommsAddrRec* addrp = NULL; @@ -1489,7 +1509,7 @@ curses_util_remSelected( XW_UtilCtxt* uc ) XWStreamCtxt* stream; XP_UCHAR* text; - stream = mem_stream_make( MPPARM(globals->cGlobals.params->util->mpool) + stream = mem_stream_make( MPPARM(globals->cGlobals.util->mpool) globals->cGlobals.params->vtMgr, globals, CHANNEL_NONE, NULL ); board_formatRemainingTiles( globals->cGlobals.game.board, stream ); @@ -1553,6 +1573,8 @@ setupCursesUtilCallbacks( CursesAppGlobals* globals, XW_UtilCtxt* util ) 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 @@ -1609,7 +1631,7 @@ positionSizeStuff( CursesAppGlobals* globals, int width, int height ) XP_U16 cellWidth, cellHt, scoreLeft, scoreWidth; BoardCtxt* board = globals->cGlobals.game.board; int remWidth = width; - int nRows = globals->cGlobals.params->gi.boardSize; + int nRows = globals->cGlobals.gi->boardSize; cellWidth = CURSES_CELL_WIDTH; cellHt = CURSES_CELL_HT; @@ -1719,6 +1741,119 @@ handle_stdin( GIOChannel* XP_UNUSED_DBG(source), GIOCondition condition, } #endif +#ifdef COMMS_XPORT_FLAGSPROC +static XP_U32 +curses_getFlags( void* XP_UNUSED(closure) ) +{ + return COMMS_XPORT_FLAGS_HASNOCONN; +} +#endif + +static void +cursesGotBuf( void* closure, const XP_U8* buf, XP_U16 len ) +{ + LOG_FUNC(); + CursesAppGlobals* globals = (CursesAppGlobals*)closure; + XP_U32 gameToken; + XP_ASSERT( sizeof(gameToken) < len ); + gameToken = ntohl(*(XP_U32*)&buf[0]); + buf += sizeof(gameToken); + len -= sizeof(gameToken); + + gameGotBuf( &globals->cGlobals, XP_TRUE, buf, len ); +} + +static gint +curses_requestMsgs( gpointer data ) +{ + CursesAppGlobals* globals = (CursesAppGlobals*)data; + XP_UCHAR devIDBuf[64] = {0}; + db_fetch( globals->cGlobals.pDb, KEY_RDEVID, devIDBuf, sizeof(devIDBuf) ); + if ( '\0' != devIDBuf[0] ) { + relaycon_requestMsgs( globals->cGlobals.params, devIDBuf ); + } else { + XP_LOGF( "%s: not requesting messages as don't have relay id", __func__ ); + } + return 0; /* don't run again */ +} + + +static void +cursesNoticeRcvd( void* closure ) +{ + LOG_FUNC(); + CursesAppGlobals* globals = (CursesAppGlobals*)closure; + (void)g_idle_add( curses_requestMsgs, globals ); +} + +static void +cursesDevIDChanged( void* closure, const XP_UCHAR* devID ) +{ + CursesAppGlobals* globals = (CursesAppGlobals*)closure; + CommonGlobals* cGlobals = &globals->cGlobals; + sqlite3* pDb = cGlobals->pDb; + if ( !!devID ) { + XP_LOGF( "%s(devID=%s)", __func__, devID ); + db_store( pDb, KEY_RDEVID, devID ); + } else { + XP_LOGF( "%s: bad relayid", __func__ ); + db_remove( pDb, KEY_RDEVID ); + + DevIDType typ; + const XP_UCHAR* devID = linux_getDevID( cGlobals->params, &typ ); + relaycon_reg( cGlobals->params, devID, typ ); + } +} + +static void +cursesErrorMsgRcvd( void* closure, const XP_UCHAR* msg ) +{ + CursesAppGlobals* globals = (CursesAppGlobals*)closure; + if ( !!globals->lastErr && 0 == strcmp( globals->lastErr, msg ) ) { + XP_LOGF( "skipping error message from relay" ); + } else { + g_free( globals->lastErr ); + globals->lastErr = g_strdup( msg ); + (void)cursesask( globals, msg, 1, "Ok" ); + } +} + + +static gboolean +curses_app_socket_proc( GIOChannel* source, GIOCondition condition, + gpointer data ) +{ + if ( 0 != (G_IO_IN & condition) ) { + CursesAppGlobals* globals = (CursesAppGlobals*)data; + int socket = g_io_channel_unix_get_fd( source ); + GList* iter; + for ( iter = globals->sources; !!iter; iter = iter->next ) { + SourceData* sd = (SourceData*)iter->data; + if ( sd->channel == source ) { + (*sd->proc)( sd->procClosure, socket ); + break; + } + } + XP_ASSERT( !!iter ); /* didn't fail to find it */ + } + return TRUE; +} + +static void +cursesUDPSocketChanged( void* closure, int newSock, int XP_UNUSED(oldSock), + SockReceiver proc, void* procClosure ) +{ + CursesAppGlobals* globals = (CursesAppGlobals*)closure; + + SourceData* sd = g_malloc( sizeof(*sd) ); + sd->channel = g_io_channel_unix_new( newSock ); + sd->watch = g_io_add_watch( sd->channel, G_IO_IN | G_IO_ERR, + curses_app_socket_proc, globals ); + sd->proc = proc; + sd->procClosure = procClosure; + globals->sources = g_list_append( globals->sources, sd ); +} + static gboolean chatsTimerFired( gpointer data ) { @@ -1748,6 +1883,7 @@ void cursesmain( XP_Bool isServer, LaunchParams* params ) { int width, height; + CommonGlobals* cGlobals = &g_globals.cGlobals; memset( &g_globals, 0, sizeof(g_globals) ); @@ -1763,6 +1899,9 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) g_globals.cGlobals.socketChanged = curses_socket_changed; g_globals.cGlobals.socketChangedClosure = &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; @@ -1778,7 +1917,11 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) g_globals.cGlobals.cp.robotTradePct = params->robotTradePct; #endif - setupCursesUtilCallbacks( &g_globals, params->util ); + 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 ( params->conType == COMMS_CONN_RELAY ) { @@ -1792,12 +1935,14 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) cursesListenOnSocket( &g_globals, 0, handle_stdin ); } setOneSecondTimer( &g_globals.cGlobals ); + # ifdef DEBUG int piperesult = # endif pipe( g_globals.quitpipe ); XP_ASSERT( piperesult == 0 ); cursesListenOnSocket( &g_globals, g_globals.quitpipe[0], handle_quitwrite ); + #else cursesListenOnSocket( &g_globals, 0 ); /* stdin */ @@ -1824,7 +1969,9 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) .rconnd = relay_connd_curses, .rerror = relay_error_curses, .sendNoConn = relay_sendNoConn_curses, - .flags = COMMS_XPORT_FLAGS_HASNOCONN, +# ifdef COMMS_XPORT_FLAGSPROC + .getFlags = curses_getFlags, +# endif #endif }; @@ -1846,7 +1993,40 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) cursesDrawCtxtMake( g_globals.boardWin ); XWStreamCtxt* stream = NULL; - if ( !!params->fileName && file_exists( params->fileName ) ) { + if ( !!params->dbName ) { + g_globals.cGlobals.pDb = openGamesDB( params->dbName ); + + RelayConnProcs procs = { + .msgReceived = cursesGotBuf, + .msgNoticeReceived = cursesNoticeRcvd, + .devIDChanged = cursesDevIDChanged, + .msgErrorMsg = cursesErrorMsgRcvd, + .socketChanged = cursesUDPSocketChanged, + }; + + relaycon_init( params, &procs, &g_globals, + params->connInfo.relay.relayName, + params->connInfo.relay.defaultSendPort ); + DevIDType typ; + const XP_UCHAR* devID = linux_getDevID( params, &typ ); + relaycon_reg( params, devID, typ ); + + GSList* games = listGames( g_globals.cGlobals.pDb ); + if ( !!games ) { + stream = mem_stream_make( MEMPOOL params->vtMgr, + &g_globals.cGlobals, CHANNEL_NONE, + NULL ); + sqlite3_int64 selRow = *(sqlite3_int64*)games->data; + if ( loadGame( stream, g_globals.cGlobals.pDb, selRow ) ) { + g_globals.cGlobals.selRow = selRow; + } else { + stream_destroy( stream ); + stream = NULL; + } + g_slist_free( games ); + } + + } else if ( !!params->fileName && file_exists( params->fileName ) ) { stream = streamFromFile( &g_globals.cGlobals, params->fileName, &g_globals ); #ifdef USE_SQLITE @@ -1855,27 +2035,40 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) #endif } + if ( NULL == cGlobals->dict ) { + if ( !!stream ) { + cGlobals->dict = makeDictForStream( cGlobals, stream ); + } else { + cGlobals->dict = + linux_dictionary_make( MEMPOOL params, + cGlobals->gi->dictName, XP_TRUE ); + } + } + cGlobals->gi->dictLang = dict_getLangCode( cGlobals->dict ); + if ( !!stream ) { - (void)game_makeFromStream( MEMPOOL stream, &g_globals.cGlobals.game, - ¶ms->gi, params->dict, ¶ms->dicts, - params->util, + (void)game_makeFromStream( MEMPOOL stream, &cGlobals->game, + cGlobals->gi, cGlobals->dict, + &cGlobals->dicts, cGlobals->util, (DrawCtx*)g_globals.draw, &g_globals.cGlobals.cp, &procs ); stream_destroy( stream ); - if ( !isServer && params->gi.serverRole == SERVER_ISSERVER ) { + if ( !isServer && cGlobals->gi->serverRole == SERVER_ISSERVER ) { isServer = XP_TRUE; } opened = XP_TRUE; } if ( !opened ) { - game_makeNewGame( MEMPOOL &g_globals.cGlobals.game, ¶ms->gi, - params->util, (DrawCtx*)g_globals.draw, + game_makeNewGame( MEMPOOL &cGlobals->game, cGlobals->gi, + cGlobals->util, (DrawCtx*)g_globals.draw, &g_globals.cGlobals.cp, &procs, params->gameSeed ); + g_globals.cGlobals.selRow = -1; + saveGame( &g_globals.cGlobals ); } #ifndef XWFEATURE_STANDALONE_ONLY - if ( g_globals.cGlobals.game.comms ) { + if ( cGlobals->game.comms ) { CommsAddrRec addr = {0}; if ( 0 ) { @@ -1907,22 +2100,22 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) sizeof(params->connInfo.bt.hostAddr) ); # endif } - comms_setAddr( g_globals.cGlobals.game.comms, &addr ); + comms_setAddr( cGlobals->game.comms, &addr ); } #endif - model_setDictionary( g_globals.cGlobals.game.model, params->dict ); - setSquareBonuses( &g_globals.cGlobals ); + model_setDictionary( cGlobals->game.model, cGlobals->dict ); + setSquareBonuses( cGlobals ); positionSizeStuff( &g_globals, width, height ); #ifndef XWFEATURE_STANDALONE_ONLY /* send any events that need to get off before the event loop begins */ if ( !isServer ) { if ( 1 /* stream_open( params->info.clientInfo.stream ) */) { - server_initClientConnection( g_globals.cGlobals.game.server, + server_initClientConnection( cGlobals->game.server, mem_stream_make( MEMPOOL params->vtMgr, - &g_globals.cGlobals, + cGlobals, (XP_PlayerAddr)0, sendOnClose ) ); } else { @@ -1961,10 +2154,13 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) } #endif } + if ( !!g_globals.cGlobals.game.comms ) { + comms_stop( g_globals.cGlobals.game.comms ); + } saveGame( &g_globals.cGlobals ); game_dispose( &g_globals.cGlobals.game ); /* takes care of the dict */ - gi_disposePlayerInfo( MEMPOOL &g_globals.cGlobals.params->gi ); + gi_disposePlayerInfo( MEMPOOL cGlobals->gi ); #ifdef XWFEATURE_BLUETOOTH linux_bt_close( &g_globals.cGlobals ); @@ -1977,5 +2173,12 @@ cursesmain( XP_Bool isServer, LaunchParams* params ) #endif endwin(); + + if ( !!params->dbName ) { + closeGamesDB( g_globals.cGlobals.pDb ); + } + relaycon_cleanup( params ); + + linux_util_vt_destroy( g_globals.cGlobals.util ); } /* cursesmain */ #endif /* PLATFORM_NCURSES */ diff --git a/xwords4/linux/cursesmain.h b/xwords4/linux/cursesmain.h index fa158471b..1d859e254 100644 --- a/xwords4/linux/cursesmain.h +++ b/xwords4/linux/cursesmain.h @@ -68,6 +68,7 @@ struct CursesAppGlobals { XP_Bool doDraw; const struct MenuList* menuList; XP_U16 nLinesMenu; + gchar* lastErr; XP_U16 nChatsSent; @@ -98,13 +99,6 @@ struct CursesAppGlobals { #endif }; -#ifdef USE_GLIBLOOP -typedef struct _SourceData { - GIOChannel* channel; - gint watch; -} SourceData; -#endif - DrawCtx* cursesDrawCtxtMake( WINDOW* boardWin ); /* Ports: Client and server pick a port at startup on which they'll listen. diff --git a/xwords4/linux/gamesdb.c b/xwords4/linux/gamesdb.c new file mode 100644 index 000000000..8107e7c0d --- /dev/null +++ b/xwords4/linux/gamesdb.c @@ -0,0 +1,292 @@ +/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ +/* + * Copyright 2000-2013 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "gamesdb.h" +#include "main.h" + +static void getColumnText( sqlite3_stmt *ppStmt, int iCol, XP_UCHAR* buf, + int len ); + + +sqlite3* +openGamesDB( const char* dbName ) +{ + sqlite3* pDb = NULL; + int result = sqlite3_open( dbName, &pDb ); + XP_ASSERT( SQLITE_OK == result ); + + const char* createGamesStr = + "CREATE TABLE games ( " + "rowid INTEGER PRIMARY KEY AUTOINCREMENT" + ",game BLOB" + ",room VARCHAR(32)" + ",ended INT(1)" + ",turn INT(2)" + ",nmoves INT" + ",nmissing INT(2)" + ")"; + result = sqlite3_exec( pDb, createGamesStr, NULL, NULL, NULL ); + + const char* createValuesStr = + "CREATE TABLE pairs ( key TEXT UNIQUE,value TEXT )"; + result = sqlite3_exec( pDb, createValuesStr, NULL, NULL, NULL ); + XP_LOGF( "sqlite3_exec=>%d", result ); + + return pDb; +} + +void +closeGamesDB( sqlite3* pDb ) +{ + sqlite3_close( pDb ); + XP_LOGF( "%s finished", __func__ ); +} + +void +writeToDB( XWStreamCtxt* stream, void* closure ) +{ + int result; + CommonGlobals* cGlobals = (CommonGlobals*)closure; + sqlite3_int64 selRow = cGlobals->selRow; + sqlite3* pDb = cGlobals->pDb; + XP_U16 len = stream_getSize( stream ); + char buf[256]; + char* query; + + sqlite3_stmt* stmt = NULL; + XP_Bool newGame = -1 == selRow; + if ( newGame ) { /* new row; need to insert blob first */ + query = "INSERT INTO games (game) VALUES (?)"; + } else { + const char* fmt = "UPDATE games SET game=? where rowid=%lld"; + snprintf( buf, sizeof(buf), fmt, selRow ); + query = buf; + } + + result = sqlite3_prepare_v2( pDb, query, -1, &stmt, NULL ); + XP_ASSERT( SQLITE_OK == result ); + result = sqlite3_bind_zeroblob( stmt, 1 /*col 0 ??*/, len ); + XP_ASSERT( SQLITE_OK == result ); + result = sqlite3_step( stmt ); + XP_ASSERT( SQLITE_DONE == result ); + + if ( newGame ) { /* new row; need to insert blob first */ + selRow = sqlite3_last_insert_rowid( pDb ); + XP_LOGF( "%s: new rowid: %lld", __func__, selRow ); + cGlobals->selRow = selRow; + } + + sqlite3_blob* blob; + result = sqlite3_blob_open( pDb, "main", "games", "game", + selRow, 1 /*flags: writeable*/, &blob ); + XP_ASSERT( SQLITE_OK == result ); + const XP_U8* ptr = stream_getPtr( stream ); + result = sqlite3_blob_write( blob, ptr, len, 0 ); + XP_ASSERT( SQLITE_OK == result ); + result = sqlite3_blob_close( blob ); + XP_ASSERT( SQLITE_OK == result ); + if ( !!stmt ) { + sqlite3_finalize( stmt ); + } + + (*cGlobals->onSave)( cGlobals->onSaveClosure, selRow, newGame ); +} + +void +summarize( CommonGlobals* cGlobals ) +{ + XP_S16 nMoves = model_getNMoves( cGlobals->game.model ); + XP_Bool gameOver = server_getGameIsOver( cGlobals->game.server ); + XP_S16 turn = server_getCurrentTurn( cGlobals->game.server ); + XP_S16 nMissing = 0; + CommsAddrRec addr = {0}; + gchar* room = ""; + + if ( !!cGlobals->game.comms ) { + nMissing = server_getMissingPlayers( cGlobals->game.server ); + comms_getAddr( cGlobals->game.comms, &addr ); + if ( COMMS_CONN_RELAY == addr.conType ) { + room = addr.u.ip_relay.invite; + } + } + + const char* fmt = "UPDATE games SET room='%s', ended=%d, turn=%d, nmissing=%d, nmoves=%d" + " where rowid=%lld"; + XP_UCHAR buf[256]; + snprintf( buf, sizeof(buf), fmt, room, gameOver?1:0, turn, nMissing, nMoves, + cGlobals->selRow ); + XP_LOGF( "query: %s", buf ); + sqlite3_stmt* stmt = NULL; + int result = sqlite3_prepare_v2( cGlobals->pDb, buf, -1, &stmt, NULL ); + XP_ASSERT( SQLITE_OK == result ); + result = sqlite3_step( stmt ); + XP_ASSERT( SQLITE_DONE == result ); + sqlite3_finalize( stmt ); +} + +GSList* +listGames( sqlite3* pDb ) +{ + GSList* list = NULL; + + sqlite3_stmt *ppStmt; + int result = sqlite3_prepare_v2( pDb, + "SELECT rowid FROM games ORDER BY rowid", + -1, &ppStmt, NULL ); + XP_ASSERT( SQLITE_OK == result ); + while ( NULL != ppStmt ) { + switch( sqlite3_step( ppStmt ) ) { + case SQLITE_ROW: /* have data */ + { + sqlite3_int64* data = g_malloc( sizeof( *data ) ); + *data = sqlite3_column_int64( ppStmt, 0 ); + XP_LOGF( "%s: got a row; id=%lld", __func__, *data ); + list = g_slist_append( list, data ); + } + break; + case SQLITE_DONE: + sqlite3_finalize( ppStmt ); + ppStmt = NULL; + break; + default: + XP_ASSERT( 0 ); + break; + } + } + return list; +} + +XP_Bool +getGameInfo( sqlite3* pDb, sqlite3_int64 rowid, GameInfo* gib ) +{ + XP_Bool success = XP_FALSE; + const char* fmt = "SELECT room, ended, turn, nmoves, nmissing " + "FROM games WHERE rowid = %lld"; + XP_UCHAR query[256]; + snprintf( query, sizeof(query), fmt, rowid ); + + sqlite3_stmt* ppStmt; + int result = sqlite3_prepare_v2( pDb, query, -1, &ppStmt, NULL ); + XP_ASSERT( SQLITE_OK == result ); + result = sqlite3_step( ppStmt ); + if ( SQLITE_ROW == result ) { + success = XP_TRUE; + getColumnText( ppStmt, 0, gib->room, sizeof(gib->room) ); + gib->gameOver = 1 == sqlite3_column_int( ppStmt, 1 ); + gib->turn = sqlite3_column_int( ppStmt, 2 ); + gib->nMoves = sqlite3_column_int( ppStmt, 3 ); + gib->nMissing = sqlite3_column_int( ppStmt, 4 ); + snprintf( gib->name, sizeof(gib->name), "Game %lld", rowid ); + } + sqlite3_finalize( ppStmt ); + return success; +} + +XP_Bool +loadGame( XWStreamCtxt* stream, sqlite3* pDb, sqlite3_int64 rowid ) +{ + char buf[256]; + snprintf( buf, sizeof(buf), "SELECT game from games WHERE rowid = %lld", rowid ); + + sqlite3_stmt *ppStmt; + int result = sqlite3_prepare_v2( pDb, buf, -1, &ppStmt, NULL ); + XP_ASSERT( SQLITE_OK == result ); + result = sqlite3_step( ppStmt ); + XP_Bool success = SQLITE_ROW == result; + if ( success ) { + const void* ptr = sqlite3_column_blob( ppStmt, 0 ); + int size = sqlite3_column_bytes( ppStmt, 0 ); + stream_putBytes( stream, ptr, size ); + } + sqlite3_finalize( ppStmt ); + return success; +} + +void +deleteGame( sqlite3* pDb, sqlite3_int64 rowid ) +{ + char query[256]; + snprintf( query, sizeof(query), "DELETE FROM games WHERE rowid = %lld", rowid ); + sqlite3_stmt* ppStmt; + int result = sqlite3_prepare_v2( pDb, query, -1, &ppStmt, NULL ); + XP_ASSERT( SQLITE_OK == result ); + result = sqlite3_step( ppStmt ); + XP_ASSERT( SQLITE_DONE == result ); + sqlite3_finalize( ppStmt ); +} + +void +db_store( sqlite3* pDb, const gchar* key, const gchar* value ) +{ + char buf[256]; + snprintf( buf, sizeof(buf), + "INSERT OR REPLACE INTO pairs (key, value) VALUES ('%s', '%s')", + key, value ); + sqlite3_stmt *ppStmt; + int result = sqlite3_prepare_v2( pDb, buf, -1, &ppStmt, NULL ); + XP_ASSERT( SQLITE_OK == result ); + result = sqlite3_step( ppStmt ); + XP_ASSERT( SQLITE_DONE == result ); + sqlite3_finalize( ppStmt ); +} + +XP_Bool +db_fetch( sqlite3* pDb, const gchar* key, gchar* buf, gint buflen ) +{ + XP_Bool found; + char query[256]; + snprintf( query, sizeof(query), + "SELECT value from pairs where key = '%s'", key ); + sqlite3_stmt *ppStmt; + int result = sqlite3_prepare_v2( pDb, query, -1, &ppStmt, NULL ); + XP_ASSERT( SQLITE_OK == result ); + result = sqlite3_step( ppStmt ); + found = SQLITE_ROW == result; + if ( found ) { + getColumnText( ppStmt, 0, buf, buflen ); + } else { + buf[0] = '\0'; + } + sqlite3_finalize( ppStmt ); + return found; +} + +void +db_remove( sqlite3* pDb, const gchar* key ) +{ + char query[256]; + snprintf( query, sizeof(query), "DELETE FROM pairs WHERE key = '%s'", key ); + sqlite3_stmt *ppStmt; + int result = sqlite3_prepare_v2( pDb, query, -1, &ppStmt, NULL ); + XP_ASSERT( SQLITE_OK == result ); + result = sqlite3_step( ppStmt ); + XP_ASSERT( SQLITE_DONE == result ); + sqlite3_finalize( ppStmt ); +} + +static void +getColumnText( sqlite3_stmt *ppStmt, int iCol, XP_UCHAR* buf, int len ) +{ + const unsigned char* txt = sqlite3_column_text( ppStmt, iCol ); + int needLen = sqlite3_column_bytes( ppStmt, iCol ); + XP_ASSERT( needLen < len ); + XP_MEMCPY( buf, txt, needLen ); + buf[needLen] = '\0'; +} diff --git a/xwords4/linux/gamesdb.h b/xwords4/linux/gamesdb.h new file mode 100644 index 000000000..2d34f96e3 --- /dev/null +++ b/xwords4/linux/gamesdb.h @@ -0,0 +1,58 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ +/* + * Copyright 2000-2012 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _GAMESDB_H_ +#define _GAMESDB_H_ + +#include +#include + +#include "main.h" +#include "comtypes.h" + +typedef struct _GameInfo { + XP_UCHAR name[128]; + XP_UCHAR room[128]; + XP_S16 nMoves; + XP_Bool gameOver; + XP_S16 turn; + XP_S16 nMissing; +} GameInfo; + + +sqlite3* openGamesDB( const char* dbName ); +void closeGamesDB( sqlite3* dbp ); + +void writeToDB( XWStreamCtxt* stream, void* closure ); +void summarize( CommonGlobals* cGlobals ); + +/* Return GSList whose data is (ptrs to) rowids */ +GSList* listGames( sqlite3* dbp ); +XP_Bool getGameInfo( sqlite3* dbp, sqlite3_int64 rowid, GameInfo* gib ); +XP_Bool loadGame( XWStreamCtxt* stream, sqlite3* pDb, sqlite3_int64 rowid ); +void deleteGame( sqlite3* pDb, sqlite3_int64 rowid ); + +#define KEY_RDEVID "RDEVID" + +void db_store( sqlite3* dbp, const gchar* key, const gchar* value ); +XP_Bool db_fetch( sqlite3* dbp, const gchar* key, gchar* buf, gint buflen ); +void db_remove( sqlite3* dbp, const gchar* key ); + +#endif diff --git a/xwords4/linux/gtkask.h b/xwords4/linux/gtkask.h index d9e319eea..78ccf2e33 100644 --- a/xwords4/linux/gtkask.h +++ b/xwords4/linux/gtkask.h @@ -23,7 +23,7 @@ #ifndef _GTKASK_H_ #define _GTKASK_H_ -#include "gtkmain.h" +#include "gtkboard.h" /* Returns true for "yes" or "ok" answer, false otherwise. */ diff --git a/xwords4/linux/gtkaskdict.c b/xwords4/linux/gtkaskdict.c index 565c662c3..d635d595b 100644 --- a/xwords4/linux/gtkaskdict.c +++ b/xwords4/linux/gtkaskdict.c @@ -41,9 +41,9 @@ init_list( GtkWidget* list ) } static void -add_to_list( GtkWidget *list, const gchar *str ) +add_to_list( GtkWidget* list, const gchar* str ) { - GtkListStore *store = + GtkListStore* store = GTK_LIST_STORE( gtk_tree_view_get_model(GTK_TREE_VIEW(list))); GtkTreeIter iter; gtk_list_store_append( store, &iter ); diff --git a/xwords4/linux/gtkaskdict.h b/xwords4/linux/gtkaskdict.h index 134285427..4a78a6a8e 100644 --- a/xwords4/linux/gtkaskdict.h +++ b/xwords4/linux/gtkaskdict.h @@ -23,7 +23,7 @@ #ifndef _GTKASKDICT_H_ #define _GTKASKDICT_H_ -#include "gtkmain.h" +#include "gtkboard.h" gchar* gtkaskdict( GSList* dicts, gchar* buf, gint buflen ); diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c new file mode 100644 index 000000000..4c9f9308b --- /dev/null +++ b/xwords4/linux/gtkboard.c @@ -0,0 +1,2649 @@ +/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ +/* + * Copyright 2000-2013 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_GTK + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifndef CLIENT_ONLY +/* # include */ +#endif +#include +#include +#include +#include + +#include "main.h" +#include "linuxmain.h" +#include "linuxutl.h" +#include "linuxbt.h" +#include "linuxudp.h" +#include "linuxsms.h" +#include "gtkmain.h" + +#include "draw.h" +#include "game.h" +#include "gtkask.h" +#include "gtkchat.h" +#include "gtknewgame.h" +#include "gtkletterask.h" +#include "gtkpasswdask.h" +#include "gtkntilesask.h" +#include "gtkaskdict.h" +#include "linuxdict.h" +/* #include "undo.h" */ +#include "gtkdraw.h" +#include "memstream.h" +#include "filestream.h" +#include "gamesdb.h" +#include "relaycon.h" + +/* static guint gtkSetupClientSocket( GtkGameGlobals* globals, int sock ); */ +static void setCtrlsForTray( GtkGameGlobals* globals ); +static void new_game( GtkWidget* widget, GtkGameGlobals* globals ); +static XP_Bool new_game_impl( GtkGameGlobals* globals, XP_Bool fireConnDlg ); +static void setZoomButtons( GtkGameGlobals* globals, XP_Bool* inOut ); +static void disenable_buttons( GtkGameGlobals* globals ); + + +#define GTK_TRAY_HT_ROWS 3 + +#if 0 +static XWStreamCtxt* +lookupClientStream( GtkGameGlobals* globals, int sock ) +{ + short i; + for ( i = 0; i < MAX_NUM_PLAYERS; ++i ) { + ClientStreamRec* rec = &globals->clientRecs[i]; + if ( rec->sock == sock ) { + XP_ASSERT( rec->stream != NULL ); + return rec->stream; + } + } + XP_ASSERT( i < MAX_NUM_PLAYERS ); + return NULL; +} /* lookupClientStream */ + +static void +rememberClient( GtkGameGlobals* globals, guint key, int sock, + XWStreamCtxt* stream ) +{ + short i; + for ( i = 0; i < MAX_NUM_PLAYERS; ++i ) { + ClientStreamRec* rec = &globals->clientRecs[i]; + if ( rec->stream == NULL ) { + XP_ASSERT( stream != NULL ); + rec->stream = stream; + rec->key = key; + rec->sock = sock; + break; + } + } + XP_ASSERT( i < MAX_NUM_PLAYERS ); +} /* rememberClient */ +#endif + +static void +gtkSetAltState( GtkGameGlobals* globals, guint state ) +{ + globals->altKeyDown + = (state & (GDK_MOD1_MASK|GDK_SHIFT_MASK|GDK_CONTROL_MASK)) != 0; +} + +static gint +button_press_event( GtkWidget* XP_UNUSED(widget), GdkEventButton *event, + GtkGameGlobals* globals ) +{ + XP_Bool redraw, handled; + + gtkSetAltState( globals, event->state ); + + if ( !globals->mouseDown ) { + globals->mouseDown = XP_TRUE; + redraw = board_handlePenDown( globals->cGlobals.game.board, + event->x, event->y, &handled ); + if ( redraw ) { + board_draw( globals->cGlobals.game.board ); + disenable_buttons( globals ); + } + } + return 1; +} /* button_press_event */ + +static gint +motion_notify_event( GtkWidget* XP_UNUSED(widget), GdkEventMotion *event, + GtkGameGlobals* globals ) +{ + XP_Bool handled; + + gtkSetAltState( globals, event->state ); + + if ( globals->mouseDown ) { + handled = board_handlePenMove( globals->cGlobals.game.board, event->x, + event->y ); + if ( handled ) { + board_draw( globals->cGlobals.game.board ); + disenable_buttons( globals ); + } + } else { + handled = XP_FALSE; + } + + return handled; +} /* motion_notify_event */ + +static gint +button_release_event( GtkWidget* XP_UNUSED(widget), GdkEventMotion *event, + GtkGameGlobals* globals ) +{ + XP_Bool redraw; + + gtkSetAltState( globals, event->state ); + + if ( globals->mouseDown ) { + redraw = board_handlePenUp( globals->cGlobals.game.board, + event->x, + event->y ); + if ( redraw ) { + board_draw( globals->cGlobals.game.board ); + disenable_buttons( globals ); + } + globals->mouseDown = XP_FALSE; + } + return 1; +} /* button_release_event */ + +#ifdef KEY_SUPPORT +static XP_Key +evtToXPKey( GdkEventKey* event, XP_Bool* movesCursorP ) +{ + XP_Key xpkey = XP_KEY_NONE; + XP_Bool movesCursor = XP_FALSE; + guint keyval = event->keyval; + + switch( keyval ) { +#ifdef KEYBOARD_NAV + case GDK_Return: + xpkey = XP_RETURN_KEY; + break; + case GDK_space: + xpkey = XP_RAISEFOCUS_KEY; + break; + + case GDK_Left: + xpkey = XP_CURSOR_KEY_LEFT; + movesCursor = XP_TRUE; + break; + case GDK_Right: + xpkey = XP_CURSOR_KEY_RIGHT; + movesCursor = XP_TRUE; + break; + case GDK_Up: + xpkey = XP_CURSOR_KEY_UP; + movesCursor = XP_TRUE; + break; + case GDK_Down: + xpkey = XP_CURSOR_KEY_DOWN; + movesCursor = XP_TRUE; + break; +#endif + case GDK_BackSpace: + XP_LOGF( "... it's a DEL" ); + xpkey = XP_CURSOR_KEY_DEL; + break; + default: + keyval = keyval & 0x00FF; /* mask out gtk stuff */ + if ( isalpha( keyval ) ) { + xpkey = toupper(keyval); + break; +#ifdef NUMBER_KEY_AS_INDEX + } else if ( isdigit( keyval ) ) { + xpkey = keyval; + break; +#endif + } + } + *movesCursorP = movesCursor; + return xpkey; +} /* evtToXPKey */ + +#ifdef KEYBOARD_NAV +static gint +key_press_event( GtkWidget* XP_UNUSED(widget), GdkEventKey* event, + GtkGameGlobals* globals ) +{ + XP_Bool handled = XP_FALSE; + XP_Bool movesCursor; + XP_Key xpkey = evtToXPKey( event, &movesCursor ); + + gtkSetAltState( globals, event->state ); + + if ( xpkey != XP_KEY_NONE ) { + XP_Bool draw = globals->keyDown ? + board_handleKeyRepeat( globals->cGlobals.game.board, xpkey, + &handled ) + : board_handleKeyDown( globals->cGlobals.game.board, xpkey, + &handled ); + if ( draw ) { + board_draw( globals->cGlobals.game.board ); + } + } + globals->keyDown = XP_TRUE; + return 1; +} +#endif + +static gint +key_release_event( GtkWidget* XP_UNUSED(widget), GdkEventKey* event, + GtkGameGlobals* globals ) +{ + XP_Bool handled = XP_FALSE; + XP_Bool movesCursor; + XP_Key xpkey = evtToXPKey( event, &movesCursor ); + + gtkSetAltState( globals, event->state ); + + if ( xpkey != XP_KEY_NONE ) { + XP_Bool draw; + draw = board_handleKeyUp( globals->cGlobals.game.board, xpkey, + &handled ); +#ifdef KEYBOARD_NAV + if ( movesCursor && !handled ) { + BoardObjectType order[] = { OBJ_SCORE, OBJ_BOARD, OBJ_TRAY }; + draw = linShiftFocus( &globals->cGlobals, xpkey, order, + NULL ) || draw; + } +#endif + if ( draw ) { + board_draw( globals->cGlobals.game.board ); + } + } + +/* XP_ASSERT( globals->keyDown ); */ +#ifdef KEYBOARD_NAV + globals->keyDown = XP_FALSE; +#endif + + return handled? 1 : 0; /* gtk will do something with the key if 0 + returned */ +} /* key_release_event */ +#endif + +#ifdef MEM_DEBUG +# define MEMPOOL globals->cGlobals.util->mpool, +#else +# define MEMPOOL +#endif + +static void +relay_status_gtk( void* closure, CommsRelayState state ) +{ + XP_LOGF( "%s got status: %s", __func__, CommsRelayState2Str(state) ); + GtkGameGlobals* globals = (GtkGameGlobals*)closure; + if ( !!globals->draw ) { + globals->cGlobals.state = state; + globals->stateChar = 'A' + COMMS_RELAYSTATE_ALLCONNECTED - state; + draw_gtk_status( globals->draw, globals->stateChar ); + } +} + +static void +relay_connd_gtk( void* closure, XP_UCHAR* const room, + XP_Bool XP_UNUSED(reconnect), XP_U16 devOrder, + XP_Bool allHere, XP_U16 nMissing ) +{ + XP_Bool skip = XP_FALSE; + char buf[256]; + + if ( allHere ) { + snprintf( buf, sizeof(buf), + "All expected players have joined in %s. Play!", room ); + } else { + if ( nMissing > 0 ) { + snprintf( buf, sizeof(buf), "%dth connected to relay; waiting " + "in %s for %d player[s].", devOrder, room, nMissing ); + } else { + /* an allHere message should be coming immediately, so no + notification now. */ + skip = XP_TRUE; + } + } + + if ( !skip ) { + GtkGameGlobals* globals = (GtkGameGlobals*)closure; + (void)gtkask_timeout( globals->window, buf, GTK_BUTTONS_OK, 500 ); + } +} + +static gint +invoke_new_game( gpointer data ) +{ + GtkGameGlobals* globals = (GtkGameGlobals*)data; + new_game_impl( globals, XP_FALSE ); + return 0; +} + +static gint +invoke_new_game_conns( gpointer data ) +{ + GtkGameGlobals* globals = (GtkGameGlobals*)data; + new_game_impl( globals, XP_TRUE ); + return 0; +} + +static void +relay_error_gtk( void* closure, XWREASON relayErr ) +{ + LOG_FUNC(); + GtkGameGlobals* globals = (GtkGameGlobals*)closure; + + gint (*proc)( gpointer data ) = NULL; + switch( relayErr ) { + case XWRELAY_ERROR_NO_ROOM: + case XWRELAY_ERROR_DUP_ROOM: + proc = invoke_new_game_conns; + break; + case XWRELAY_ERROR_TOO_MANY: + case XWRELAY_ERROR_BADPROTO: + proc = invoke_new_game; + break; + case XWRELAY_ERROR_DELETED: + gtkask_timeout( globals->window, + "relay says another device deleted game.", + GTK_BUTTONS_OK, 1000 ); + break; + case XWRELAY_ERROR_DEADGAME: + break; + default: + assert(0); + break; + } + + if ( !!proc ) { + (void)g_idle_add( proc, globals ); + } +} + +static XP_Bool +relay_sendNoConn_gtk( const XP_U8* msg, XP_U16 len, const XP_UCHAR* relayID, + void* closure ) +{ + GtkGameGlobals* globals = (GtkGameGlobals*)closure; + XP_Bool success = XP_FALSE; + if ( !!globals->cGlobals.pDb && !globals->draw ) { + XP_S16 nSent = relaycon_sendnoconn( globals->cGlobals.params, msg, len, + relayID, globals->cGlobals.selRow ); + success = nSent == len; + } + return success; +} /* relay_sendNoConn_gtk */ + +#ifdef COMMS_XPORT_FLAGSPROC +static XP_U32 +gtk_getFlags( void* closure ) +{ + GtkGameGlobals* globals = (GtkGameGlobals*)closure; + return (!!globals->draw) ? COMMS_XPORT_FLAGS_NONE + : COMMS_XPORT_FLAGS_HASNOCONN; +} +#endif + +static void +setTransportProcs( TransportProcs* procs, GtkGameGlobals* globals ) +{ + procs->closure = globals; + procs->send = LINUX_SEND; +#ifdef COMMS_XPORT_FLAGSPROC + procs->getFlags = gtk_getFlags; +#endif +#ifdef COMMS_HEARTBEAT + procs->reset = linux_reset; +#endif +#ifdef XWFEATURE_RELAY + procs->rstatus = relay_status_gtk; + procs->rconnd = relay_connd_gtk; + procs->rerror = relay_error_gtk; + procs->sendNoConn = relay_sendNoConn_gtk; +#endif +} + +static void +createOrLoadObjects( GtkGameGlobals* globals ) +{ + XWStreamCtxt* stream = NULL; + XP_Bool opened = XP_FALSE; + +#ifndef XWFEATURE_STANDALONE_ONLY +#endif + CommonGlobals* cGlobals = &globals->cGlobals; + LaunchParams* params = cGlobals->params; + + globals->draw = (GtkDrawCtx*)gtkDrawCtxtMake( globals->drawing_area, + globals ); + + TransportProcs procs; + setTransportProcs( &procs, globals ); + + if ( !!params->fileName && file_exists( params->fileName ) ) { + stream = streamFromFile( cGlobals, params->fileName, globals ); +#ifdef USE_SQLITE + } else if ( !!params->dbFileName && file_exists( params->dbFileName ) ) { + stream = streamFromDB( cGlobals, globals ); +#endif + } else if ( !!cGlobals->pDb && 0 <= cGlobals->selRow ) { + stream = mem_stream_make( MPPARM(cGlobals->util->mpool) + params->vtMgr, + cGlobals, CHANNEL_NONE, NULL ); + if ( !loadGame( stream, cGlobals->pDb, cGlobals->selRow ) ) { + stream_destroy( stream ); + stream = NULL; + } + } + + if ( !!stream ) { + if ( NULL == cGlobals->dict ) { + cGlobals->dict = makeDictForStream( cGlobals, stream ); + } + + opened = game_makeFromStream( MEMPOOL stream, &cGlobals->game, + cGlobals->gi, cGlobals->dict, + &cGlobals->dicts, cGlobals->util, + (DrawCtx*)globals->draw, + &cGlobals->cp, &procs ); + XP_LOGF( "%s: loaded gi at %p", __func__, &cGlobals->gi ); + stream_destroy( stream ); + } + + if ( !opened ) { + CommsAddrRec addr = cGlobals->addr; + + /* XP_MEMSET( &addr, 0, sizeof(addr) ); */ + /* addr.conType = cGlobals->addr.conType; */ + +#ifdef XWFEATURE_RELAY + /* if ( addr.conType == COMMS_CONN_RELAY ) { */ + /* XP_ASSERT( !!params->connInfo.relay.relayName ); */ + /* globals->cGlobals.defaultServerName */ + /* = params->connInfo.relay.relayName; */ + /* } */ +#endif + game_makeNewGame( MEMPOOL &cGlobals->game, cGlobals->gi, + cGlobals->util, (DrawCtx*)globals->draw, + &cGlobals->cp, &procs, params->gameSeed ); + + // addr.conType = params->conType; + if ( 0 ) { +#ifdef XWFEATURE_RELAY + } else if ( addr.conType == COMMS_CONN_RELAY ) { + /* addr.u.ip_relay.ipAddr = 0; */ + /* addr.u.ip_relay.port = params->connInfo.relay.defaultSendPort; */ + /* addr.u.ip_relay.seeksPublicRoom = params->connInfo.relay.seeksPublicRoom; */ + /* addr.u.ip_relay.advertiseRoom = params->connInfo.relay.advertiseRoom; */ + /* XP_STRNCPY( addr.u.ip_relay.hostName, params->connInfo.relay.relayName, */ + /* sizeof(addr.u.ip_relay.hostName) - 1 ); */ + /* XP_STRNCPY( addr.u.ip_relay.invite, params->connInfo.relay.invite, */ + /* sizeof(addr.u.ip_relay.invite) - 1 ); */ +#endif +#ifdef XWFEATURE_BLUETOOTH + } else if ( addr.conType == COMMS_CONN_BT ) { + XP_ASSERT( sizeof(addr.u.bt.btAddr) + >= sizeof(params->connInfo.bt.hostAddr)); + XP_MEMCPY( &addr.u.bt.btAddr, ¶ms->connInfo.bt.hostAddr, + sizeof(params->connInfo.bt.hostAddr) ); +#endif +#ifdef XWFEATURE_IP_DIRECT + } else if ( addr.conType == COMMS_CONN_IP_DIRECT ) { + XP_STRNCPY( addr.u.ip.hostName_ip, params->connInfo.ip.hostName, + sizeof(addr.u.ip.hostName_ip) - 1 ); + addr.u.ip.port_ip = params->connInfo.ip.port; +#endif +#ifdef XWFEATURE_SMS + } else if ( addr.conType == COMMS_CONN_SMS ) { + XP_STRNCPY( addr.u.sms.phone, params->connInfo.sms.serverPhone, + sizeof(addr.u.sms.phone) - 1 ); + addr.u.sms.port = params->connInfo.sms.port; +#endif + } + + /* Need to save in order to have a valid selRow for the first send */ + saveGame( cGlobals ); + +#ifndef XWFEATURE_STANDALONE_ONLY + /* This may trigger network activity */ + if ( !!cGlobals->game.comms ) { + XP_ASSERT( COMMS_CONN_RELAY == addr.conType ); + comms_setAddr( cGlobals->game.comms, &addr ); + } +#endif + model_setDictionary( cGlobals->game.model, cGlobals->dict ); + setSquareBonuses( cGlobals ); + model_setPlayerDicts( cGlobals->game.model, &cGlobals->dicts ); + +#ifdef XWFEATURE_SEARCHLIMIT + cGlobals->gi->allowHintRect = params->allowHintRect; +#endif + + + if ( params->needsNewGame ) { + new_game_impl( globals, XP_FALSE ); +#ifndef XWFEATURE_STANDALONE_ONLY + } else { + DeviceRole serverRole = cGlobals->gi->serverRole; + if ( serverRole == SERVER_ISCLIENT ) { + XWStreamCtxt* stream = + mem_stream_make( MEMPOOL params->vtMgr, + cGlobals, CHANNEL_NONE, + sendOnClose ); + server_initClientConnection( cGlobals->game.server, + stream ); + } +#endif + } + } + + if ( !params->fileName && !!params->dbName ) { + XP_UCHAR buf[64]; + snprintf( buf, sizeof(buf), "%s / %lld", params->dbName, + cGlobals->selRow ); + gtk_window_set_title( GTK_WINDOW(globals->window), buf ); + } + +#ifndef XWFEATURE_STANDALONE_ONLY + if ( !!globals->cGlobals.game.comms ) { + comms_start( globals->cGlobals.game.comms ); + } +#endif + server_do( globals->cGlobals.game.server ); + + disenable_buttons( globals ); +} /* createOrLoadObjects */ + +/* Create a new backing pixmap of the appropriate size and set up contxt to + * draw using that size. + */ +static gboolean +configure_event( GtkWidget* widget, GdkEventConfigure* XP_UNUSED(event), + GtkGameGlobals* globals ) +{ + short bdWidth, bdHeight; + short timerLeft, timerTop; + gint hscale, vscale; + gint trayTop; + gint boardTop = 0; + XP_U16 netStatWidth = 0; + gint nCols; + gint nRows; + + if ( globals->draw == NULL ) { + createOrLoadObjects( globals ); + } + + nCols = globals->cGlobals.gi->boardSize; + nRows = nCols; + bdWidth = widget->allocation.width - (GTK_RIGHT_MARGIN + + GTK_BOARD_LEFT_MARGIN); + if ( globals->cGlobals.params->verticalScore ) { + bdWidth -= GTK_VERT_SCORE_WIDTH; + } + bdHeight = widget->allocation.height - (GTK_TOP_MARGIN + GTK_BOTTOM_MARGIN) + - GTK_MIN_TRAY_SCALEV - GTK_BOTTOM_MARGIN; + + hscale = bdWidth / nCols; + if ( 0 != globals->cGlobals.params->nHidden ) { + vscale = hscale; + } else { + vscale = (bdHeight / (nCols + GTK_TRAY_HT_ROWS)); /* makd tray height + 3x cell height */ + } + + if ( !globals->cGlobals.params->verticalScore ) { + boardTop += GTK_HOR_SCORE_HEIGHT; + } + + trayTop = boardTop + (vscale * nRows); + /* move tray up if part of board's meant to be hidden */ + trayTop -= vscale * globals->cGlobals.params->nHidden; + board_setPos( globals->cGlobals.game.board, GTK_BOARD_LEFT, boardTop, + hscale * nCols, vscale * nRows, hscale * 4, XP_FALSE ); + /* board_setScale( globals->cGlobals.game.board, hscale, vscale ); */ + globals->gridOn = XP_TRUE; + + if ( !!globals->cGlobals.game.comms ) { + netStatWidth = GTK_NETSTAT_WIDTH; + } + + timerTop = GTK_TIMER_TOP; + if ( globals->cGlobals.params->verticalScore ) { + timerLeft = GTK_BOARD_LEFT + (hscale*nCols) + 1; + board_setScoreboardLoc( globals->cGlobals.game.board, + timerLeft, + GTK_VERT_SCORE_TOP, + GTK_VERT_SCORE_WIDTH, + vscale*nCols, + XP_FALSE ); + + } else { + timerLeft = GTK_BOARD_LEFT + (hscale*nCols) + - GTK_TIMER_WIDTH - netStatWidth; + board_setScoreboardLoc( globals->cGlobals.game.board, + GTK_BOARD_LEFT, GTK_HOR_SCORE_TOP, + timerLeft-GTK_BOARD_LEFT, + GTK_HOR_SCORE_HEIGHT, + XP_TRUE ); + + } + + /* Still pending: do this for the vertical score case */ + if ( globals->cGlobals.game.comms ) { + globals->netStatLeft = timerLeft + GTK_TIMER_WIDTH; + globals->netStatTop = 0; + } + + board_setTimerLoc( globals->cGlobals.game.board, timerLeft, timerTop, + GTK_TIMER_WIDTH, GTK_HOR_SCORE_HEIGHT ); + + board_setTrayLoc( globals->cGlobals.game.board, GTK_TRAY_LEFT, trayTop, + hscale * nCols, vscale * GTK_TRAY_HT_ROWS + 10, + GTK_DIVIDER_WIDTH ); + + setCtrlsForTray( globals ); + + board_invalAll( globals->cGlobals.game.board ); + + XP_Bool inOut[2]; + board_zoom( globals->cGlobals.game.board, 0, inOut ); + setZoomButtons( globals, inOut ); + + return TRUE; +} /* configure_event */ + +/* Redraw the screen from the backing pixmap */ +static gint +expose_event( GtkWidget* XP_UNUSED(widget), + GdkEventExpose* XP_UNUSED(event), + GtkGameGlobals* globals ) +{ + /* + gdk_draw_rectangle( widget->window,//((GtkDrawCtx*)globals->draw)->pixmap, + widget->style->white_gc, + TRUE, + 0, 0, + widget->allocation.width, + widget->allocation.height+widget->allocation.y ); + */ + /* I want to inval only the area that's exposed, but the rect is always + empty, even when clearly shouldn't be. Need to investigate. Until + fixed, use board_invalAll to ensure board is drawn.*/ +/* board_invalRect( globals->cGlobals.game.board, (XP_Rect*)&event->area ); */ + + board_invalAll( globals->cGlobals.game.board ); + board_draw( globals->cGlobals.game.board ); + draw_gtk_status( globals->draw, globals->stateChar ); + +/* gdk_draw_pixmap( widget->window, */ +/* widget->style->fg_gc[GTK_WIDGET_STATE (widget)], */ +/* ((GtkDrawCtx*)globals->draw)->pixmap, */ +/* event->area.x, event->area.y, */ +/* event->area.x, event->area.y, */ +/* event->area.width, event->area.height ); */ + + return FALSE; +} /* expose_event */ + +#if 0 +static gint +handle_client_event( GtkWidget *widget, GdkEventClient *event, + GtkGameGlobals* globals ) +{ + XP_LOGF( "handle_client_event called: event->type = " ); + if ( event->type == GDK_CLIENT_EVENT ) { + XP_LOGF( "GDK_CLIENT_EVENT" ); + return 1; + } else { + XP_LOGF( "%d", event->type ); + return 0; + } +} /* handle_client_event */ +#endif + +void +destroy_board_window( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) +{ + LOG_FUNC(); + if ( !!globals->cGlobals.game.comms ) { + comms_stop( globals->cGlobals.game.comms ); + } + saveGame( &globals->cGlobals ); + windowDestroyed( globals ); + // gtk_main_quit(); +} + +static void +cleanup( GtkGameGlobals* globals ) +{ + saveGame( &globals->cGlobals ); + g_source_remove( globals->idleID ); + +#ifdef XWFEATURE_BLUETOOTH + linux_bt_close( &globals->cGlobals ); +#endif +#ifdef XWFEATURE_SMS + linux_sms_close( &globals->cGlobals ); +#endif +#ifdef XWFEATURE_IP_DIRECT + linux_udp_close( &globals->cGlobals ); +#endif +#ifdef XWFEATURE_RELAY + linux_close_socket( &globals->cGlobals ); +#endif + game_dispose( &globals->cGlobals.game ); /* takes care of the dict */ + gi_disposePlayerInfo( MEMPOOL globals->cGlobals.gi ); + + linux_util_vt_destroy( globals->cGlobals.util ); + free( globals->cGlobals.util ); +} /* cleanup */ + +GtkWidget* +makeAddSubmenu( GtkWidget* menubar, gchar* label ) +{ + GtkWidget* submenu; + GtkWidget* item; + + item = gtk_menu_item_new_with_label( label ); + gtk_menu_bar_append( GTK_MENU_BAR(menubar), item ); + + submenu = gtk_menu_new(); + gtk_menu_item_set_submenu( GTK_MENU_ITEM(item), submenu ); + + gtk_widget_show(item); + + return submenu; +} /* makeAddSubmenu */ + +static void +tile_values( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) +{ + if ( !!globals->cGlobals.game.server ) { + XWStreamCtxt* stream = + mem_stream_make( MEMPOOL + globals->cGlobals.params->vtMgr, + globals, + CHANNEL_NONE, + catOnClose ); + server_formatDictCounts( globals->cGlobals.game.server, stream, 5 ); + stream_putU8( stream, '\n' ); + stream_destroy( stream ); + } + +} /* tile_values */ + +static void +game_history( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) +{ + catGameHistory( &globals->cGlobals ); +} /* game_history */ + +#ifdef TEXT_MODEL +static void +dump_board( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) +{ + if ( !!globals->cGlobals.game.model ) { + XWStreamCtxt* stream = + mem_stream_make( MEMPOOL + globals->cGlobals.params->vtMgr, + globals, + CHANNEL_NONE, + catOnClose ); + model_writeToTextStream( globals->cGlobals.game.model, stream ); + stream_destroy( stream ); + } +} +#endif + +static void +final_scores( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) +{ + XP_Bool gameOver = server_getGameIsOver( globals->cGlobals.game.server ); + + if ( gameOver ) { + catFinalScores( &globals->cGlobals, -1 ); + } else { + if ( gtkask( globals->window, + "Are you sure you want to resign?", + GTK_BUTTONS_YES_NO ) ) { + globals->cGlobals.manualFinal = XP_TRUE; + server_endGame( globals->cGlobals.game.server ); + gameOver = TRUE; + } + } + + /* the end game listener will take care of printing the final scores */ +} /* final_scores */ + +static XP_Bool +new_game_impl( GtkGameGlobals* globals, XP_Bool fireConnDlg ) +{ + XP_Bool success = XP_FALSE; + CommsAddrRec addr; + + if ( !!globals->cGlobals.game.comms ) { + comms_getAddr( globals->cGlobals.game.comms, &addr ); + } else { + comms_getInitialAddr( &addr, RELAY_NAME_DEFAULT, RELAY_PORT_DEFAULT ); + } + + CurGameInfo* gi = globals->cGlobals.gi; + success = newGameDialog( globals, gi, &addr, XP_TRUE, fireConnDlg ); + if ( success ) { +#ifndef XWFEATURE_STANDALONE_ONLY + XP_Bool isClient = gi->serverRole == SERVER_ISCLIENT; +#endif + TransportProcs procs = { + .closure = globals, + .send = LINUX_SEND, +#ifdef COMMS_HEARTBEAT + .reset = linux_reset, +#endif + }; + + if ( !game_reset( MEMPOOL &globals->cGlobals.game, gi, + globals->cGlobals.util, + &globals->cGlobals.cp, &procs ) ) { + /* if ( NULL == globals->draw ) { */ + /* globals->draw = (GtkDrawCtx*)gtkDrawCtxtMake( globals->drawing_area, */ + /* globals ); */ + /* } */ + /* game_makeNewGame( MEMPOOL &globals->cGlobals.game, gi, */ + /* globals->cGlobals.params->util, */ + /* (DrawCtx*)globals->draw, */ + /* &globals->cGlobals.cp, &procs, */ + /* globals->cGlobals.params->gameSeed ); */ + /* ModelCtxt* model = globals->cGlobals.game.model; */ + /* if ( NULL == model_getDictionary( model ) ) { */ + /* DictionaryCtxt* dict = */ + /* linux_dictionary_make( MEMPOOL globals->cGlobals.params, */ + /* gi->dictName, XP_TRUE ); */ + /* model_setDictionary( model, dict ); */ + /* } */ + } + +#ifndef XWFEATURE_STANDALONE_ONLY + if ( !!globals->cGlobals.game.comms ) { + comms_setAddr( globals->cGlobals.game.comms, &addr ); + } else if ( gi->serverRole != SERVER_STANDALONE ) { + XP_ASSERT(0); + } + + if ( isClient ) { + XWStreamCtxt* stream = + mem_stream_make( MEMPOOL globals->cGlobals.params->vtMgr, + &globals->cGlobals, CHANNEL_NONE, + sendOnClose ); + server_initClientConnection( globals->cGlobals.game.server, + stream ); + } +#endif + (void)server_do( globals->cGlobals.game.server ); /* assign tiles, etc. */ + board_invalAll( globals->cGlobals.game.board ); + board_draw( globals->cGlobals.game.board ); + } + return success; +} /* new_game_impl */ + +static void +new_game( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) +{ + new_game_impl( globals, XP_FALSE ); +} /* new_game */ + +static void +game_info( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) +{ + CommsAddrRec addr; + comms_getAddr( globals->cGlobals.game.comms, &addr ); + + /* Anything to do if OK is clicked? Changed names etc. already saved. Try + server_do in case one's become a robot. */ + CurGameInfo* gi = globals->cGlobals.gi; + if ( newGameDialog( globals, gi, &addr, XP_FALSE, XP_FALSE ) ) { + if ( server_do( globals->cGlobals.game.server ) ) { + board_draw( globals->cGlobals.game.board ); + } + } +} + +static void +load_game( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* XP_UNUSED(globals) ) +{ + XP_ASSERT(0); +} /* load_game */ + +static void +save_game( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* XP_UNUSED(globals) ) +{ + XP_ASSERT(0); +} /* save_game */ + +#ifdef XWFEATURE_CHANGEDICT +static void +change_dictionary( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) +{ + CommonGlobals* cGlobals = &globals->cGlobals; + LaunchParams* params = cGlobals->params; + GSList* dicts = listDicts( params ); + gchar buf[265]; + gchar* name = gtkaskdict( dicts, buf, VSIZE(buf) ); + if ( !!name ) { + DictionaryCtxt* dict = + linux_dictionary_make( MPPARM(cGlobals->util->mpool) params, name, + params->useMmap ); + game_changeDict( MPPARM(cGlobals->util->mpool) &cGlobals->game, + cGlobals->gi, dict ); + } + g_slist_free( dicts ); +} /* change_dictionary */ +#endif + +static void +handle_undo( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* XP_UNUSED(globals) ) +{ +} /* handle_undo */ + +static void +handle_redo( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* XP_UNUSED(globals) ) +{ +} /* handle_redo */ + +#ifdef FEATURE_TRAY_EDIT +static void +handle_trayEditToggle( GtkWidget* XP_UNUSED(widget), + GtkGameGlobals* XP_UNUSED(globals), + XP_Bool XP_UNUSED(on) ) +{ +} /* handle_trayEditToggle */ + +static void +handle_trayEditToggle_on( GtkWidget* widget, GtkGameGlobals* globals ) +{ + handle_trayEditToggle( widget, globals, XP_TRUE ); +} + +static void +handle_trayEditToggle_off( GtkWidget* widget, GtkGameGlobals* globals ) +{ + handle_trayEditToggle( widget, globals, XP_FALSE ); +} +#endif + +static void +handle_trade_cancel( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) +{ + BoardCtxt* board = globals->cGlobals.game.board; + if ( board_endTrade( board ) ) { + board_draw( board ); + } +} + +#ifndef XWFEATURE_STANDALONE_ONLY +static void +handle_resend( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) +{ + CommsCtxt* comms = globals->cGlobals.game.comms; + if ( comms != NULL ) { + comms_resendAll( comms, XP_TRUE ); + } +} /* handle_resend */ + +#ifdef XWFEATURE_COMMSACK +static void +handle_ack( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) +{ + CommsCtxt* comms = globals->cGlobals.game.comms; + if ( comms != NULL ) { + comms_ackAny( comms ); + } +} +#endif + +#ifdef DEBUG +static void +handle_commstats( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) +{ + CommsCtxt* comms = globals->cGlobals.game.comms; + + if ( !!comms ) { + XWStreamCtxt* stream = + mem_stream_make( MEMPOOL + globals->cGlobals.params->vtMgr, + globals, + CHANNEL_NONE, catOnClose ); + comms_getStats( comms, stream ); + stream_destroy( stream ); + } +} /* handle_commstats */ +#endif +#endif + +#ifdef MEM_DEBUG +static void +handle_memstats( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) +{ + XWStreamCtxt* stream = mem_stream_make( MEMPOOL + globals->cGlobals.params->vtMgr, + globals, + CHANNEL_NONE, catOnClose ); + mpool_stats( MEMPOOL stream ); + stream_destroy( stream ); + +} /* handle_memstats */ +#endif + +static GtkWidget* +createAddItem( GtkWidget* parent, gchar* label, + GtkSignalFunc handlerFunc, GtkGameGlobals* globals ) +{ + GtkWidget* item = gtk_menu_item_new_with_label( label ); + +/* g_print( "createAddItem called with label %s\n", label ); */ + + if ( handlerFunc != NULL ) { + g_signal_connect( GTK_OBJECT(item), "activate", + G_CALLBACK(handlerFunc), globals ); + } + + gtk_menu_append( GTK_MENU(parent), item ); + gtk_widget_show( item ); + + return item; +} /* createAddItem */ + +static GtkWidget* +makeMenus( GtkGameGlobals* globals ) +{ + GtkWidget* menubar = gtk_menu_bar_new(); + GtkWidget* fileMenu; + + fileMenu = makeAddSubmenu( menubar, "File" ); + (void)createAddItem( fileMenu, "Tile values", + GTK_SIGNAL_FUNC(tile_values), globals ); + (void)createAddItem( fileMenu, "Game history", + GTK_SIGNAL_FUNC(game_history), globals ); +#ifdef TEXT_MODEL + (void)createAddItem( fileMenu, "Dump board", + GTK_SIGNAL_FUNC(dump_board), globals ); +#endif + + (void)createAddItem( fileMenu, "Final scores", + GTK_SIGNAL_FUNC(final_scores), globals ); + + (void)createAddItem( fileMenu, "New game", + GTK_SIGNAL_FUNC(new_game), globals ); + (void)createAddItem( fileMenu, "Game info", + GTK_SIGNAL_FUNC(game_info), globals ); + + (void)createAddItem( fileMenu, "Load game", + GTK_SIGNAL_FUNC(load_game), globals ); + (void)createAddItem( fileMenu, "Save game", + GTK_SIGNAL_FUNC(save_game), globals ); +#ifdef XWFEATURE_CHANGEDICT + (void)createAddItem( fileMenu, "Change dictionary", + GTK_SIGNAL_FUNC(change_dictionary), globals ); +#endif + (void)createAddItem( fileMenu, "Cancel trade", + GTK_SIGNAL_FUNC(handle_trade_cancel), globals ); + + fileMenu = makeAddSubmenu( menubar, "Edit" ); + + (void)createAddItem( fileMenu, "Undo", + GTK_SIGNAL_FUNC(handle_undo), globals ); + (void)createAddItem( fileMenu, "Redo", + GTK_SIGNAL_FUNC(handle_redo), globals ); + +#ifdef FEATURE_TRAY_EDIT + (void)createAddItem( fileMenu, "Allow tray edit", + GTK_SIGNAL_FUNC(handle_trayEditToggle_on), globals ); + (void)createAddItem( fileMenu, "Dis-allow tray edit", + GTK_SIGNAL_FUNC(handle_trayEditToggle_off), globals ); +#endif + fileMenu = makeAddSubmenu( menubar, "Network" ); + +#ifndef XWFEATURE_STANDALONE_ONLY + (void)createAddItem( fileMenu, "Resend", + GTK_SIGNAL_FUNC(handle_resend), globals ); +#ifdef XWFEATURE_COMMSACK + (void)createAddItem( fileMenu, "ack any", + GTK_SIGNAL_FUNC(handle_ack), globals ); +#endif +# ifdef DEBUG + (void)createAddItem( fileMenu, "Stats", + GTK_SIGNAL_FUNC(handle_commstats), globals ); +# endif +#endif +#ifdef MEM_DEBUG + (void)createAddItem( fileMenu, "Mem stats", + GTK_SIGNAL_FUNC(handle_memstats), globals ); +#endif + + /* (void)createAddItem( fileMenu, "Print board", */ + /* GTK_SIGNAL_FUNC(handle_print_board), globals ); */ + + /* listAllGames( menubar, argc, argv, globals ); */ + + gtk_widget_show( menubar ); + + return menubar; +} /* makeMenus */ + +static void +disenable_buttons( GtkGameGlobals* globals ) +{ + XP_Bool canFlip = 1 < board_visTileCount( globals->cGlobals.game.board ); + gtk_widget_set_sensitive( globals->flip_button, canFlip ); + + XP_Bool canToggle = board_canTogglePending( globals->cGlobals.game.board ); + gtk_widget_set_sensitive( globals->toggle_undo_button, canToggle ); + + XP_Bool canHint = board_canHint( globals->cGlobals.game.board ); + gtk_widget_set_sensitive( globals->prevhint_button, canHint ); + gtk_widget_set_sensitive( globals->nexthint_button, canHint ); + +#ifdef XWFEATURE_CHAT + XP_Bool canChat = !!globals->cGlobals.game.comms + && comms_canChat( globals->cGlobals.game.comms ); + gtk_widget_set_sensitive( globals->chat_button, canChat ); +#endif +} + +static gboolean +handle_flip_button( GtkWidget* XP_UNUSED(widget), gpointer _globals ) +{ + GtkGameGlobals* globals = (GtkGameGlobals*)_globals; + if ( board_flip( globals->cGlobals.game.board ) ) { + board_draw( globals->cGlobals.game.board ); + } + return TRUE; +} /* handle_flip_button */ + +static gboolean +handle_value_button( GtkWidget* XP_UNUSED(widget), gpointer closure ) +{ + GtkGameGlobals* globals = (GtkGameGlobals*)closure; + if ( board_toggle_showValues( globals->cGlobals.game.board ) ) { + board_draw( globals->cGlobals.game.board ); + } + return TRUE; +} /* handle_value_button */ + +static void +handle_hint_button( GtkGameGlobals* globals, XP_Bool prev ) +{ + XP_Bool redo; + if ( board_requestHint( globals->cGlobals.game.board, +#ifdef XWFEATURE_SEARCHLIMIT + XP_FALSE, +#endif + prev, &redo ) ) { + board_draw( globals->cGlobals.game.board ); + disenable_buttons( globals ); + } +} /* handle_hint_button */ + +static void +handle_prevhint_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) +{ + handle_hint_button( globals, XP_TRUE ); +} /* handle_prevhint_button */ + +static void +handle_nexthint_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) +{ + handle_hint_button( globals, XP_FALSE ); +} /* handle_nexthint_button */ + +static void +handle_nhint_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) +{ + XP_Bool redo; + + board_resetEngine( globals->cGlobals.game.board ); + if ( board_requestHint( globals->cGlobals.game.board, +#ifdef XWFEATURE_SEARCHLIMIT + XP_TRUE, +#endif + XP_FALSE, &redo ) ) { + board_draw( globals->cGlobals.game.board ); + } +} /* handle_nhint_button */ + +static void +handle_colors_button( GtkWidget* XP_UNUSED(widget), + GtkGameGlobals* XP_UNUSED(globals) ) +{ +/* XP_Bool oldVal = board_getShowColors( globals->cGlobals.game.board ); */ +/* if ( board_setShowColors( globals->cGlobals.game.board, !oldVal ) ) { */ +/* board_draw( globals->cGlobals.game.board ); */ +/* } */ +} /* handle_colors_button */ + +static void +handle_juggle_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) +{ + if ( board_juggleTray( globals->cGlobals.game.board ) ) { + board_draw( globals->cGlobals.game.board ); + } +} /* handle_juggle_button */ + +static void +handle_undo_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) +{ + if ( server_handleUndo( globals->cGlobals.game.server, 0 ) ) { + board_draw( globals->cGlobals.game.board ); + } +} /* handle_undo_button */ + +static void +handle_redo_button( GtkWidget* XP_UNUSED(widget), + GtkGameGlobals* XP_UNUSED(globals) ) +{ +} /* handle_redo_button */ + +static void +handle_toggle_undo( GtkWidget* XP_UNUSED(widget), + GtkGameGlobals* globals ) +{ + BoardCtxt* board = globals->cGlobals.game.board; + if ( board_redoReplacedTiles( board ) || board_replaceTiles( board ) ) { + board_draw( board ); + } +} + +static void +handle_trade_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) +{ + if ( board_beginTrade( globals->cGlobals.game.board ) ) { + board_draw( globals->cGlobals.game.board ); + disenable_buttons( globals ); + } +} /* handle_juggle_button */ + +static void +handle_done_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) +{ + if ( board_commitTurn( globals->cGlobals.game.board ) ) { + board_draw( globals->cGlobals.game.board ); + disenable_buttons( globals ); + } +} /* handle_done_button */ + +static void +setZoomButtons( GtkGameGlobals* globals, XP_Bool* inOut ) +{ + gtk_widget_set_sensitive( globals->zoomin_button, inOut[0] ); + gtk_widget_set_sensitive( globals->zoomout_button, inOut[1] ); +} + +static void +handle_zoomin_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) +{ + XP_Bool inOut[2]; + if ( board_zoom( globals->cGlobals.game.board, 1, inOut ) ) { + board_draw( globals->cGlobals.game.board ); + setZoomButtons( globals, inOut ); + } +} /* handle_done_button */ + +static void +handle_zoomout_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) +{ + XP_Bool inOut[2]; + if ( board_zoom( globals->cGlobals.game.board, -1, inOut ) ) { + board_draw( globals->cGlobals.game.board ); + setZoomButtons( globals, inOut ); + } +} /* handle_done_button */ + +#ifdef XWFEATURE_CHAT +static void +handle_chat_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) +{ + gchar* msg = gtkGetChatMessage( globals ); + if ( NULL != msg ) { + server_sendChat( globals->cGlobals.game.server, msg ); + g_free( msg ); + } +} +#endif + +static void +scroll_value_changed( GtkAdjustment *adj, GtkGameGlobals* globals ) +{ + XP_U16 newValue; + gfloat newValueF = adj->value; + + /* XP_ASSERT( newValueF >= 0.0 */ + /* && newValueF <= globals->cGlobals.params->nHidden ); */ + newValue = (XP_U16)newValueF; + + if ( board_setYOffset( globals->cGlobals.game.board, newValue ) ) { + board_draw( globals->cGlobals.game.board ); + } +} /* scroll_value_changed */ + +static void +handle_grid_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) +{ + globals->gridOn = !globals->gridOn; + + board_invalAll( globals->cGlobals.game.board ); + board_draw( globals->cGlobals.game.board ); +} /* handle_grid_button */ + +static void +handle_hide_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) +{ + BoardCtxt* board; + XP_Bool draw = XP_FALSE; + + if ( globals->cGlobals.params->nHidden > 0 ) { + gint nRows = globals->cGlobals.gi->boardSize; + globals->adjustment->page_size = nRows; + globals->adjustment->value = 0.0; + + gtk_signal_emit_by_name( GTK_OBJECT(globals->adjustment), "changed" ); + gtk_adjustment_value_changed( GTK_ADJUSTMENT(globals->adjustment) ); + } + + board = globals->cGlobals.game.board; + if ( TRAY_REVEALED == board_getTrayVisState( board ) ) { + draw = board_hideTray( board ); + } else { + draw = board_showTray( board ); + } + if ( draw ) { + board_draw( board ); + } +} /* handle_hide_button */ + +static void +handle_commit_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals ) +{ + if ( board_commitTurn( globals->cGlobals.game.board ) ) { + board_draw( globals->cGlobals.game.board ); + } +} /* handle_commit_button */ + +static void +gtkUserError( GtkGameGlobals* globals, const char* format, ... ) +{ + char buf[512]; + va_list ap; + + va_start( ap, format ); + + vsprintf( buf, format, ap ); + + (void)gtkask( globals->window, buf, GTK_BUTTONS_OK ); + + va_end(ap); +} /* gtkUserError */ + +static VTableMgr* +gtk_util_getVTManager(XW_UtilCtxt* uc) +{ + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; + return globals->cGlobals.params->vtMgr; +} /* linux_util_getVTManager */ + +static XP_S16 +gtk_util_userPickTileBlank( XW_UtilCtxt* uc, XP_U16 playerNum, + const XP_UCHAR** texts, XP_U16 nTiles ) +{ + XP_S16 chosen; + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; + XP_UCHAR* name = globals->cGlobals.gi->players[playerNum].name; + + chosen = gtkletterask( NULL, XP_FALSE, name, nTiles, texts ); + return chosen; +} + +static XP_S16 +gtk_util_userPickTileTray( XW_UtilCtxt* uc, const PickInfo* pi, + XP_U16 playerNum, const XP_UCHAR** texts, + XP_U16 nTiles ) +{ + XP_S16 chosen; + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; + XP_UCHAR* name = globals->cGlobals.gi->players[playerNum].name; + + chosen = gtkletterask( pi, XP_TRUE, name, nTiles, texts ); + return chosen; +} /* gtk_util_userPickTile */ + +static XP_Bool +gtk_util_askPassword( XW_UtilCtxt* XP_UNUSED(uc), const XP_UCHAR* name, + XP_UCHAR* buf, XP_U16* len ) +{ + XP_Bool ok = gtkpasswdask( name, buf, len ); + return ok; +} /* gtk_util_askPassword */ + +static void +setCtrlsForTray( GtkGameGlobals* XP_UNUSED(globals) ) +{ +#if 0 + XW_TrayVisState state = + board_getTrayVisState( globals->cGlobals.game.board ); + XP_S16 nHidden = globals->cGlobals.params->nHidden; + + if ( nHidden != 0 ) { + XP_U16 pageSize = nRows; + + if ( state == TRAY_HIDDEN ) { /* we recover what tray covers */ + nHidden -= GTK_TRAY_HT_ROWS; + } + if ( nHidden > 0 ) { + pageSize -= nHidden; + } + globals->adjustment->page_size = pageSize; + + globals->adjustment->value = + board_getYOffset( globals->cGlobals.game.board ); + gtk_signal_emit_by_name( GTK_OBJECT(globals->adjustment), "changed" ); + } +#endif +} /* setCtrlsForTray */ + +static void +gtk_util_trayHiddenChange( XW_UtilCtxt* uc, XW_TrayVisState XP_UNUSED(state), + XP_U16 XP_UNUSED(nVisibleRows) ) +{ + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; + setCtrlsForTray( globals ); +} /* gtk_util_trayHiddenChange */ + +static void +gtk_util_yOffsetChange( XW_UtilCtxt* uc, XP_U16 maxOffset, + XP_U16 XP_UNUSED(oldOffset), + XP_U16 newOffset ) +{ + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; + if ( !!globals->adjustment ) { + gint nRows = globals->cGlobals.gi->boardSize; + globals->adjustment->page_size = nRows - maxOffset; + globals->adjustment->value = newOffset; + gtk_adjustment_value_changed( GTK_ADJUSTMENT(globals->adjustment) ); + } +} /* gtk_util_yOffsetChange */ + +static void +gtkShowFinalScores( const GtkGameGlobals* globals ) +{ + XWStreamCtxt* stream; + XP_UCHAR* text; + const CommonGlobals* cGlobals = &globals->cGlobals; + + stream = mem_stream_make( MPPARM(cGlobals->util->mpool) + cGlobals->params->vtMgr, + NULL, CHANNEL_NONE, NULL ); + server_writeFinalScores( cGlobals->game.server, stream ); + + text = strFromStream( stream ); + stream_destroy( stream ); + + XP_U16 timeout = cGlobals->manualFinal? 0 : 500; + (void)gtkask_timeout( globals->window, text, GTK_BUTTONS_OK, timeout ); + + free( text ); +} /* gtkShowFinalScores */ + +static void +gtk_util_informMove( XW_UtilCtxt* uc, XWStreamCtxt* expl, + XWStreamCtxt* words ) +{ + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; + char* question = strFromStream( !!words? words : expl ); + (void)gtkask( globals->window, question, GTK_BUTTONS_OK ); + free( question ); +} + +static void +gtk_util_informUndo( XW_UtilCtxt* uc ) +{ + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; + (void)gtkask_timeout( globals->window, "Remote player undid a move", + GTK_BUTTONS_OK, 500 ); +} + +static void +gtk_util_notifyGameOver( XW_UtilCtxt* uc, XP_S16 quitter ) +{ + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; + CommonGlobals* cGlobals = &globals->cGlobals; + + if ( cGlobals->params->printHistory ) { + catGameHistory( cGlobals ); + } + + catFinalScores( cGlobals, quitter ); + + if ( cGlobals->params->quitAfter >= 0 ) { + sleep( cGlobals->params->quitAfter ); + destroy_board_window( NULL, globals ); + } else if ( cGlobals->params->undoWhenDone ) { + server_handleUndo( cGlobals->game.server, 0 ); + board_draw( cGlobals->game.board ); + } else if ( !cGlobals->params->skipGameOver ) { + gtkShowFinalScores( globals ); + } +} /* gtk_util_notifyGameOver */ + +static void +gtk_util_informNetDict( XW_UtilCtxt* uc, XP_LangCode XP_UNUSED(lang), + const XP_UCHAR* oldName, + const XP_UCHAR* newName, const XP_UCHAR* newSum, + XWPhoniesChoice phoniesAction ) +{ + if ( 0 != strcmp( oldName, newName ) ) { + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; + gchar buf[512]; + int offset = snprintf( buf, VSIZE(buf), + "dict changing from %s to %s (sum=%s).", + oldName, newName, newSum ); + if ( PHONIES_DISALLOW == phoniesAction ) { + snprintf( &buf[offset], VSIZE(buf)-offset, "%s", + "\nPHONIES_DISALLOW is set so this may " + "lead to some surprises." ); + } + (void)gtkask( globals->window, buf, GTK_BUTTONS_OK ); + } +} + +static gint +changeRoles( gpointer data ) +{ + linuxChangeRoles( (CommonGlobals*)data ); + return 0; +} + +static void +gtk_util_setIsServer( XW_UtilCtxt* uc, XP_Bool isServer ) +{ + CommonGlobals* cGlobals = (CommonGlobals*)uc->closure; + linuxSetIsServer( cGlobals, isServer ); + + (void)g_idle_add( changeRoles, cGlobals ); +} + +/* define this to prevent user events during debugging from stopping the engine */ +/* #define DONT_ABORT_ENGINE */ + +#ifdef XWFEATURE_HILITECELL +static XP_Bool +gtk_util_hiliteCell( XW_UtilCtxt* uc, XP_U16 col, XP_U16 row ) +{ + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; +#ifndef DONT_ABORT_ENGINE + gboolean pending; +#endif + + board_hiliteCellAt( globals->cGlobals.game.board, col, row ); + if ( globals->cGlobals.params->sleepOnAnchor ) { + usleep( 10000 ); + } + +#ifdef DONT_ABORT_ENGINE + return XP_TRUE; /* keep going */ +#else + pending = gdk_events_pending(); + if ( pending ) { + XP_DEBUGF( "gtk_util_hiliteCell=>%d", pending ); + } + return !pending; +#endif +} /* gtk_util_hiliteCell */ +#endif + +static XP_Bool +gtk_util_altKeyDown( XW_UtilCtxt* uc ) +{ + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; + return globals->altKeyDown; +} + +static XP_Bool +gtk_util_engineProgressCallback( XW_UtilCtxt* XP_UNUSED(uc) ) +{ +#ifdef DONT_ABORT_ENGINE + return XP_TRUE; /* keep going */ +#else + gboolean pending = gdk_events_pending(); + +/* XP_DEBUGF( "gdk_events_pending returned %d\n", pending ); */ + + return !pending; +#endif +} /* gtk_util_engineProgressCallback */ + +static void +cancelTimer( GtkGameGlobals* globals, XWTimerReason why ) +{ + guint src = globals->timerSources[why-1]; + if ( src != 0 ) { + g_source_remove( src ); + globals->timerSources[why-1] = 0; + } +} /* cancelTimer */ + +static gint +pen_timer_func( gpointer data ) +{ + GtkGameGlobals* globals = (GtkGameGlobals*)data; + + if ( linuxFireTimer( &globals->cGlobals, TIMER_PENDOWN ) ) { + board_draw( globals->cGlobals.game.board ); + } + + return XP_FALSE; +} /* pen_timer_func */ + +static gint +score_timer_func( gpointer data ) +{ + GtkGameGlobals* globals = (GtkGameGlobals*)data; + + if ( linuxFireTimer( &globals->cGlobals, TIMER_TIMERTICK ) ) { + board_draw( globals->cGlobals.game.board ); + } + + return XP_FALSE; +} /* score_timer_func */ + +#ifndef XWFEATURE_STANDALONE_ONLY +static gint +comms_timer_func( gpointer data ) +{ + GtkGameGlobals* globals = (GtkGameGlobals*)data; + + if ( linuxFireTimer( &globals->cGlobals, TIMER_COMMS ) ) { + board_draw( globals->cGlobals.game.board ); + } + + return (gint)0; +} +#endif + +#ifdef XWFEATURE_SLOW_ROBOT +static gint +slowrob_timer_func( gpointer data ) +{ + GtkGameGlobals* globals = (GtkGameGlobals*)data; + + if ( linuxFireTimer( &globals->cGlobals, TIMER_SLOWROBOT ) ) { + board_draw( globals->cGlobals.game.board ); + } + + return (gint)0; +} +#endif + +static void +gtk_util_setTimer( XW_UtilCtxt* uc, XWTimerReason why, + XP_U16 XP_UNUSED_STANDALONE(when), + XWTimerProc proc, void* closure ) +{ + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; + guint newSrc; + + cancelTimer( globals, why ); + + if ( why == TIMER_PENDOWN ) { + if ( 0 != globals->timerSources[why-1] ) { + g_source_remove( globals->timerSources[why-1] ); + } + newSrc = g_timeout_add( 1000, pen_timer_func, globals ); + } else if ( why == TIMER_TIMERTICK ) { + /* one second */ + globals->scoreTimerInterval = 100 * 10000; + + (void)gettimeofday( &globals->scoreTv, NULL ); + + newSrc = g_timeout_add( 1000, score_timer_func, globals ); +#ifndef XWFEATURE_STANDALONE_ONLY + } else if ( why == TIMER_COMMS ) { + newSrc = g_timeout_add( 1000 * when, comms_timer_func, globals ); +#endif +#ifdef XWFEATURE_SLOW_ROBOT + } else if ( why == TIMER_SLOWROBOT ) { + newSrc = g_timeout_add( 1000 * when, slowrob_timer_func, globals ); +#endif + } else { + XP_ASSERT( 0 ); + } + + globals->cGlobals.timerInfo[why].proc = proc; + globals->cGlobals.timerInfo[why].closure = closure; + XP_ASSERT( newSrc != 0 ); + globals->timerSources[why-1] = newSrc; +} /* gtk_util_setTimer */ + +static void +gtk_util_clearTimer( XW_UtilCtxt* uc, XWTimerReason why ) +{ + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; + globals->cGlobals.timerInfo[why].proc = NULL; +} + +static gint +idle_func( gpointer data ) +{ + GtkGameGlobals* globals = (GtkGameGlobals*)data; +/* XP_DEBUGF( "idle_func called\n" ); */ + + /* remove before calling server_do. If server_do puts up a dialog that + calls gtk_main, then this idle proc will also apply to that event loop + and bad things can happen. So kill the idle proc asap. */ + g_source_remove( globals->idleID ); + + ServerCtxt* server = globals->cGlobals.game.server; + if ( !!server && server_do( server ) ) { + if ( !!globals->cGlobals.game.board ) { + board_draw( globals->cGlobals.game.board ); + } + } + return 0; /* 0 will stop it from being called again */ +} /* idle_func */ + +static void +gtk_util_requestTime( XW_UtilCtxt* uc ) +{ + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; + globals->idleID = g_idle_add( idle_func, globals ); +} /* gtk_util_requestTime */ + +static XP_Bool +gtk_util_warnIllegalWord( XW_UtilCtxt* uc, BadWordInfo* bwi, XP_U16 player, + XP_Bool turnLost ) +{ + XP_Bool result; + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; + char buf[300]; + + if ( turnLost ) { + char wordsBuf[256]; + XP_U16 i; + XP_UCHAR* name = globals->cGlobals.gi->players[player].name; + XP_ASSERT( !!name ); + + for ( i = 0, wordsBuf[0] = '\0'; ; ) { + char wordBuf[18]; + sprintf( wordBuf, "\"%s\"", bwi->words[i] ); + strcat( wordsBuf, wordBuf ); + if ( ++i == bwi->nWords ) { + break; + } + strcat( wordsBuf, ", " ); + } + + sprintf( buf, "Player %d (%s) played illegal word[s] %s; loses turn.", + player+1, name, wordsBuf ); + + if ( globals->cGlobals.params->skipWarnings ) { + XP_LOGF( "%s", buf ); + } else { + gtkUserError( globals, buf ); + } + result = XP_TRUE; + } else { + XP_ASSERT( bwi->nWords == 1 ); + sprintf( buf, "Word \"%s\" not in the current dictionary (%s). " + "Use it anyway?", bwi->words[0], bwi->dictName ); + result = gtkask( globals->window, buf, GTK_BUTTONS_YES_NO ); + } + + return result; +} /* gtk_util_warnIllegalWord */ + +static void +gtk_util_remSelected( XW_UtilCtxt* uc ) +{ + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; + XWStreamCtxt* stream; + XP_UCHAR* text; + + stream = mem_stream_make( MEMPOOL + globals->cGlobals.params->vtMgr, + globals, CHANNEL_NONE, NULL ); + board_formatRemainingTiles( globals->cGlobals.game.board, stream ); + text = strFromStream( stream ); + stream_destroy( stream ); + + (void)gtkask( globals->window, text, GTK_BUTTONS_OK ); + free( text ); +} + +#ifndef XWFEATURE_STANDALONE_ONLY +static XWStreamCtxt* +gtk_util_makeStreamFromAddr(XW_UtilCtxt* uc, XP_PlayerAddr channelNo ) +{ + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; + + XWStreamCtxt* stream = mem_stream_make( MEMPOOL + globals->cGlobals.params->vtMgr, + &globals->cGlobals, channelNo, + sendOnClose ); + return stream; +} /* gtk_util_makeStreamFromAddr */ + +#ifdef XWFEATURE_CHAT +static void +gtk_util_showChat( XW_UtilCtxt* uc, const XP_UCHAR* const msg ) +{ + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; + (void)gtkask( globals->window, msg, GTK_BUTTONS_OK ); +} +#endif +#endif + +#ifdef XWFEATURE_SEARCHLIMIT +static XP_Bool +gtk_util_getTraySearchLimits( XW_UtilCtxt* XP_UNUSED(uc), + XP_U16* XP_UNUSED(min), XP_U16* max ) +{ + *max = askNTiles( MAX_TRAY_TILES, *max ); + return XP_TRUE; +} +#endif + +#ifndef XWFEATURE_MINIWIN +static void +gtk_util_bonusSquareHeld( XW_UtilCtxt* uc, XWBonusType bonus ) +{ + LOG_FUNC(); + XP_USE( uc ); + XP_USE( bonus ); +} + +static void +gtk_util_playerScoreHeld( XW_UtilCtxt* uc, XP_U16 player ) +{ + LOG_FUNC(); + + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; + + XP_UCHAR scoreExpl[48] = {0}; + XP_U16 explLen = sizeof(scoreExpl); + + if ( model_getPlayersLastScore( globals->cGlobals.game.model, + player, scoreExpl, &explLen ) ) { + XP_LOGF( "got: %s", scoreExpl ); + } +} +#endif + +#ifdef XWFEATURE_BOARDWORDS +static void +gtk_util_cellSquareHeld( XW_UtilCtxt* uc, XWStreamCtxt* words ) +{ + XP_USE( uc ); + catOnClose( words, NULL ); + fprintf( stderr, "\n" ); +} +#endif + +static void +gtk_util_userError( XW_UtilCtxt* uc, UtilErrID id ) +{ + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; + XP_Bool silent; + const XP_UCHAR* message = linux_getErrString( id, &silent ); + + XP_LOGF( "%s(%d)", __func__, id ); + + if ( silent ) { + XP_LOGF( "%s", message ); + } else { + gtkUserError( globals, message ); + } +} /* gtk_util_userError */ + +static XP_Bool +gtk_util_userQuery( XW_UtilCtxt* uc, UtilQueryID id, + XWStreamCtxt* stream ) +{ + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; + XP_Bool result; + char* question; + XP_Bool freeMe = XP_FALSE; + GtkButtonsType buttons = GTK_BUTTONS_YES_NO; + + switch( id ) { + + case QUERY_COMMIT_TURN: + question = strFromStream( stream ); + freeMe = XP_TRUE; + break; + case QUERY_ROBOT_TRADE: + question = strFromStream( stream ); + freeMe = XP_TRUE; + buttons = GTK_BUTTONS_OK; + break; + + default: + XP_ASSERT( 0 ); + return XP_FALSE; + } + + result = gtkask( globals->window, question, buttons ); + + if ( freeMe ) { + free( question ); + } + + return result; +} /* gtk_util_userQuery */ + +static XP_Bool +gtk_util_confirmTrade( XW_UtilCtxt* uc, + const XP_UCHAR** tiles, XP_U16 nTiles ) +{ + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; + char question[256]; + formatConfirmTrade( tiles, nTiles, question, sizeof(question) ); + return gtkask( globals->window, question, GTK_BUTTONS_YES_NO ); +} + +static GtkWidget* +makeShowButtonFromBitmap( void* closure, const gchar* filename, + const gchar* alt, GCallback func ) +{ + GtkWidget* widget; + GtkWidget* button; + + if ( file_exists( filename ) ) { + widget = gtk_image_new_from_file( filename ); + } else { + widget = gtk_label_new( alt ); + } + gtk_widget_show( widget ); + + button = gtk_button_new(); + gtk_container_add (GTK_CONTAINER (button), widget ); + gtk_widget_show (button); + + if ( func != NULL ) { + g_signal_connect( GTK_OBJECT(button), "clicked", func, closure ); + } + + return button; +} /* makeShowButtonFromBitmap */ + +static GtkWidget* +makeVerticalBar( GtkGameGlobals* globals, GtkWidget* XP_UNUSED(window) ) +{ + GtkWidget* vbox; + GtkWidget* button; + + vbox = gtk_vbutton_box_new(); + + button = makeShowButtonFromBitmap( globals, "../flip.xpm", "f", + G_CALLBACK(handle_flip_button) ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + globals->flip_button = button; + + button = makeShowButtonFromBitmap( globals, "../value.xpm", "v", + G_CALLBACK(handle_value_button) ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + + button = makeShowButtonFromBitmap( globals, "../hint.xpm", "?-", + G_CALLBACK(handle_prevhint_button) ); + globals->prevhint_button = button; + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + button = makeShowButtonFromBitmap( globals, "../hint.xpm", "?+", + G_CALLBACK(handle_nexthint_button) ); + globals->nexthint_button = button; + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + + button = makeShowButtonFromBitmap( globals, "../hintNum.xpm", "n", + G_CALLBACK(handle_nhint_button) ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + + button = makeShowButtonFromBitmap( globals, "../colors.xpm", "c", + G_CALLBACK(handle_colors_button) ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + + /* undo and redo buttons */ + button = makeShowButtonFromBitmap( globals, "../undo.xpm", "U", + G_CALLBACK(handle_undo_button) ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + button = makeShowButtonFromBitmap( globals, "../redo.xpm", "R", + G_CALLBACK(handle_redo_button) ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + + button = makeShowButtonFromBitmap( globals, "", "u/r", + G_CALLBACK(handle_toggle_undo) ); + globals->toggle_undo_button = button; + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + + /* the four buttons that on palm are beside the tray */ + button = makeShowButtonFromBitmap( globals, "../juggle.xpm", "j", + G_CALLBACK(handle_juggle_button) ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + + button = makeShowButtonFromBitmap( globals, "../trade.xpm", "t", + G_CALLBACK(handle_trade_button) ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + button = makeShowButtonFromBitmap( globals, "../done.xpm", "d", + G_CALLBACK(handle_done_button) ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + button = makeShowButtonFromBitmap( globals, "../done.xpm", "+", + G_CALLBACK(handle_zoomin_button) ); + globals->zoomin_button = button; + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + button = makeShowButtonFromBitmap( globals, "../done.xpm", "-", + G_CALLBACK(handle_zoomout_button) ); + globals->zoomout_button = button; + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); +#ifdef XWFEATURE_CHAT + button = makeShowButtonFromBitmap( globals, "", "chat", + G_CALLBACK(handle_chat_button) ); + globals->chat_button = button; + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); +#endif + + gtk_widget_show( vbox ); + return vbox; +} /* makeVerticalBar */ + +static GtkWidget* +makeButtons( GtkGameGlobals* globals ) +{ + short i; + GtkWidget* hbox; + GtkWidget* button; + + struct { + char* name; + GCallback func; + } buttons[] = { + /* { "Flip", handle_flip_button }, */ + { "Grid", G_CALLBACK(handle_grid_button) }, + { "Hide", G_CALLBACK(handle_hide_button) }, + { "Commit", G_CALLBACK(handle_commit_button) }, + }; + + hbox = gtk_hbox_new( FALSE, 0 ); + + for ( i = 0; i < sizeof(buttons)/sizeof(*buttons); ++i ) { + button = gtk_button_new_with_label( buttons[i].name ); + gtk_widget_show( button ); + g_signal_connect( GTK_OBJECT(button), "clicked", + G_CALLBACK(buttons[i].func), globals ); + + gtk_box_pack_start( GTK_BOX(hbox), button, FALSE, TRUE, 0); + } + + gtk_widget_show( hbox ); + return hbox; +} /* makeButtons */ + +static void +setupGtkUtilCallbacks( GtkGameGlobals* globals, XW_UtilCtxt* util ) +{ + util->vtable->m_util_userError = gtk_util_userError; + util->vtable->m_util_userQuery = gtk_util_userQuery; + util->vtable->m_util_confirmTrade = gtk_util_confirmTrade; + util->vtable->m_util_getVTManager = gtk_util_getVTManager; + util->vtable->m_util_userPickTileBlank = gtk_util_userPickTileBlank; + util->vtable->m_util_userPickTileTray = gtk_util_userPickTileTray; + util->vtable->m_util_askPassword = gtk_util_askPassword; + util->vtable->m_util_trayHiddenChange = gtk_util_trayHiddenChange; + util->vtable->m_util_yOffsetChange = gtk_util_yOffsetChange; + util->vtable->m_util_informMove = gtk_util_informMove; + util->vtable->m_util_informUndo = gtk_util_informUndo; + util->vtable->m_util_notifyGameOver = gtk_util_notifyGameOver; + util->vtable->m_util_informNetDict = gtk_util_informNetDict; + util->vtable->m_util_setIsServer = gtk_util_setIsServer; +#ifdef XWFEATURE_HILITECELL + util->vtable->m_util_hiliteCell = gtk_util_hiliteCell; +#endif + util->vtable->m_util_altKeyDown = gtk_util_altKeyDown; + util->vtable->m_util_engineProgressCallback = + gtk_util_engineProgressCallback; + util->vtable->m_util_setTimer = gtk_util_setTimer; + util->vtable->m_util_clearTimer = gtk_util_clearTimer; + util->vtable->m_util_requestTime = gtk_util_requestTime; + util->vtable->m_util_warnIllegalWord = gtk_util_warnIllegalWord; + util->vtable->m_util_remSelected = gtk_util_remSelected; +#ifndef XWFEATURE_STANDALONE_ONLY + util->vtable->m_util_makeStreamFromAddr = gtk_util_makeStreamFromAddr; +#endif +#ifdef XWFEATURE_CHAT + util->vtable->m_util_showChat = gtk_util_showChat; +#endif +#ifdef XWFEATURE_SEARCHLIMIT + util->vtable->m_util_getTraySearchLimits = gtk_util_getTraySearchLimits; +#endif + +#ifndef XWFEATURE_MINIWIN + util->vtable->m_util_bonusSquareHeld = gtk_util_bonusSquareHeld; + util->vtable->m_util_playerScoreHeld = gtk_util_playerScoreHeld; +#endif +#ifdef XWFEATURE_BOARDWORDS + util->vtable->m_util_cellSquareHeld = gtk_util_cellSquareHeld; +#endif + + util->closure = globals; +} /* setupGtkUtilCallbacks */ + +#ifndef XWFEATURE_STANDALONE_ONLY +static gboolean +newConnectionInput( GIOChannel *source, + GIOCondition condition, + gpointer data ) +{ + gboolean keepSource = TRUE; + int sock = g_io_channel_unix_get_fd( source ); + GtkGameGlobals* globals = (GtkGameGlobals*)data; + + XP_LOGF( "%s(%p):condition = 0x%x", __func__, source, (int)condition ); + +/* XP_ASSERT( sock == globals->cGlobals.socket ); */ + + if ( (condition & G_IO_IN) != 0 ) { + ssize_t nRead; + unsigned char buf[1024]; + CommsAddrRec* addrp = NULL; +#if defined XWFEATURE_IP_DIRECT || defined XWFEATURE_SMS + CommsAddrRec addr; +#endif + + switch ( comms_getConType( globals->cGlobals.game.comms ) ) { +#ifdef XWFEATURE_RELAY + case COMMS_CONN_RELAY: + XP_ASSERT( globals->cGlobals.socket == sock ); + nRead = linux_relay_receive( &globals->cGlobals, buf, sizeof(buf) ); + break; +#endif +#ifdef XWFEATURE_BLUETOOTH + case COMMS_CONN_BT: + nRead = linux_bt_receive( sock, buf, sizeof(buf) ); + break; +#endif +#ifdef XWFEATURE_SMS + case COMMS_CONN_SMS: + addrp = &addr; + nRead = linux_sms_receive( &globals->cGlobals, sock, + buf, sizeof(buf), addrp ); + break; +#endif +#ifdef XWFEATURE_IP_DIRECT + case COMMS_CONN_IP_DIRECT: + addrp = &addr; + nRead = linux_udp_receive( sock, buf, sizeof(buf), addrp, &globals->cGlobals ); + break; +#endif + default: + XP_ASSERT( 0 ); + } + + if ( !globals->dropIncommingMsgs && nRead > 0 ) { + XWStreamCtxt* inboundS; + XP_Bool redraw = XP_FALSE; + + inboundS = stream_from_msgbuf( &globals->cGlobals, buf, nRead ); + if ( !!inboundS ) { + XP_LOGF( "%s: got %d bytes", __func__, nRead ); + if ( comms_checkIncomingStream( globals->cGlobals.game.comms, + inboundS, addrp ) ) { + redraw = + server_receiveMessage(globals->cGlobals.game.server, + inboundS ); + if ( redraw ) { + saveGame( &globals->cGlobals ); + } + } + 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 ) { + gtk_util_requestTime( globals->cGlobals.util ); + } else { + redraw = server_do( globals->cGlobals.game.server ); + } + if ( redraw ) { + board_draw( globals->cGlobals.game.board ); + } + } else { + XP_LOGF( "errno from read: %d/%s", errno, strerror(errno) ); + } + } + + if ( (condition & (G_IO_HUP | G_IO_ERR)) != 0 ) { + XP_LOGF( "dropping socket %d", sock ); + close( sock ); +#ifdef XWFEATURE_RELAY + globals->cGlobals.socket = -1; +#endif + if ( 0 ) { +#ifdef XWFEATURE_BLUETOOTH + } else if ( COMMS_CONN_BT == globals->cGlobals.params->conType ) { + linux_bt_socketclosed( &globals->cGlobals, sock ); +#endif +#ifdef XWFEATURE_IP_DIRECT + } else if ( COMMS_CONN_IP_DIRECT == globals->cGlobals.params->conType ) { + linux_udp_socketclosed( &globals->cGlobals, sock ); +#endif + } + keepSource = FALSE; /* remove the event source */ + } + + return keepSource; /* FALSE means to remove event source */ +} /* newConnectionInput */ + +typedef struct _SockInfo { + GIOChannel* channel; + guint watch; + int socket; +} SockInfo; + +static void +gtk_socket_changed( void* closure, int oldSock, int newSock, void** storage ) +{ + GtkGameGlobals* globals = (GtkGameGlobals*)closure; + SockInfo* info = (SockInfo*)*storage; + XP_LOGF( "%s(old:%d; new:%d)", __func__, oldSock, newSock ); + + if ( oldSock != -1 ) { + XP_ASSERT( info != NULL ); + g_source_remove( info->watch ); + g_io_channel_unref( info->channel ); + XP_FREE( globals->cGlobals.util->mpool, info ); + *storage = NULL; + XP_LOGF( "Removed socket %d from gtk's list of listened-to sockets", + oldSock ); + } + if ( newSock != -1 ) { + info = (SockInfo*)XP_MALLOC( globals->cGlobals.util->mpool, + sizeof(*info) ); + GIOChannel* channel = g_io_channel_unix_new( newSock ); + g_io_channel_set_close_on_unref( channel, TRUE ); + guint result = g_io_add_watch( channel, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_PRI, + newConnectionInput, + globals ); + info->channel = channel; + info->watch = result; + *storage = info; + XP_LOGF( "g_io_add_watch(%d) => %d", newSock, result ); + } +#ifdef XWFEATURE_RELAY + globals->cGlobals.socket = newSock; +#endif + /* A hack for the bluetooth case. */ + CommsCtxt* comms = globals->cGlobals.game.comms; + if ( (comms != NULL) && (comms_getConType(comms) == COMMS_CONN_BT) ) { + comms_resendAll( comms, XP_FALSE ); + } + LOG_RETURN_VOID(); +} /* gtk_socket_changed */ + +static gboolean +acceptorInput( GIOChannel* source, GIOCondition condition, gpointer data ) +{ + gboolean keepSource; + CommonGlobals* globals = (CommonGlobals*)data; + LOG_FUNC(); + + if ( (condition & G_IO_IN) != 0 ) { + int listener = g_io_channel_unix_get_fd( source ); + XP_LOGF( "%s: input on socket %d", __func__, listener ); + keepSource = (*globals->acceptor)( listener, data ); + } else { + keepSource = FALSE; + } + + return keepSource; +} /* acceptorInput */ + +static void +gtk_socket_acceptor( int listener, Acceptor func, CommonGlobals* globals, + void** storage ) +{ + SockInfo* info = (SockInfo*)*storage; + GIOChannel* channel; + guint watch; + + LOG_FUNC(); + + if ( listener == -1 ) { + XP_ASSERT( !!globals->acceptor ); + globals->acceptor = NULL; + XP_ASSERT( !!info ); +#ifdef DEBUG + int oldSock = info->socket; +#endif + g_source_remove( info->watch ); + g_io_channel_unref( info->channel ); + XP_FREE( globals->util->mpool, info ); + *storage = NULL; + XP_LOGF( "Removed listener %d from gtk's list of listened-to sockets", oldSock ); + } else { + XP_ASSERT( !globals->acceptor || (func == globals->acceptor) ); + globals->acceptor = func; + + channel = g_io_channel_unix_new( listener ); + g_io_channel_set_close_on_unref( channel, TRUE ); + watch = g_io_add_watch( channel, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_PRI, + acceptorInput, globals ); + g_io_channel_unref( channel ); /* only main loop holds it now */ + XP_LOGF( "%s: g_io_add_watch(%d) => %d", __func__, listener, watch ); + + XP_ASSERT( NULL == info ); + info = XP_MALLOC( globals->util->mpool, sizeof(*info) ); + info->channel = channel; + info->watch = watch; + info->socket = listener; + *storage = info; + } +} /* gtk_socket_acceptor */ + +static void +drop_msg_toggle( GtkWidget* toggle, GtkGameGlobals* globals ) +{ + globals->dropIncommingMsgs = gtk_toggle_button_get_active( + GTK_TOGGLE_BUTTON(toggle) ); +} /* drop_msg_toggle */ +#endif + +/* int */ +/* board_main( LaunchParams* params ) */ +/* { */ +/* GtkGameGlobals globals; */ +/* initGlobals( &globals, params ); */ + +/* if ( !!params->pipe && !!params->fileName ) { */ +/* read_pipe_then_close( &globals.cGlobals, NULL ); */ +/* } else { */ +/* gtk_widget_show( globals.window ); */ + +/* gtk_main(); */ +/* } */ +/* /\* MONCONTROL(1); *\/ */ + +/* cleanup( &globals ); */ + +/* return 0; */ +/* } */ + +static void +initGlobalsNoDraw( GtkGameGlobals* globals, LaunchParams* params ) +{ + memset( globals, 0, sizeof(*globals) ); + + globals->cGlobals.gi = &globals->gi; + gi_copy( MPPARM(params->mpool) globals->cGlobals.gi, ¶ms->pgi ); + + globals->cGlobals.params = params; + globals->cGlobals.lastNTilesToUse = MAX_TRAY_TILES; +#ifndef XWFEATURE_STANDALONE_ONLY +# ifdef XWFEATURE_RELAY + globals->cGlobals.socket = -1; +# endif + + globals->cGlobals.socketChanged = gtk_socket_changed; + globals->cGlobals.socketChangedClosure = globals; + globals->cGlobals.onSave = onGameSaved; + globals->cGlobals.onSaveClosure = globals; + globals->cGlobals.addAcceptor = gtk_socket_acceptor; +#endif + + globals->cGlobals.cp.showBoardArrow = XP_TRUE; + globals->cGlobals.cp.hideTileValues = params->hideValues; + globals->cGlobals.cp.skipCommitConfirm = params->skipCommitConfirm; + globals->cGlobals.cp.sortNewTiles = params->sortNewTiles; + globals->cGlobals.cp.showColors = params->showColors; + globals->cGlobals.cp.allowPeek = params->allowPeek; + globals->cGlobals.cp.showRobotScores = params->showRobotScores; +#ifdef XWFEATURE_SLOW_ROBOT + globals->cGlobals.cp.robotThinkMin = params->robotThinkMin; + globals->cGlobals.cp.robotThinkMax = params->robotThinkMax; + globals->cGlobals.cp.robotTradePct = params->robotTradePct; +#endif +#ifdef XWFEATURE_CROSSHAIRS + globals->cGlobals.cp.hideCrosshairs = params->hideCrosshairs; +#endif + + setupUtil( &globals->cGlobals ); + setupGtkUtilCallbacks( globals, globals->cGlobals.util ); +} + +void +initGlobals( GtkGameGlobals* globals, LaunchParams* params ) +{ + short width, height; + GtkWidget* window; + GtkWidget* drawing_area; + GtkWidget* menubar; + GtkWidget* buttonbar; + GtkWidget* vbox; + GtkWidget* hbox; +#ifndef XWFEATURE_STANDALONE_ONLY + GtkWidget* dropCheck; +#endif + + initGlobalsNoDraw( globals, params ); + + globals->window = window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); + if ( !!params->fileName ) { + gtk_window_set_title( GTK_WINDOW(window), params->fileName ); + } + + vbox = gtk_vbox_new (FALSE, 0); + gtk_container_add( GTK_CONTAINER(window), vbox ); + gtk_widget_show( vbox ); + + g_signal_connect( G_OBJECT (window), "destroy", + G_CALLBACK( destroy_board_window ), globals ); + + menubar = makeMenus( globals ); + gtk_box_pack_start( GTK_BOX(vbox), menubar, FALSE, TRUE, 0); + +#ifndef XWFEATURE_STANDALONE_ONLY + dropCheck = gtk_check_button_new_with_label( "drop incoming messages" ); + g_signal_connect( GTK_OBJECT(dropCheck), + "toggled", G_CALLBACK(drop_msg_toggle), globals ); + gtk_box_pack_start( GTK_BOX(vbox), dropCheck, FALSE, TRUE, 0); + gtk_widget_show( dropCheck ); +#endif + + buttonbar = makeButtons( globals ); + gtk_box_pack_start( GTK_BOX(vbox), buttonbar, FALSE, TRUE, 0); + + drawing_area = gtk_drawing_area_new(); + globals->drawing_area = drawing_area; + gtk_widget_show( drawing_area ); + + width = GTK_HOR_SCORE_WIDTH + GTK_TIMER_WIDTH + GTK_TIMER_PAD; + if ( globals->cGlobals.params->verticalScore ) { + width += GTK_VERT_SCORE_WIDTH; + } + height = 196; + if ( globals->cGlobals.params->nHidden == 0 ) { + height += GTK_MIN_SCALE * GTK_TRAY_HT_ROWS; + } + + gtk_widget_set_size_request( GTK_WIDGET(drawing_area), width, height ); + + hbox = gtk_hbox_new( FALSE, 0 ); + gtk_box_pack_start( GTK_BOX (hbox), drawing_area, TRUE, TRUE, 0); + + /* install scrollbar even if not needed -- since zooming can make it + needed later */ + GtkWidget* vscrollbar; + gint nRows = globals->cGlobals.gi->boardSize; + globals->adjustment = (GtkAdjustment*) + gtk_adjustment_new( 0, 0, nRows, 1, 2, + nRows - globals->cGlobals.params->nHidden ); + vscrollbar = gtk_vscrollbar_new( globals->adjustment ); + g_signal_connect( GTK_OBJECT(globals->adjustment), "value_changed", + G_CALLBACK(scroll_value_changed), globals ); + gtk_widget_show( vscrollbar ); + gtk_box_pack_start( GTK_BOX(hbox), vscrollbar, TRUE, TRUE, 0 ); + + gtk_box_pack_start( GTK_BOX (hbox), + makeVerticalBar( globals, window ), + FALSE, TRUE, 0 ); + gtk_widget_show( hbox ); + + gtk_box_pack_start( GTK_BOX(vbox), hbox/* drawing_area */, TRUE, TRUE, 0); + + g_signal_connect( GTK_OBJECT(drawing_area), "expose_event", + G_CALLBACK(expose_event), globals ); + g_signal_connect( GTK_OBJECT(drawing_area),"configure_event", + G_CALLBACK(configure_event), globals ); + g_signal_connect( GTK_OBJECT(drawing_area), "button_press_event", + G_CALLBACK(button_press_event), globals ); + g_signal_connect( GTK_OBJECT(drawing_area), "motion_notify_event", + G_CALLBACK(motion_notify_event), globals ); + g_signal_connect( GTK_OBJECT(drawing_area), "button_release_event", + G_CALLBACK(button_release_event), globals ); + + setOneSecondTimer( &globals->cGlobals ); + +#ifdef KEY_SUPPORT +# ifdef KEYBOARD_NAV + g_signal_connect( GTK_OBJECT(window), "key_press_event", + G_CALLBACK(key_press_event), globals ); +# endif + g_signal_connect( GTK_OBJECT(window), "key_release_event", + G_CALLBACK(key_release_event), globals ); +#endif + + gtk_widget_set_events( drawing_area, GDK_EXPOSURE_MASK + | GDK_LEAVE_NOTIFY_MASK + | GDK_BUTTON_PRESS_MASK + | GDK_POINTER_MOTION_MASK + | GDK_BUTTON_RELEASE_MASK +#ifdef KEY_SUPPORT +# ifdef KEYBOARD_NAV + | GDK_KEY_PRESS_MASK +# endif + | GDK_KEY_RELEASE_MASK +#endif +/* | GDK_POINTER_MOTION_HINT_MASK */ + ); +} /* initGlobals */ + +void +freeGlobals( GtkGameGlobals* globals ) +{ + cleanup( globals ); +} + +XP_Bool +loadGameNoDraw( GtkGameGlobals* globals, LaunchParams* params, + sqlite3_int64 rowid ) +{ + LOG_FUNC(); + sqlite3* pDb = params->pDb; + initGlobalsNoDraw( globals, params ); + + TransportProcs procs; + setTransportProcs( &procs, globals ); + + CommonGlobals* cGlobals = &globals->cGlobals; + cGlobals->selRow = rowid; + cGlobals->pDb = pDb; + XWStreamCtxt* stream = mem_stream_make( MPPARM(cGlobals->util->mpool) + params->vtMgr, cGlobals, + CHANNEL_NONE, NULL ); + XP_Bool loaded = loadGame( stream, cGlobals->pDb, rowid ); + if ( loaded ) { + if ( NULL == cGlobals->dict ) { + cGlobals->dict = makeDictForStream( cGlobals, stream ); + } + loaded = game_makeFromStream( MEMPOOL stream, &cGlobals->game, + cGlobals->gi, cGlobals->dict, + &cGlobals->dicts, cGlobals->util, + (DrawCtx*)NULL, &cGlobals->cp, &procs ); + if ( loaded ) { + XP_LOGF( "%s: game loaded", __func__ ); +#ifndef XWFEATURE_STANDALONE_ONLY + if ( !!globals->cGlobals.game.comms ) { + comms_resendAll( globals->cGlobals.game.comms, XP_FALSE ); + } +#endif + } + } + stream_destroy( stream ); + return loaded; +} + +XP_Bool +makeNewGame( GtkGameGlobals* globals ) +{ + CommonGlobals* cGlobals = &globals->cGlobals; + if ( !!cGlobals->game.comms ) { + comms_getAddr( cGlobals->game.comms, &cGlobals->addr ); + } else { + LaunchParams* params = globals->cGlobals.params; + const XP_UCHAR* relayName = params->connInfo.relay.relayName; + if ( !relayName ) { + relayName = RELAY_NAME_DEFAULT; + } + XP_U16 relayPort = params->connInfo.relay.defaultSendPort; + if ( 0 == relayPort ) { + relayPort = RELAY_PORT_DEFAULT; + } + comms_getInitialAddr( &cGlobals->addr, relayName, relayPort ); + } + + CurGameInfo* gi = cGlobals->gi; + XP_Bool success = newGameDialog( globals, gi, &cGlobals->addr, + XP_TRUE, XP_FALSE ); + if ( success && !!gi->dictName && !cGlobals->dict ) { + cGlobals->dict = + linux_dictionary_make( MEMPOOL cGlobals->params, + gi->dictName, XP_TRUE ); + gi->dictLang = dict_getLangCode( cGlobals->dict ); + } + LOG_RETURNF( "%d", success ); + return success; +} + +#endif /* PLATFORM_GTK */ diff --git a/xwords4/linux/gtkboard.h b/xwords4/linux/gtkboard.h new file mode 100644 index 000000000..927cbb677 --- /dev/null +++ b/xwords4/linux/gtkboard.h @@ -0,0 +1,180 @@ +/* -*- mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* Copyright 1997 - 2005 by Eric House (xwords@eehouse.org) All rights + * reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _GTKBOARD_H_ +#define _GTKBOARD_H_ + +#ifdef PLATFORM_GTK +#include +#include +#include +#include + +#include "draw.h" +#include "main.h" +#include "game.h" +#include "dictnry.h" + +enum { + LAYOUT_BOARD + ,LAYOUT_SMALL + ,LAYOUT_LARGE + ,LAYOUT_NLAYOUTS +}; + +#define MAX_SCORE_LEN 31 + +typedef struct GtkDrawCtx { + DrawCtxVTable* vtable; + +/* GdkDrawable* pixmap; */ + GtkWidget* drawing_area; + struct GtkGameGlobals* globals; + +#ifdef USE_CAIRO + cairo_t* cr; +#else + GdkGC* drawGC; +#endif + + GdkColor black; + GdkColor white; + GdkColor grey; + GdkColor red; /* for pending tiles */ + GdkColor tileBack; /* for pending tiles */ + GdkColor cursor; + GdkColor bonusColors[4]; + GdkColor playerColors[MAX_NUM_PLAYERS]; + + /* new for gtk 2.0 */ + PangoContext* pangoContext; + GList* fontsPerSize; + + struct { + XP_UCHAR str[MAX_SCORE_LEN+1]; + XP_U16 fontHt; + } scoreCache[MAX_NUM_PLAYERS]; + + XP_U16 trayOwner; + XP_U16 cellWidth; + XP_U16 cellHeight; + + XP_Bool scoreIsVertical; +} GtkDrawCtx; + +typedef struct ClientStreamRec { + XWStreamCtxt* stream; + guint key; + int sock; +} ClientStreamRec; + +typedef struct GtkGameGlobals { + CommonGlobals cGlobals; + CurGameInfo gi; + GtkWidget* window; + GtkDrawCtx* draw; + GtkAppGlobals* apg; +/* GdkPixmap* pixmap; */ + GtkWidget* drawing_area; + + GtkWidget* flip_button; + GtkWidget* zoomin_button; + GtkWidget* zoomout_button; + GtkWidget* toggle_undo_button; + GtkWidget* prevhint_button; + GtkWidget* nexthint_button; + +#ifdef XWFEATURE_CHAT + GtkWidget* chat_button; +#endif + + EngineCtxt* engine; + + guint idleID; + + struct timeval scoreTv; /* for timer */ + XP_U32 scoreTimerInterval; + + GtkAdjustment* adjustment; + + ClientStreamRec clientRecs[MAX_NUM_PLAYERS]; + + guint timerSources[NUM_TIMERS_PLUS_ONE - 1]; + +#ifndef XWFEATURE_STANDALONE_ONLY + XP_U16 netStatLeft, netStatTop; + XP_UCHAR stateChar; +#endif + + XP_Bool gridOn; + XP_Bool dropIncommingMsgs; + XP_Bool mouseDown; + XP_Bool altKeyDown; +#ifdef KEYBOARD_NAV + XP_Bool keyDown; +#endif +} GtkGameGlobals; + +/* DictionaryCtxt* gtk_dictionary_make(); */ +#define GTK_MIN_SCALE 12 /* was 14 */ + +#define GTK_MIN_TRAY_SCALEH 24 +#define GTK_MIN_TRAY_SCALEV GTK_MIN_TRAY_SCALEH +#define GTK_TRAYPAD_WIDTH 2 + +#define GTK_TOP_MARGIN 0 /* was 2 */ +#define GTK_BOARD_LEFT_MARGIN 2 +#define GTK_TRAY_LEFT_MARGIN 2 +#define GTK_SCORE_BOARD_PADDING 0 + +#define GTK_HOR_SCORE_LEFT (GTK_BOARD_LEFT_MARGIN) +#define GTK_HOR_SCORE_HEIGHT 12 +#define GTK_TIMER_HEIGHT GTK_HOR_SCORE_HEIGHT +#define GTK_HOR_SCORE_TOP (GTK_TOP_MARGIN) +#define GTK_TIMER_PAD 10 +#define GTK_VERT_SCORE_TOP (GTK_TIMER_HEIGHT + GTK_TIMER_PAD) +#define GTK_VERT_SCORE_HEIGHT ((MIN_SCALE*MAX_COLS) - GTK_TIMER_HEIGHT - \ + GTK_TIMER_PAD) +#define GTK_TIMER_WIDTH 40 +#define GTK_NETSTAT_WIDTH 20 +#define GTK_TIMER_TOP GTK_HOR_SCORE_TOP +#define GTK_HOR_SCORE_WIDTH ((GTK_MIN_SCALE*20)-GTK_TIMER_PAD) +#define GTK_VERT_SCORE_WIDTH 40 + +#define GTK_BOARD_TOP (GTK_SCORE_TOP + GTK_SCORE_HEIGHT \ + + GTK_SCORE_BOARD_PADDING ) +#define GTK_BOARD_LEFT (GTK_BOARD_LEFT_MARGIN) + +#define GTK_TRAY_LEFT GTK_TRAY_LEFT_MARGIN + +#define GTK_DIVIDER_WIDTH 5 + +#define GTK_BOTTOM_MARGIN GTK_TOP_MARGIN +#define GTK_RIGHT_MARGIN GTK_BOARD_LEFT_MARGIN + +void initGlobals( GtkGameGlobals* globals, LaunchParams* params ); +void freeGlobals( GtkGameGlobals* globals ); +XP_Bool makeNewGame( GtkGameGlobals* globals ); +XP_Bool loadGameNoDraw( GtkGameGlobals* globals, LaunchParams* params, + sqlite3_int64 rowid ); +void destroy_board_window( GtkWidget* widget, GtkGameGlobals* globals ); + +#endif /* PLATFORM_GTK */ + +#endif diff --git a/xwords4/linux/gtkchat.c b/xwords4/linux/gtkchat.c index b8b09940e..3442a8b1b 100644 --- a/xwords4/linux/gtkchat.c +++ b/xwords4/linux/gtkchat.c @@ -22,7 +22,7 @@ #include "gtkchat.h" gchar* -gtkGetChatMessage( GtkAppGlobals* XP_UNUSED(globals) ) +gtkGetChatMessage( GtkGameGlobals* XP_UNUSED(globals) ) { gchar* result = NULL; GtkWidget* dialog = gtk_dialog_new_with_buttons( "message text", NULL, //GtkWindow *parent, diff --git a/xwords4/linux/gtkchat.h b/xwords4/linux/gtkchat.h index 1f171eafe..25a2dc415 100644 --- a/xwords4/linux/gtkchat.h +++ b/xwords4/linux/gtkchat.h @@ -23,9 +23,9 @@ #ifdef PLATFORM_GTK #include -#include "gtkmain.h" +#include "gtkboard.h" -gchar* gtkGetChatMessage( GtkAppGlobals* globals ); +gchar* gtkGetChatMessage( GtkGameGlobals* globals ); #endif #endif /* #ifndef _GTKCHAT_H_ */ diff --git a/xwords4/linux/gtkconnsdlg.c b/xwords4/linux/gtkconnsdlg.c index 6488453df..bbc410b96 100644 --- a/xwords4/linux/gtkconnsdlg.c +++ b/xwords4/linux/gtkconnsdlg.c @@ -24,7 +24,7 @@ #include "gtkutils.h" typedef struct _GtkConnsState { - GtkAppGlobals* globals; + GtkGameGlobals* globals; CommsAddrRec* addr; DeviceRole role; @@ -179,7 +179,7 @@ makeBTPage( GtkConnsState* state ) } /* makeBTPage */ gboolean -gtkConnsDlg( GtkAppGlobals* globals, CommsAddrRec* addr, DeviceRole role, +gtkConnsDlg( GtkGameGlobals* globals, CommsAddrRec* addr, DeviceRole role, XP_Bool readOnly ) { GtkConnsState state; diff --git a/xwords4/linux/gtkconnsdlg.h b/xwords4/linux/gtkconnsdlg.h index 8581b22a5..86bdb2f76 100644 --- a/xwords4/linux/gtkconnsdlg.h +++ b/xwords4/linux/gtkconnsdlg.h @@ -24,9 +24,9 @@ #ifndef _GTKCONNSDLG_H_ #define _GTKCONNSDLG_H_ -#include "gtkmain.h" +#include "gtkboard.h" -gboolean gtkConnsDlg( GtkAppGlobals* globals, CommsAddrRec* addr, +gboolean gtkConnsDlg( GtkGameGlobals* globals, CommsAddrRec* addr, DeviceRole role, XP_Bool readOnly ); #endif /* _GTKCONNSDLG_H_ */ diff --git a/xwords4/linux/gtkdraw.c b/xwords4/linux/gtkdraw.c index edcaa7e92..dd350f42c 100644 --- a/xwords4/linux/gtkdraw.c +++ b/xwords4/linux/gtkdraw.c @@ -26,7 +26,7 @@ #include -#include "gtkmain.h" +#include "gtkboard.h" #include "draw.h" #include "board.h" #include "linuxmain.h" @@ -515,7 +515,8 @@ gtk_draw_drawCell( DrawCtx* p_dctx, const XP_Rect* rect, const XP_UCHAR* letter, { GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; XP_Rect rectInset = *rect; - XP_Bool showGrid = dctx->globals->gridOn; + GtkGameGlobals* globals = dctx->globals; + XP_Bool showGrid = globals->gridOn; XP_Bool highlight = (flags & CELL_HIGHLIGHT) != 0; GdkColor* cursor = ((flags & CELL_ISCURSOR) != 0) ? &dctx->cursor : NULL; @@ -1305,7 +1306,7 @@ allocAndSet( GdkColormap* map, GdkColor* color, unsigned short red, } /* allocAndSet */ DrawCtx* -gtkDrawCtxtMake( GtkWidget* drawing_area, GtkAppGlobals* globals ) +gtkDrawCtxtMake( GtkWidget* drawing_area, GtkGameGlobals* globals ) { GtkDrawCtx* dctx = g_malloc0( sizeof(GtkDrawCtx) ); GdkColormap* map; @@ -1423,7 +1424,7 @@ gtkDrawCtxtMake( GtkWidget* drawing_area, GtkAppGlobals* globals ) void draw_gtk_status( GtkDrawCtx* dctx, char ch ) { - GtkAppGlobals* globals = dctx->globals; + GtkGameGlobals* globals = dctx->globals; XP_Rect rect = { .left = globals->netStatLeft, diff --git a/xwords4/linux/gtkdraw.h b/xwords4/linux/gtkdraw.h index 26b3de603..452575a3c 100644 --- a/xwords4/linux/gtkdraw.h +++ b/xwords4/linux/gtkdraw.h @@ -22,7 +22,7 @@ #include "draw.h" -DrawCtx* gtkDrawCtxtMake( GtkWidget *widget, GtkAppGlobals* globals ); +DrawCtx* gtkDrawCtxtMake( GtkWidget *widget, GtkGameGlobals* globals ); void draw_gtk_status( GtkDrawCtx* draw, char ch ); diff --git a/xwords4/linux/gtkletterask.h b/xwords4/linux/gtkletterask.h index ff7c3f02b..474279a97 100644 --- a/xwords4/linux/gtkletterask.h +++ b/xwords4/linux/gtkletterask.h @@ -24,7 +24,7 @@ #ifndef _GTKLETTERASK_H_ #define _GTKLETTERASK_H_ -#include "gtkmain.h" +#include "gtkboard.h" XP_S16 gtkletterask( const PickInfo* pi, XP_Bool forTray, const XP_UCHAR* name, diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c index f2b18537f..4c57fc330 100644 --- a/xwords4/linux/gtkmain.c +++ b/xwords4/linux/gtkmain.c @@ -1,6 +1,7 @@ -/* -*-mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ +/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ /* - * Copyright 2000-2009 by Eric House (xwords@eehouse.org). All rights reserved. + * Copyright 2000-2013 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 @@ -19,2153 +20,379 @@ #ifdef PLATFORM_GTK -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#ifndef CLIENT_ONLY -/* # include */ -#endif -#include -#include -#include -#include - #include "main.h" +#include "gtkmain.h" +#include "gamesdb.h" +#include "gtkboard.h" #include "linuxmain.h" -#include "linuxutl.h" -#include "linuxbt.h" -#include "linuxudp.h" -#include "linuxsms.h" -/* #include "gtkmain.h" */ - -#include "draw.h" -#include "game.h" +#include "relaycon.h" #include "gtkask.h" -#include "gtkchat.h" -#include "gtknewgame.h" -#include "gtkletterask.h" -#include "gtkpasswdask.h" -#include "gtkntilesask.h" -#include "gtkaskdict.h" -/* #include "undo.h" */ -#include "gtkdraw.h" -#include "memstream.h" -#include "filestream.h" -/* static guint gtkSetupClientSocket( GtkAppGlobals* globals, int sock ); */ -static void setCtrlsForTray( GtkAppGlobals* globals ); -static void new_game( GtkWidget* widget, GtkAppGlobals* globals ); -static void new_game_impl( GtkAppGlobals* globals, XP_Bool fireConnDlg ); -static void setZoomButtons( GtkAppGlobals* globals, XP_Bool* inOut ); -static void disenable_buttons( GtkAppGlobals* globals ); - - -#define GTK_TRAY_HT_ROWS 3 - -#if 0 -static XWStreamCtxt* -lookupClientStream( GtkAppGlobals* globals, int sock ) -{ - short i; - for ( i = 0; i < MAX_NUM_PLAYERS; ++i ) { - ClientStreamRec* rec = &globals->clientRecs[i]; - if ( rec->sock == sock ) { - XP_ASSERT( rec->stream != NULL ); - return rec->stream; - } - } - XP_ASSERT( i < MAX_NUM_PLAYERS ); - return NULL; -} /* lookupClientStream */ +static void onNewData( GtkAppGlobals* apg, sqlite3_int64 rowid, + XP_Bool isNew ); +static void updateButtons( GtkAppGlobals* apg ); static void -rememberClient( GtkAppGlobals* globals, guint key, int sock, - XWStreamCtxt* stream ) +recordOpened( GtkAppGlobals* apg, GtkGameGlobals* globals ) { - short i; - for ( i = 0; i < MAX_NUM_PLAYERS; ++i ) { - ClientStreamRec* rec = &globals->clientRecs[i]; - if ( rec->stream == NULL ) { - XP_ASSERT( stream != NULL ); - rec->stream = stream; - rec->key = key; - rec->sock = sock; + apg->globalsList = g_slist_prepend( apg->globalsList, globals ); + globals->apg = apg; +} + +static void +recordClosed( GtkAppGlobals* apg, GtkGameGlobals* globals ) +{ + apg->globalsList = g_slist_remove( apg->globalsList, globals ); +} + +static XP_Bool +gameIsOpen( GtkAppGlobals* apg, sqlite3_int64 rowid ) +{ + XP_Bool found = XP_FALSE; + GSList* iter; + for ( iter = apg->globalsList; !!iter && !found; iter = iter->next ) { + GtkGameGlobals* globals = (GtkGameGlobals*)iter->data; + found = globals->cGlobals.selRow == rowid; + } + return found; +} + +static GtkGameGlobals* +findGame( const GtkAppGlobals* apg, XP_U32 clientToken ) +{ + GtkGameGlobals* result = NULL; + GSList* iter; + for ( iter = apg->globalsList; !!iter; iter = iter->next ) { + GtkGameGlobals* globals = (GtkGameGlobals*)iter->data; + CommonGlobals* cGlobals = &globals->cGlobals; + if ( cGlobals->selRow == clientToken ) { + result = globals; break; } } - XP_ASSERT( i < MAX_NUM_PLAYERS ); -} /* rememberClient */ -#endif - -static void -gtkSetAltState( GtkAppGlobals* globals, guint state ) -{ - globals->altKeyDown - = (state & (GDK_MOD1_MASK|GDK_SHIFT_MASK|GDK_CONTROL_MASK)) != 0; + return result; } -static gint -button_press_event( GtkWidget* XP_UNUSED(widget), GdkEventButton *event, - GtkAppGlobals* globals ) +enum { ROW_ITEM, NAME_ITEM, ROOM_ITEM, OVER_ITEM, TURN_ITEM, NMOVES_ITEM, + MISSING_ITEM, N_ITEMS }; + +static void +foreachProc( GtkTreeModel* model, GtkTreePath* XP_UNUSED(path), + GtkTreeIter* iter, gpointer data ) { - XP_Bool redraw, handled; + GtkAppGlobals* apg = (GtkAppGlobals*)data; + sqlite3_int64 rowid; + gtk_tree_model_get( model, iter, ROW_ITEM, &rowid, -1 ); + apg->selRows = g_array_append_val( apg->selRows, rowid ); +} - gtkSetAltState( globals, event->state ); +static void +tree_selection_changed_cb( GtkTreeSelection* selection, gpointer data ) +{ + GtkAppGlobals* apg = (GtkAppGlobals*)data; - if ( !globals->mouseDown ) { - globals->mouseDown = XP_TRUE; - redraw = board_handlePenDown( globals->cGlobals.game.board, - event->x, event->y, &handled ); - if ( redraw ) { - board_draw( globals->cGlobals.game.board ); - disenable_buttons( globals ); + apg->selRows = g_array_set_size( apg->selRows, 0 ); + gtk_tree_selection_selected_foreach( selection, foreachProc, apg ); + + updateButtons( apg ); +} + +static void +removeRow( GtkAppGlobals* apg, sqlite3_int64 rowid ) +{ + GtkTreeModel* model = + gtk_tree_view_get_model(GTK_TREE_VIEW(apg->listWidget)); + GtkTreeIter iter; + gboolean valid; + for ( valid = gtk_tree_model_get_iter_first( model, &iter ); + valid; + valid = gtk_tree_model_iter_next( model, &iter ) ) { + sqlite3_int64 tmpid; + gtk_tree_model_get( model, &iter, ROW_ITEM, &tmpid, -1 ); + if ( tmpid == rowid ) { + gtk_list_store_remove( GTK_LIST_STORE(model), &iter ); + break; } } - return 1; -} /* button_press_event */ +} -static gint -motion_notify_event( GtkWidget* XP_UNUSED(widget), GdkEventMotion *event, - GtkAppGlobals* globals ) +static void +addTextColumn( GtkWidget* list, const gchar* title, int item ) { - XP_Bool handled; + GtkCellRenderer* renderer = gtk_cell_renderer_text_new(); + GtkTreeViewColumn* column = + gtk_tree_view_column_new_with_attributes( title, renderer, "text", + item, NULL ); + gtk_tree_view_append_column( GTK_TREE_VIEW(list), column ); +} - gtkSetAltState( globals, event->state ); +static GtkWidget* +init_games_list( GtkAppGlobals* apg ) +{ + GtkWidget* list = gtk_tree_view_new(); - if ( globals->mouseDown ) { - handled = board_handlePenMove( globals->cGlobals.game.board, event->x, - event->y ); - if ( handled ) { - board_draw( globals->cGlobals.game.board ); - disenable_buttons( globals ); - } + addTextColumn( list, "Row", ROW_ITEM ); + addTextColumn( list, "Name", NAME_ITEM ); + addTextColumn( list, "Room", ROOM_ITEM ); + addTextColumn( list, "Ended", OVER_ITEM ); + addTextColumn( list, "Turn", TURN_ITEM ); + addTextColumn( list, "NMoves", NMOVES_ITEM ); + addTextColumn( list, "NMissing", MISSING_ITEM ); + + GtkListStore* store = gtk_list_store_new( N_ITEMS, + G_TYPE_INT64, G_TYPE_STRING, + G_TYPE_STRING, G_TYPE_BOOLEAN, + G_TYPE_INT, G_TYPE_INT, + G_TYPE_INT ); + gtk_tree_view_set_model( GTK_TREE_VIEW(list), GTK_TREE_MODEL(store) ); + g_object_unref( store ); + + GtkTreeSelection* select = + gtk_tree_view_get_selection( GTK_TREE_VIEW (list) ); + gtk_tree_selection_set_mode( select, GTK_SELECTION_MULTIPLE ); + g_signal_connect( G_OBJECT(select), "changed", + G_CALLBACK(tree_selection_changed_cb), apg ); + return list; +} + +static void +add_to_list( GtkWidget* list, sqlite3_int64 rowid, XP_Bool isNew, + const GameInfo* gib ) +{ + GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(list)); + GtkListStore* store = GTK_LIST_STORE( model ); + GtkTreeIter iter; + if ( isNew ) { + gtk_list_store_append( store, &iter ); } else { - handled = XP_FALSE; - } - - return handled; -} /* motion_notify_event */ - -static gint -button_release_event( GtkWidget* XP_UNUSED(widget), GdkEventMotion *event, - GtkAppGlobals* globals ) -{ - XP_Bool redraw; - - gtkSetAltState( globals, event->state ); - - if ( globals->mouseDown ) { - redraw = board_handlePenUp( globals->cGlobals.game.board, - event->x, - event->y ); - if ( redraw ) { - board_draw( globals->cGlobals.game.board ); - disenable_buttons( globals ); - } - globals->mouseDown = XP_FALSE; - } - return 1; -} /* button_release_event */ - -#ifdef KEY_SUPPORT -static XP_Key -evtToXPKey( GdkEventKey* event, XP_Bool* movesCursorP ) -{ - XP_Key xpkey = XP_KEY_NONE; - XP_Bool movesCursor = XP_FALSE; - guint keyval = event->keyval; - - switch( keyval ) { -#ifdef KEYBOARD_NAV - case GDK_Return: - xpkey = XP_RETURN_KEY; - break; - case GDK_space: - xpkey = XP_RAISEFOCUS_KEY; - break; - - case GDK_Left: - xpkey = XP_CURSOR_KEY_LEFT; - movesCursor = XP_TRUE; - break; - case GDK_Right: - xpkey = XP_CURSOR_KEY_RIGHT; - movesCursor = XP_TRUE; - break; - case GDK_Up: - xpkey = XP_CURSOR_KEY_UP; - movesCursor = XP_TRUE; - break; - case GDK_Down: - xpkey = XP_CURSOR_KEY_DOWN; - movesCursor = XP_TRUE; - break; -#endif - case GDK_BackSpace: - XP_LOGF( "... it's a DEL" ); - xpkey = XP_CURSOR_KEY_DEL; - break; - default: - keyval = keyval & 0x00FF; /* mask out gtk stuff */ - if ( isalpha( keyval ) ) { - xpkey = toupper(keyval); - break; -#ifdef NUMBER_KEY_AS_INDEX - } else if ( isdigit( keyval ) ) { - xpkey = keyval; - break; -#endif + gboolean valid; + for ( valid = gtk_tree_model_get_iter_first( model, &iter ); + valid; + valid = gtk_tree_model_iter_next( model, &iter ) ) { + sqlite3_int64 tmpid; + gtk_tree_model_get( model, &iter, ROW_ITEM, &tmpid, -1 ); + if ( tmpid == rowid ) { + break; + } } } - *movesCursorP = movesCursor; - return xpkey; -} /* evtToXPKey */ - -#ifdef KEYBOARD_NAV -static gint -key_press_event( GtkWidget* XP_UNUSED(widget), GdkEventKey* event, - GtkAppGlobals* globals ) -{ - XP_Bool handled = XP_FALSE; - XP_Bool movesCursor; - XP_Key xpkey = evtToXPKey( event, &movesCursor ); - - gtkSetAltState( globals, event->state ); - - if ( xpkey != XP_KEY_NONE ) { - XP_Bool draw = globals->keyDown ? - board_handleKeyRepeat( globals->cGlobals.game.board, xpkey, - &handled ) - : board_handleKeyDown( globals->cGlobals.game.board, xpkey, - &handled ); - if ( draw ) { - board_draw( globals->cGlobals.game.board ); - } - } - globals->keyDown = XP_TRUE; - return 1; + gtk_list_store_set( store, &iter, + ROW_ITEM, rowid, + NAME_ITEM, gib->name, + ROOM_ITEM, gib->room, + OVER_ITEM, gib->gameOver, + TURN_ITEM, gib->turn, + NMOVES_ITEM, gib->nMoves, + MISSING_ITEM, gib->nMissing, + -1 ); + XP_LOGF( "DONE adding" ); } -#endif -static gint -key_release_event( GtkWidget* XP_UNUSED(widget), GdkEventKey* event, - GtkAppGlobals* globals ) +static void updateButtons( GtkAppGlobals* apg ) { - XP_Bool handled = XP_FALSE; - XP_Bool movesCursor; - XP_Key xpkey = evtToXPKey( event, &movesCursor ); + guint count = apg->selRows->len; - gtkSetAltState( globals, event->state ); - - if ( xpkey != XP_KEY_NONE ) { - XP_Bool draw; - draw = board_handleKeyUp( globals->cGlobals.game.board, xpkey, - &handled ); -#ifdef KEYBOARD_NAV - if ( movesCursor && !handled ) { - BoardObjectType order[] = { OBJ_SCORE, OBJ_BOARD, OBJ_TRAY }; - draw = linShiftFocus( &globals->cGlobals, xpkey, order, - NULL ) || draw; - } -#endif - if ( draw ) { - board_draw( globals->cGlobals.game.board ); - } - } - -/* XP_ASSERT( globals->keyDown ); */ -#ifdef KEYBOARD_NAV - globals->keyDown = XP_FALSE; -#endif - - return handled? 1 : 0; /* gtk will do something with the key if 0 - returned */ -} /* key_release_event */ -#endif - -#ifdef MEM_DEBUG -# define MEMPOOL globals->cGlobals.params->util->mpool, -#else -# define MEMPOOL -#endif - -static void -relay_status_gtk( void* closure, CommsRelayState state ) -{ - XP_LOGF( "%s got status: %s", __func__, CommsRelayState2Str(state) ); - GtkAppGlobals* globals = (GtkAppGlobals*)closure; - globals->cGlobals.state = state; - globals->stateChar = 'A' + COMMS_RELAYSTATE_ALLCONNECTED - state; - draw_gtk_status( globals->draw, globals->stateChar ); + gtk_widget_set_sensitive( apg->openButton, 1 == count ); + gtk_widget_set_sensitive( apg->deleteButton, 1 <= count ); } static void -relay_connd_gtk( void* closure, XP_UCHAR* const room, - XP_Bool XP_UNUSED(reconnect), XP_U16 devOrder, - XP_Bool allHere, XP_U16 nMissing ) +handle_newgame_button( GtkWidget* XP_UNUSED(widget), void* closure ) { - XP_Bool skip = XP_FALSE; - char buf[256]; - - if ( allHere ) { - snprintf( buf, sizeof(buf), - "All expected players have joined in %s. Play!", room ); + GtkAppGlobals* apg = (GtkAppGlobals*)closure; + XP_LOGF( "%s called", __func__ ); + GtkGameGlobals* globals = malloc( sizeof(*globals) ); + apg->params->needsNewGame = XP_FALSE; + initGlobals( globals, apg->params ); + if ( !makeNewGame( globals ) ) { + freeGlobals( globals ); } else { - if ( nMissing > 0 ) { - snprintf( buf, sizeof(buf), "%dth connected to relay; waiting " - "in %s for %d player[s].", devOrder, room, nMissing ); + GtkWidget* gameWindow = globals->window; + globals->cGlobals.pDb = apg->params->pDb; + globals->cGlobals.selRow = -1; + recordOpened( apg, globals ); + gtk_widget_show( gameWindow ); + } +} + +static void +handle_open_button( GtkWidget* XP_UNUSED(widget), void* closure ) +{ + GtkAppGlobals* apg = (GtkAppGlobals*)closure; + sqlite3_int64 selRow = getSelRow( apg ); + if ( -1 != selRow && !gameIsOpen( apg, selRow ) ) { + apg->params->needsNewGame = XP_FALSE; + GtkGameGlobals* globals = malloc( sizeof(*globals) ); + initGlobals( globals, apg->params ); + globals->cGlobals.pDb = apg->params->pDb; + globals->cGlobals.selRow = selRow; + recordOpened( apg, globals ); + gtk_widget_show( globals->window ); + } +} + +static void +handle_delete_button( GtkWidget* XP_UNUSED(widget), void* closure ) +{ + GtkAppGlobals* apg = (GtkAppGlobals*)closure; + LaunchParams* params = apg->params; + guint len = apg->selRows->len; + for ( guint ii = 0; ii < len; ++ii ) { + sqlite3_int64 rowid = g_array_index( apg->selRows, sqlite3_int64, ii ); + removeRow( apg, rowid ); + deleteGame( params->pDb, rowid ); + + XP_UCHAR devIDBuf[64] = {0}; + db_fetch( params->pDb, KEY_RDEVID, devIDBuf, sizeof(devIDBuf) ); + if ( '\0' != devIDBuf[0] ) { + relaycon_deleted( params, devIDBuf, rowid ); } else { - /* an allHere message should be coming immediately, so no - notification now. */ - skip = XP_TRUE; + XP_LOGF( "%s: not calling relaycon_deleted: no relayID", __func__ ); } } - - if ( !skip ) { - GtkAppGlobals* globals = (GtkAppGlobals*)closure; - (void)gtkask_timeout( globals->window, buf, GTK_BUTTONS_OK, 500 ); - } -} - -static gint -invoke_new_game( gpointer data ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)data; - new_game_impl( globals, XP_FALSE ); - return 0; -} - -static gint -invoke_new_game_conns( gpointer data ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)data; - new_game_impl( globals, XP_TRUE ); - return 0; + apg->selRows = g_array_set_size( apg->selRows, 0 ); + updateButtons( apg ); + /* Need now to update the selection and sync the buttons */ } static void -relay_error_gtk( void* closure, XWREASON relayErr ) +handle_destroy( GtkWidget* XP_UNUSED(widget), gpointer data ) { LOG_FUNC(); - GtkAppGlobals* globals = (GtkAppGlobals*)closure; - - gint (*proc)( gpointer data ) = NULL; - switch( relayErr ) { - case XWRELAY_ERROR_NO_ROOM: - case XWRELAY_ERROR_DUP_ROOM: - proc = invoke_new_game_conns; - break; - case XWRELAY_ERROR_TOO_MANY: - case XWRELAY_ERROR_BADPROTO: - proc = invoke_new_game; - break; - case XWRELAY_ERROR_DELETED: - gtkask_timeout( globals->window, - "relay says another device deleted game.", - GTK_BUTTONS_OK, 1000 ); - break; - case XWRELAY_ERROR_DEADGAME: - break; - default: - assert(0); - break; + GtkAppGlobals* apg = (GtkAppGlobals*)data; + GSList* iter; + for ( iter = apg->globalsList; !!iter; iter = iter->next ) { + GtkGameGlobals* globals = (GtkGameGlobals*)iter->data; + destroy_board_window( NULL, globals ); + // freeGlobals( globals ); } - - if ( !!proc ) { - (void)g_idle_add( proc, globals ); - } -} - -static void -createOrLoadObjects( GtkAppGlobals* globals ) -{ - XWStreamCtxt* stream = NULL; - XP_Bool opened = XP_FALSE; - -#ifndef XWFEATURE_STANDALONE_ONLY - DeviceRole serverRole = globals->cGlobals.params->serverRole; - XP_Bool isServer = serverRole != SERVER_ISCLIENT; -#endif - LaunchParams* params = globals->cGlobals.params; - - globals->draw = (GtkDrawCtx*)gtkDrawCtxtMake( globals->drawing_area, - globals ); - - TransportProcs procs = { - .closure = globals, - .send = LINUX_SEND, -#ifdef COMMS_HEARTBEAT - .reset = linux_reset, -#endif -#ifdef XWFEATURE_RELAY - .rstatus = relay_status_gtk, - .rconnd = relay_connd_gtk, - .rerror = relay_error_gtk, -#endif - }; - - if ( !!params->fileName && file_exists( params->fileName ) ) { - stream = streamFromFile( &globals->cGlobals, params->fileName, globals ); -#ifdef USE_SQLITE - } else if ( !!params->dbFileName && file_exists( params->dbFileName ) ) { - stream = streamFromDB( &globals->cGlobals, globals ); -#endif - } - - if ( !!stream ) { - opened = game_makeFromStream( MEMPOOL stream, &globals->cGlobals.game, - &globals->cGlobals.params->gi, - params->dict, ¶ms->dicts, params->util, - (DrawCtx*)globals->draw, - &globals->cGlobals.cp, &procs ); - - stream_destroy( stream ); - } - - if ( !opened ) { - CommsAddrRec addr; - - XP_MEMSET( &addr, 0, sizeof(addr) ); - addr.conType = params->conType; - -#ifdef XWFEATURE_RELAY - if ( addr.conType == COMMS_CONN_RELAY ) { - XP_ASSERT( !!params->connInfo.relay.relayName ); - globals->cGlobals.defaultServerName - = params->connInfo.relay.relayName; - } -#endif - - game_makeNewGame( MEMPOOL &globals->cGlobals.game, ¶ms->gi, - params->util, (DrawCtx*)globals->draw, - &globals->cGlobals.cp, &procs, params->gameSeed ); - - addr.conType = params->conType; - if ( 0 ) { -#ifdef XWFEATURE_RELAY - } else if ( addr.conType == COMMS_CONN_RELAY ) { - addr.u.ip_relay.ipAddr = 0; - addr.u.ip_relay.port = params->connInfo.relay.defaultSendPort; - addr.u.ip_relay.seeksPublicRoom = params->connInfo.relay.seeksPublicRoom; - addr.u.ip_relay.advertiseRoom = params->connInfo.relay.advertiseRoom; - XP_STRNCPY( addr.u.ip_relay.hostName, params->connInfo.relay.relayName, - sizeof(addr.u.ip_relay.hostName) - 1 ); - XP_STRNCPY( addr.u.ip_relay.invite, params->connInfo.relay.invite, - sizeof(addr.u.ip_relay.invite) - 1 ); -#endif -#ifdef XWFEATURE_BLUETOOTH - } else if ( addr.conType == COMMS_CONN_BT ) { - XP_ASSERT( sizeof(addr.u.bt.btAddr) - >= sizeof(params->connInfo.bt.hostAddr)); - XP_MEMCPY( &addr.u.bt.btAddr, ¶ms->connInfo.bt.hostAddr, - sizeof(params->connInfo.bt.hostAddr) ); -#endif -#ifdef XWFEATURE_IP_DIRECT - } else if ( addr.conType == COMMS_CONN_IP_DIRECT ) { - XP_STRNCPY( addr.u.ip.hostName_ip, params->connInfo.ip.hostName, - sizeof(addr.u.ip.hostName_ip) - 1 ); - addr.u.ip.port_ip = params->connInfo.ip.port; -#endif -#ifdef XWFEATURE_SMS - } else if ( addr.conType == COMMS_CONN_SMS ) { - XP_STRNCPY( addr.u.sms.phone, params->connInfo.sms.serverPhone, - sizeof(addr.u.sms.phone) - 1 ); - addr.u.sms.port = params->connInfo.sms.port; -#endif - } - -#ifndef XWFEATURE_STANDALONE_ONLY - /* This may trigger network activity */ - if ( !!globals->cGlobals.game.comms ) { - comms_setAddr( globals->cGlobals.game.comms, &addr ); - } -#endif - model_setDictionary( globals->cGlobals.game.model, params->dict ); - setSquareBonuses( &globals->cGlobals ); - model_setPlayerDicts( globals->cGlobals.game.model, ¶ms->dicts ); - -#ifdef XWFEATURE_SEARCHLIMIT - params->gi.allowHintRect = params->allowHintRect; -#endif - - if ( params->needsNewGame ) { - new_game_impl( globals, XP_FALSE ); -#ifndef XWFEATURE_STANDALONE_ONLY - } else if ( !isServer ) { - XWStreamCtxt* stream = - mem_stream_make( MEMPOOL params->vtMgr, &globals->cGlobals, CHANNEL_NONE, - sendOnClose ); - server_initClientConnection( globals->cGlobals.game.server, - stream ); -#endif - } - } - -#ifndef XWFEATURE_STANDALONE_ONLY - if ( !!globals->cGlobals.game.comms ) { - comms_start( globals->cGlobals.game.comms ); - } -#endif - server_do( globals->cGlobals.game.server ); - - disenable_buttons( globals ); -} /* createOrLoadObjects */ - -/* Create a new backing pixmap of the appropriate size and set up contxt to - * draw using that size. - */ -static gboolean -configure_event( GtkWidget* widget, GdkEventConfigure* XP_UNUSED(event), - GtkAppGlobals* globals ) -{ - short bdWidth, bdHeight; - short timerLeft, timerTop; - gint hscale, vscale; - gint trayTop; - gint boardTop = 0; - XP_U16 netStatWidth = 0; - gint nCols = globals->cGlobals.params->gi.boardSize; - gint nRows = nCols; - - if ( globals->draw == NULL ) { - createOrLoadObjects( globals ); - } - - bdWidth = widget->allocation.width - (GTK_RIGHT_MARGIN - + GTK_BOARD_LEFT_MARGIN); - if ( globals->cGlobals.params->verticalScore ) { - bdWidth -= GTK_VERT_SCORE_WIDTH; - } - bdHeight = widget->allocation.height - (GTK_TOP_MARGIN + GTK_BOTTOM_MARGIN) - - GTK_MIN_TRAY_SCALEV - GTK_BOTTOM_MARGIN; - - hscale = bdWidth / nCols; - if ( 0 != globals->cGlobals.params->nHidden ) { - vscale = hscale; - } else { - vscale = (bdHeight / (nCols + GTK_TRAY_HT_ROWS)); /* makd tray height - 3x cell height */ - } - - if ( !globals->cGlobals.params->verticalScore ) { - boardTop += GTK_HOR_SCORE_HEIGHT; - } - - trayTop = boardTop + (vscale * nRows); - /* move tray up if part of board's meant to be hidden */ - trayTop -= vscale * globals->cGlobals.params->nHidden; - board_setPos( globals->cGlobals.game.board, GTK_BOARD_LEFT, boardTop, - hscale * nCols, vscale * nRows, hscale * 4, XP_FALSE ); - /* board_setScale( globals->cGlobals.game.board, hscale, vscale ); */ - globals->gridOn = XP_TRUE; - - if ( !!globals->cGlobals.game.comms ) { - netStatWidth = GTK_NETSTAT_WIDTH; - } - - timerTop = GTK_TIMER_TOP; - if ( globals->cGlobals.params->verticalScore ) { - timerLeft = GTK_BOARD_LEFT + (hscale*nCols) + 1; - board_setScoreboardLoc( globals->cGlobals.game.board, - timerLeft, - GTK_VERT_SCORE_TOP, - GTK_VERT_SCORE_WIDTH, - vscale*nCols, - XP_FALSE ); - - } else { - timerLeft = GTK_BOARD_LEFT + (hscale*nCols) - - GTK_TIMER_WIDTH - netStatWidth; - board_setScoreboardLoc( globals->cGlobals.game.board, - GTK_BOARD_LEFT, GTK_HOR_SCORE_TOP, - timerLeft-GTK_BOARD_LEFT, - GTK_HOR_SCORE_HEIGHT, - XP_TRUE ); - - } - - /* Still pending: do this for the vertical score case */ - if ( globals->cGlobals.game.comms ) { - globals->netStatLeft = timerLeft + GTK_TIMER_WIDTH; - globals->netStatTop = 0; - } - - board_setTimerLoc( globals->cGlobals.game.board, timerLeft, timerTop, - GTK_TIMER_WIDTH, GTK_HOR_SCORE_HEIGHT ); - - board_setTrayLoc( globals->cGlobals.game.board, GTK_TRAY_LEFT, trayTop, - hscale * nCols, vscale * GTK_TRAY_HT_ROWS + 10, - GTK_DIVIDER_WIDTH ); - - setCtrlsForTray( globals ); - - board_invalAll( globals->cGlobals.game.board ); - - XP_Bool inOut[2]; - board_zoom( globals->cGlobals.game.board, 0, inOut ); - setZoomButtons( globals, inOut ); - - return TRUE; -} /* configure_event */ - -/* Redraw the screen from the backing pixmap */ -static gint -expose_event( GtkWidget* XP_UNUSED(widget), - GdkEventExpose* XP_UNUSED(event), - GtkAppGlobals* globals ) -{ - /* - gdk_draw_rectangle( widget->window,//((GtkDrawCtx*)globals->draw)->pixmap, - widget->style->white_gc, - TRUE, - 0, 0, - widget->allocation.width, - widget->allocation.height+widget->allocation.y ); - */ - /* I want to inval only the area that's exposed, but the rect is always - empty, even when clearly shouldn't be. Need to investigate. Until - fixed, use board_invalAll to ensure board is drawn.*/ -/* board_invalRect( globals->cGlobals.game.board, (XP_Rect*)&event->area ); */ - - board_invalAll( globals->cGlobals.game.board ); - board_draw( globals->cGlobals.game.board ); - draw_gtk_status( globals->draw, globals->stateChar ); - -/* gdk_draw_pixmap( widget->window, */ -/* widget->style->fg_gc[GTK_WIDGET_STATE (widget)], */ -/* ((GtkDrawCtx*)globals->draw)->pixmap, */ -/* event->area.x, event->area.y, */ -/* event->area.x, event->area.y, */ -/* event->area.width, event->area.height ); */ - - return FALSE; -} /* expose_event */ - -#if 0 -static gint -handle_client_event( GtkWidget *widget, GdkEventClient *event, - GtkAppGlobals* globals ) -{ - XP_LOGF( "handle_client_event called: event->type = " ); - if ( event->type == GDK_CLIENT_EVENT ) { - XP_LOGF( "GDK_CLIENT_EVENT" ); - return 1; - } else { - XP_LOGF( "%d", event->type ); - return 0; - } -} /* handle_client_event */ -#endif - -static void -quit( void ) -{ + g_slist_free( apg->globalsList ); gtk_main_quit(); } static void -cleanup( GtkAppGlobals* globals ) +handle_quit_button( GtkWidget* XP_UNUSED(widget), gpointer data ) { - saveGame( &globals->cGlobals ); - - game_dispose( &globals->cGlobals.game ); /* takes care of the dict */ - gi_disposePlayerInfo( MEMPOOL &globals->cGlobals.params->gi ); - -#ifdef XWFEATURE_BLUETOOTH - linux_bt_close( &globals->cGlobals ); -#endif -#ifdef XWFEATURE_SMS - linux_sms_close( &globals->cGlobals ); -#endif -#ifdef XWFEATURE_IP_DIRECT - linux_udp_close( &globals->cGlobals ); -#endif -#ifdef XWFEATURE_RELAY - linux_close_socket( &globals->cGlobals ); -#endif -} /* cleanup */ - -GtkWidget* -makeAddSubmenu( GtkWidget* menubar, gchar* label ) -{ - GtkWidget* submenu; - GtkWidget* item; - - item = gtk_menu_item_new_with_label( label ); - gtk_menu_bar_append( GTK_MENU_BAR(menubar), item ); - - submenu = gtk_menu_new(); - gtk_menu_item_set_submenu( GTK_MENU_ITEM(item), submenu ); - - gtk_widget_show(item); - - return submenu; -} /* makeAddSubmenu */ - -static void -tile_values( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - if ( !!globals->cGlobals.game.server ) { - XWStreamCtxt* stream = - mem_stream_make( MEMPOOL - globals->cGlobals.params->vtMgr, - globals, - CHANNEL_NONE, - catOnClose ); - server_formatDictCounts( globals->cGlobals.game.server, stream, 5 ); - stream_putU8( stream, '\n' ); - stream_destroy( stream ); - } - -} /* tile_values */ - -static void -game_history( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - catGameHistory( &globals->cGlobals ); -} /* game_history */ - -#ifdef TEXT_MODEL -static void -dump_board( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - if ( !!globals->cGlobals.game.model ) { - XWStreamCtxt* stream = - mem_stream_make( MEMPOOL - globals->cGlobals.params->vtMgr, - globals, - CHANNEL_NONE, - catOnClose ); - model_writeToTextStream( globals->cGlobals.game.model, stream ); - stream_destroy( stream ); - } + GtkAppGlobals* apg = (GtkAppGlobals*)data; + handle_destroy( NULL, apg ); } -#endif - -static void -final_scores( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - XP_Bool gameOver = server_getGameIsOver( globals->cGlobals.game.server ); - - if ( gameOver ) { - catFinalScores( &globals->cGlobals, -1 ); - } else { - if ( gtkask( globals->window, - "Are you sure you want to resign?", - GTK_BUTTONS_YES_NO ) ) { - globals->cGlobals.manualFinal = XP_TRUE; - server_endGame( globals->cGlobals.game.server ); - gameOver = TRUE; - } - } - - /* the end game listener will take care of printing the final scores */ -} /* final_scores */ - -static void -new_game_impl( GtkAppGlobals* globals, XP_Bool fireConnDlg ) -{ - CommsAddrRec addr; - - if ( !!globals->cGlobals.game.comms ) { - comms_getAddr( globals->cGlobals.game.comms, &addr ); - } else { - comms_getInitialAddr( &addr, RELAY_NAME_DEFAULT, RELAY_PORT_DEFAULT ); - } - - if ( newGameDialog( globals, &addr, XP_TRUE, fireConnDlg ) ) { - CurGameInfo* gi = &globals->cGlobals.params->gi; -#ifndef XWFEATURE_STANDALONE_ONLY - XP_Bool isClient = gi->serverRole == SERVER_ISCLIENT; -#endif - TransportProcs procs = { - .closure = globals, - .send = LINUX_SEND, -#ifdef COMMS_HEARTBEAT - .reset = linux_reset, -#endif - }; - - game_reset( MEMPOOL &globals->cGlobals.game, gi, - globals->cGlobals.params->util, - &globals->cGlobals.cp, &procs ); - -#ifndef XWFEATURE_STANDALONE_ONLY - if ( !!globals->cGlobals.game.comms ) { - comms_setAddr( globals->cGlobals.game.comms, &addr ); - } else if ( gi->serverRole != SERVER_STANDALONE ) { - XP_ASSERT(0); - } - - if ( isClient ) { - XWStreamCtxt* stream = - mem_stream_make( MEMPOOL globals->cGlobals.params->vtMgr, - &globals->cGlobals, CHANNEL_NONE, - sendOnClose ); - server_initClientConnection( globals->cGlobals.game.server, - stream ); - } -#endif - (void)server_do( globals->cGlobals.game.server ); /* assign tiles, etc. */ - board_invalAll( globals->cGlobals.game.board ); - board_draw( globals->cGlobals.game.board ); - } - -} /* new_game_impl */ - -static void -new_game( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - new_game_impl( globals, XP_FALSE ); -} /* new_game */ - -static void -game_info( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - CommsAddrRec addr; - comms_getAddr( globals->cGlobals.game.comms, &addr ); - - /* Anything to do if OK is clicked? Changed names etc. already saved. Try - server_do in case one's become a robot. */ - if ( newGameDialog( globals, &addr, XP_FALSE, XP_FALSE ) ) { - if ( server_do( globals->cGlobals.game.server ) ) { - board_draw( globals->cGlobals.game.board ); - } - } -} - -static void -load_game( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* XP_UNUSED(globals) ) -{ -} /* load_game */ - -static void -save_game( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* XP_UNUSED(globals) ) -{ -} /* save_game */ - -#ifdef XWFEATURE_CHANGEDICT -static void -change_dictionary( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - LaunchParams* params = globals->cGlobals.params; - GSList* dicts = listDicts( params ); - gchar buf[265]; - gchar* name = gtkaskdict( dicts, buf, VSIZE(buf) ); - if ( !!name ) { - DictionaryCtxt* dict = - linux_dictionary_make( MPPARM(params->util->mpool) params, name, - params->useMmap ); - game_changeDict( MPPARM(params->util->mpool) &globals->cGlobals.game, - ¶ms->gi, dict ); - } - g_slist_free( dicts ); -} /* change_dictionary */ -#endif - -static void -handle_undo( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* XP_UNUSED(globals) ) -{ -} /* handle_undo */ - -static void -handle_redo( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* XP_UNUSED(globals) ) -{ -} /* handle_redo */ - -#ifdef FEATURE_TRAY_EDIT -static void -handle_trayEditToggle( GtkWidget* XP_UNUSED(widget), - GtkAppGlobals* XP_UNUSED(globals), - XP_Bool XP_UNUSED(on) ) -{ -} /* handle_trayEditToggle */ - -static void -handle_trayEditToggle_on( GtkWidget* widget, GtkAppGlobals* globals ) -{ - handle_trayEditToggle( widget, globals, XP_TRUE ); -} - -static void -handle_trayEditToggle_off( GtkWidget* widget, GtkAppGlobals* globals ) -{ - handle_trayEditToggle( widget, globals, XP_FALSE ); -} -#endif - -static void -handle_trade_cancel( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - BoardCtxt* board = globals->cGlobals.game.board; - if ( board_endTrade( board ) ) { - board_draw( board ); - } -} - -#ifndef XWFEATURE_STANDALONE_ONLY -static void -handle_resend( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - CommsCtxt* comms = globals->cGlobals.game.comms; - if ( comms != NULL ) { - comms_resendAll( comms, XP_TRUE ); - } -} /* handle_resend */ - -#ifdef XWFEATURE_COMMSACK -static void -handle_ack( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - CommsCtxt* comms = globals->cGlobals.game.comms; - if ( comms != NULL ) { - comms_ackAny( comms ); - } -} -#endif - -#ifdef DEBUG -static void -handle_commstats( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - CommsCtxt* comms = globals->cGlobals.game.comms; - - if ( !!comms ) { - XWStreamCtxt* stream = - mem_stream_make( MEMPOOL - globals->cGlobals.params->vtMgr, - globals, - CHANNEL_NONE, catOnClose ); - comms_getStats( comms, stream ); - stream_destroy( stream ); - } -} /* handle_commstats */ -#endif -#endif - -#ifdef MEM_DEBUG -static void -handle_memstats( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - XWStreamCtxt* stream = mem_stream_make( MEMPOOL - globals->cGlobals.params->vtMgr, - globals, - CHANNEL_NONE, catOnClose ); - mpool_stats( MEMPOOL stream ); - stream_destroy( stream ); - -} /* handle_memstats */ -#endif static GtkWidget* -createAddItem( GtkWidget* parent, gchar* label, - GtkSignalFunc handlerFunc, GtkAppGlobals* globals ) +addButton( gchar* label, GtkWidget* parent, GCallback proc, void* closure ) { - GtkWidget* item = gtk_menu_item_new_with_label( label ); - -/* g_print( "createAddItem called with label %s\n", label ); */ - - if ( handlerFunc != NULL ) { - g_signal_connect( GTK_OBJECT(item), "activate", - G_CALLBACK(handlerFunc), globals ); - } - - gtk_menu_append( GTK_MENU(parent), item ); - gtk_widget_show( item ); - - return item; -} /* createAddItem */ + GtkWidget* button = gtk_button_new_with_label( label ); + gtk_container_add( GTK_CONTAINER(parent), button ); + g_signal_connect( GTK_OBJECT(button), "clicked", + G_CALLBACK(proc), closure ); + gtk_widget_show( button ); + return button; +} static GtkWidget* -makeMenus( GtkAppGlobals* globals, int XP_UNUSED(argc), - char** XP_UNUSED(argv) ) +makeGamesWindow( GtkAppGlobals* apg ) { - GtkWidget* menubar = gtk_menu_bar_new(); - GtkWidget* fileMenu; + GtkWidget* window; - fileMenu = makeAddSubmenu( menubar, "File" ); - (void)createAddItem( fileMenu, "Tile values", - GTK_SIGNAL_FUNC(tile_values), globals ); - (void)createAddItem( fileMenu, "Game history", - GTK_SIGNAL_FUNC(game_history), globals ); -#ifdef TEXT_MODEL - (void)createAddItem( fileMenu, "Dump board", - GTK_SIGNAL_FUNC(dump_board), globals ); -#endif + window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); + g_signal_connect( G_OBJECT(window), "destroy", + G_CALLBACK(handle_destroy), apg ); - (void)createAddItem( fileMenu, "Final scores", - GTK_SIGNAL_FUNC(final_scores), globals ); + if ( !!apg->params->dbName ) { + gtk_window_set_title( GTK_WINDOW(window), apg->params->dbName ); + } + + GtkWidget* vbox = gtk_vbox_new( FALSE, 0 ); + gtk_container_add( GTK_CONTAINER(window), vbox ); + gtk_widget_show( vbox ); + GtkWidget* list = init_games_list( apg ); + apg->listWidget = list; + gtk_container_add( GTK_CONTAINER(vbox), list ); + + gtk_widget_show( list ); - (void)createAddItem( fileMenu, "New game", - GTK_SIGNAL_FUNC(new_game), globals ); - (void)createAddItem( fileMenu, "Game info", - GTK_SIGNAL_FUNC(game_info), globals ); + GSList* games = listGames( apg->params->pDb ); + for ( GSList* iter = games; !!iter; iter = iter->next ) { + sqlite3_int64* rowid = (sqlite3_int64*)iter->data; + onNewData( apg, *rowid, XP_TRUE ); + } + g_slist_free( games ); - (void)createAddItem( fileMenu, "Load game", - GTK_SIGNAL_FUNC(load_game), globals ); - (void)createAddItem( fileMenu, "Save game", - GTK_SIGNAL_FUNC(save_game), globals ); -#ifdef XWFEATURE_CHANGEDICT - (void)createAddItem( fileMenu, "Change dictionary", - GTK_SIGNAL_FUNC(change_dictionary), globals ); -#endif - (void)createAddItem( fileMenu, "Cancel trade", - GTK_SIGNAL_FUNC(handle_trade_cancel), globals ); + GtkWidget* hbox = gtk_hbox_new( FALSE, 0 ); + gtk_widget_show( hbox ); + gtk_container_add( GTK_CONTAINER(vbox), hbox ); - fileMenu = makeAddSubmenu( menubar, "Edit" ); + (void)addButton( "New game", hbox, G_CALLBACK(handle_newgame_button), apg ); + apg->openButton = addButton( "Open", hbox, + G_CALLBACK(handle_open_button), apg ); + apg->deleteButton = addButton( "Delete", hbox, + G_CALLBACK(handle_delete_button), apg ); + (void)addButton( "Quit", hbox, G_CALLBACK(handle_quit_button), apg ); + updateButtons( apg ); - (void)createAddItem( fileMenu, "Undo", - GTK_SIGNAL_FUNC(handle_undo), globals ); - (void)createAddItem( fileMenu, "Redo", - GTK_SIGNAL_FUNC(handle_redo), globals ); + gtk_widget_show( window ); + return window; +} -#ifdef FEATURE_TRAY_EDIT - (void)createAddItem( fileMenu, "Allow tray edit", - GTK_SIGNAL_FUNC(handle_trayEditToggle_on), globals ); - (void)createAddItem( fileMenu, "Dis-allow tray edit", - GTK_SIGNAL_FUNC(handle_trayEditToggle_off), globals ); -#endif - fileMenu = makeAddSubmenu( menubar, "Network" ); +static gint +freeGameGlobals( gpointer data ) +{ + LOG_FUNC(); + GtkGameGlobals* globals = (GtkGameGlobals*)data; + GtkAppGlobals* apg = globals->apg; + recordClosed( apg, globals ); + freeGlobals( globals ); + return 0; /* don't run again */ +} -#ifndef XWFEATURE_STANDALONE_ONLY - (void)createAddItem( fileMenu, "Resend", - GTK_SIGNAL_FUNC(handle_resend), globals ); -#ifdef XWFEATURE_COMMSACK - (void)createAddItem( fileMenu, "ack any", - GTK_SIGNAL_FUNC(handle_ack), globals ); -#endif -# ifdef DEBUG - (void)createAddItem( fileMenu, "Stats", - GTK_SIGNAL_FUNC(handle_commstats), globals ); -# endif -#endif -#ifdef MEM_DEBUG - (void)createAddItem( fileMenu, "Mem stats", - GTK_SIGNAL_FUNC(handle_memstats), globals ); -#endif - - /* (void)createAddItem( fileMenu, "Print board", */ - /* GTK_SIGNAL_FUNC(handle_print_board), globals ); */ - - /* listAllGames( menubar, argc, argv, globals ); */ - - gtk_widget_show( menubar ); - - return menubar; -} /* makeMenus */ +void +windowDestroyed( GtkGameGlobals* globals ) +{ + /* schedule to run after we're back to main loop */ + (void)g_idle_add( freeGameGlobals, globals ); +} static void -disenable_buttons( GtkAppGlobals* globals ) +onNewData( GtkAppGlobals* apg, sqlite3_int64 rowid, XP_Bool isNew ) { - XP_Bool canFlip = 1 < board_visTileCount( globals->cGlobals.game.board ); - gtk_widget_set_sensitive( globals->flip_button, canFlip ); - - XP_Bool canToggle = board_canTogglePending( globals->cGlobals.game.board ); - gtk_widget_set_sensitive( globals->toggle_undo_button, canToggle ); - - XP_Bool canHint = board_canHint( globals->cGlobals.game.board ); - gtk_widget_set_sensitive( globals->prevhint_button, canHint ); - gtk_widget_set_sensitive( globals->nexthint_button, canHint ); - -#ifdef XWFEATURE_CHAT - XP_Bool canChat = !!globals->cGlobals.game.comms - && comms_canChat( globals->cGlobals.game.comms ); - gtk_widget_set_sensitive( globals->chat_button, canChat ); -#endif + GameInfo gib; + if ( getGameInfo( apg->params->pDb, rowid, &gib ) ) { + add_to_list( apg->listWidget, rowid, isNew, &gib ); + } } static gboolean -handle_flip_button( GtkWidget* XP_UNUSED(widget), gpointer _globals ) +gtk_app_socket_proc( GIOChannel* source, GIOCondition condition, gpointer data ) { - GtkAppGlobals* globals = (GtkAppGlobals*)_globals; - if ( board_flip( globals->cGlobals.game.board ) ) { - board_draw( globals->cGlobals.game.board ); - } - return TRUE; -} /* handle_flip_button */ - -static gboolean -handle_value_button( GtkWidget* XP_UNUSED(widget), gpointer closure ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)closure; - if ( board_toggle_showValues( globals->cGlobals.game.board ) ) { - board_draw( globals->cGlobals.game.board ); - } - return TRUE; -} /* handle_value_button */ - -static void -handle_hint_button( GtkAppGlobals* globals, XP_Bool prev ) -{ - XP_Bool redo; - if ( board_requestHint( globals->cGlobals.game.board, -#ifdef XWFEATURE_SEARCHLIMIT - XP_FALSE, -#endif - prev, &redo ) ) { - board_draw( globals->cGlobals.game.board ); - disenable_buttons( globals ); - } -} /* handle_hint_button */ - -static void -handle_prevhint_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - handle_hint_button( globals, XP_TRUE ); -} /* handle_prevhint_button */ - -static void -handle_nexthint_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - handle_hint_button( globals, XP_FALSE ); -} /* handle_nexthint_button */ - -static void -handle_nhint_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - XP_Bool redo; - - board_resetEngine( globals->cGlobals.game.board ); - if ( board_requestHint( globals->cGlobals.game.board, -#ifdef XWFEATURE_SEARCHLIMIT - XP_TRUE, -#endif - XP_FALSE, &redo ) ) { - board_draw( globals->cGlobals.game.board ); - } -} /* handle_nhint_button */ - -static void -handle_colors_button( GtkWidget* XP_UNUSED(widget), - GtkAppGlobals* XP_UNUSED(globals) ) -{ -/* XP_Bool oldVal = board_getShowColors( globals->cGlobals.game.board ); */ -/* if ( board_setShowColors( globals->cGlobals.game.board, !oldVal ) ) { */ -/* board_draw( globals->cGlobals.game.board ); */ -/* } */ -} /* handle_colors_button */ - -static void -handle_juggle_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - if ( board_juggleTray( globals->cGlobals.game.board ) ) { - board_draw( globals->cGlobals.game.board ); - } -} /* handle_juggle_button */ - -static void -handle_undo_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - if ( server_handleUndo( globals->cGlobals.game.server, 0 ) ) { - board_draw( globals->cGlobals.game.board ); - } -} /* handle_undo_button */ - -static void -handle_redo_button( GtkWidget* XP_UNUSED(widget), - GtkAppGlobals* XP_UNUSED(globals) ) -{ -} /* handle_redo_button */ - -static void -handle_toggle_undo( GtkWidget* XP_UNUSED(widget), - GtkAppGlobals* globals ) -{ - BoardCtxt* board = globals->cGlobals.game.board; - if ( board_redoReplacedTiles( board ) || board_replaceTiles( board ) ) { - board_draw( board ); - } -} - -static void -handle_trade_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - if ( board_beginTrade( globals->cGlobals.game.board ) ) { - board_draw( globals->cGlobals.game.board ); - disenable_buttons( globals ); - } -} /* handle_juggle_button */ - -static void -handle_done_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - if ( board_commitTurn( globals->cGlobals.game.board ) ) { - board_draw( globals->cGlobals.game.board ); - disenable_buttons( globals ); - } -} /* handle_done_button */ - -static void -setZoomButtons( GtkAppGlobals* globals, XP_Bool* inOut ) -{ - gtk_widget_set_sensitive( globals->zoomin_button, inOut[0] ); - gtk_widget_set_sensitive( globals->zoomout_button, inOut[1] ); -} - -static void -handle_zoomin_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - XP_Bool inOut[2]; - if ( board_zoom( globals->cGlobals.game.board, 1, inOut ) ) { - board_draw( globals->cGlobals.game.board ); - setZoomButtons( globals, inOut ); - } -} /* handle_done_button */ - -static void -handle_zoomout_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - XP_Bool inOut[2]; - if ( board_zoom( globals->cGlobals.game.board, -1, inOut ) ) { - board_draw( globals->cGlobals.game.board ); - setZoomButtons( globals, inOut ); - } -} /* handle_done_button */ - -#ifdef XWFEATURE_CHAT -static void -handle_chat_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - gchar* msg = gtkGetChatMessage( globals ); - if ( NULL != msg ) { - server_sendChat( globals->cGlobals.game.server, msg ); - g_free( msg ); - } -} -#endif - -static void -scroll_value_changed( GtkAdjustment *adj, GtkAppGlobals* globals ) -{ - XP_U16 newValue; - gfloat newValueF = adj->value; - - /* XP_ASSERT( newValueF >= 0.0 */ - /* && newValueF <= globals->cGlobals.params->nHidden ); */ - newValue = (XP_U16)newValueF; - - if ( board_setYOffset( globals->cGlobals.game.board, newValue ) ) { - board_draw( globals->cGlobals.game.board ); - } -} /* scroll_value_changed */ - -static void -handle_grid_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - globals->gridOn = !globals->gridOn; - - board_invalAll( globals->cGlobals.game.board ); - board_draw( globals->cGlobals.game.board ); -} /* handle_grid_button */ - -static void -handle_hide_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - BoardCtxt* board; - XP_Bool draw = XP_FALSE; - - if ( globals->cGlobals.params->nHidden > 0 ) { - gint nRows = globals->cGlobals.params->gi.boardSize; - globals->adjustment->page_size = nRows; - globals->adjustment->value = 0.0; - - gtk_signal_emit_by_name( GTK_OBJECT(globals->adjustment), "changed" ); - gtk_adjustment_value_changed( GTK_ADJUSTMENT(globals->adjustment) ); - } - - board = globals->cGlobals.game.board; - if ( TRAY_REVEALED == board_getTrayVisState( board ) ) { - draw = board_hideTray( board ); - } else { - draw = board_showTray( board ); - } - if ( draw ) { - board_draw( board ); - } -} /* handle_hide_button */ - -static void -handle_commit_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) -{ - if ( board_commitTurn( globals->cGlobals.game.board ) ) { - board_draw( globals->cGlobals.game.board ); - } -} /* handle_commit_button */ - -static void -gtkUserError( GtkAppGlobals* globals, const char* format, ... ) -{ - char buf[512]; - va_list ap; - - va_start( ap, format ); - - vsprintf( buf, format, ap ); - - (void)gtkask( globals->window, buf, GTK_BUTTONS_OK ); - - va_end(ap); -} /* gtkUserError */ - -static VTableMgr* -gtk_util_getVTManager(XW_UtilCtxt* uc) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - return globals->cGlobals.params->vtMgr; -} /* linux_util_getVTManager */ - -static XP_S16 -gtk_util_userPickTileBlank( XW_UtilCtxt* uc, XP_U16 playerNum, - const XP_UCHAR** texts, XP_U16 nTiles ) -{ - XP_S16 chosen; - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - XP_UCHAR* name = globals->cGlobals.params->gi.players[playerNum].name; - - chosen = gtkletterask( NULL, XP_FALSE, name, nTiles, texts ); - return chosen; -} - -static XP_S16 -gtk_util_userPickTileTray( XW_UtilCtxt* uc, const PickInfo* pi, - XP_U16 playerNum, const XP_UCHAR** texts, - XP_U16 nTiles ) -{ - XP_S16 chosen; - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - XP_UCHAR* name = globals->cGlobals.params->gi.players[playerNum].name; - - chosen = gtkletterask( pi, XP_TRUE, name, nTiles, texts ); - return chosen; -} /* gtk_util_userPickTile */ - -static XP_Bool -gtk_util_askPassword( XW_UtilCtxt* XP_UNUSED(uc), const XP_UCHAR* name, - XP_UCHAR* buf, XP_U16* len ) -{ - XP_Bool ok = gtkpasswdask( name, buf, len ); - return ok; -} /* gtk_util_askPassword */ - -static void -setCtrlsForTray( GtkAppGlobals* XP_UNUSED(globals) ) -{ -#if 0 - XW_TrayVisState state = - board_getTrayVisState( globals->cGlobals.game.board ); - XP_S16 nHidden = globals->cGlobals.params->nHidden; - - if ( nHidden != 0 ) { - XP_U16 pageSize = nRows; - - if ( state == TRAY_HIDDEN ) { /* we recover what tray covers */ - nHidden -= GTK_TRAY_HT_ROWS; - } - if ( nHidden > 0 ) { - pageSize -= nHidden; - } - globals->adjustment->page_size = pageSize; - - globals->adjustment->value = - board_getYOffset( globals->cGlobals.game.board ); - gtk_signal_emit_by_name( GTK_OBJECT(globals->adjustment), "changed" ); - } -#endif -} /* setCtrlsForTray */ - -static void -gtk_util_trayHiddenChange( XW_UtilCtxt* uc, XW_TrayVisState XP_UNUSED(state), - XP_U16 XP_UNUSED(nVisibleRows) ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - setCtrlsForTray( globals ); -} /* gtk_util_trayHiddenChange */ - -static void -gtk_util_yOffsetChange( XW_UtilCtxt* uc, XP_U16 maxOffset, - XP_U16 XP_UNUSED(oldOffset), - XP_U16 newOffset ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - if ( !!globals->adjustment ) { - gint nRows = globals->cGlobals.params->gi.boardSize; - globals->adjustment->page_size = nRows - maxOffset; - globals->adjustment->value = newOffset; - gtk_adjustment_value_changed( GTK_ADJUSTMENT(globals->adjustment) ); - } -} /* gtk_util_yOffsetChange */ - -static void -gtkShowFinalScores( const GtkAppGlobals* globals ) -{ - XWStreamCtxt* stream; - XP_UCHAR* text; - const CommonGlobals* cGlobals = &globals->cGlobals; - - stream = mem_stream_make( MPPARM(cGlobals->params->util->mpool) - cGlobals->params->vtMgr, - NULL, CHANNEL_NONE, NULL ); - server_writeFinalScores( cGlobals->game.server, stream ); - - text = strFromStream( stream ); - stream_destroy( stream ); - - XP_U16 timeout = cGlobals->manualFinal? 0 : 500; - (void)gtkask_timeout( globals->window, text, GTK_BUTTONS_OK, timeout ); - - free( text ); -} /* gtkShowFinalScores */ - -static void -gtk_util_informMove( XW_UtilCtxt* uc, XWStreamCtxt* expl, - XWStreamCtxt* words ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - char* question = strFromStream( !!words? words : expl ); - (void)gtkask( globals->window, question, GTK_BUTTONS_OK ); - free( question ); -} - -static void -gtk_util_informUndo( XW_UtilCtxt* uc ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - (void)gtkask_timeout( globals->window, "Remote player undid a move", - GTK_BUTTONS_OK, 500 ); -} - -static void -gtk_util_notifyGameOver( XW_UtilCtxt* uc, XP_S16 quitter ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - CommonGlobals* cGlobals = &globals->cGlobals; - - if ( cGlobals->params->printHistory ) { - catGameHistory( cGlobals ); - } - - catFinalScores( cGlobals, quitter ); - - if ( cGlobals->params->quitAfter >= 0 ) { - sleep( cGlobals->params->quitAfter ); - quit(); - } else if ( cGlobals->params->undoWhenDone ) { - server_handleUndo( cGlobals->game.server, 0 ); - board_draw( cGlobals->game.board ); - } else if ( !cGlobals->params->skipGameOver ) { - gtkShowFinalScores( globals ); - } -} /* gtk_util_notifyGameOver */ - -static void -gtk_util_informNetDict( XW_UtilCtxt* uc, XP_LangCode XP_UNUSED(lang), - const XP_UCHAR* oldName, - const XP_UCHAR* newName, const XP_UCHAR* newSum, - XWPhoniesChoice phoniesAction ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - gchar buf[512]; - - int offset = snprintf( buf, VSIZE(buf), - "dict changing from %s to %s (sum=%s).", - oldName, newName, newSum ); - if ( PHONIES_DISALLOW == phoniesAction ) { - snprintf( &buf[offset], VSIZE(buf)-offset, "%s", - "\nPHONIES_DISALLOW is set so this may lead to some surprises." ); - } - (void)gtkask( globals->window, buf, GTK_BUTTONS_OK ); -} - -/* define this to prevent user events during debugging from stopping the engine */ -/* #define DONT_ABORT_ENGINE */ - -#ifdef XWFEATURE_HILITECELL -static XP_Bool -gtk_util_hiliteCell( XW_UtilCtxt* uc, XP_U16 col, XP_U16 row ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; -#ifndef DONT_ABORT_ENGINE - gboolean pending; -#endif - - board_hiliteCellAt( globals->cGlobals.game.board, col, row ); - if ( globals->cGlobals.params->sleepOnAnchor ) { - usleep( 10000 ); - } - -#ifdef DONT_ABORT_ENGINE - return XP_TRUE; /* keep going */ -#else - pending = gdk_events_pending(); - if ( pending ) { - XP_DEBUGF( "gtk_util_hiliteCell=>%d", pending ); - } - return !pending; -#endif -} /* gtk_util_hiliteCell */ -#endif - -static XP_Bool -gtk_util_altKeyDown( XW_UtilCtxt* uc ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - return globals->altKeyDown; -} - -static XP_Bool -gtk_util_engineProgressCallback( XW_UtilCtxt* XP_UNUSED(uc) ) -{ -#ifdef DONT_ABORT_ENGINE - return XP_TRUE; /* keep going */ -#else - gboolean pending = gdk_events_pending(); - -/* XP_DEBUGF( "gdk_events_pending returned %d\n", pending ); */ - - return !pending; -#endif -} /* gtk_util_engineProgressCallback */ - -static void -cancelTimer( GtkAppGlobals* globals, XWTimerReason why ) -{ - guint src = globals->timerSources[why-1]; - if ( src != 0 ) { - g_source_remove( src ); - globals->timerSources[why-1] = 0; - } -} /* cancelTimer */ - -static gint -pen_timer_func( gpointer data ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)data; - - if ( linuxFireTimer( &globals->cGlobals, TIMER_PENDOWN ) ) { - board_draw( globals->cGlobals.game.board ); - } - - return XP_FALSE; -} /* pentimer_idle_func */ - -static gint -score_timer_func( gpointer data ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)data; - - if ( linuxFireTimer( &globals->cGlobals, TIMER_TIMERTICK ) ) { - board_draw( globals->cGlobals.game.board ); - } - - return XP_FALSE; -} /* score_timer_func */ - -#ifndef XWFEATURE_STANDALONE_ONLY -static gint -comms_timer_func( gpointer data ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)data; - - if ( linuxFireTimer( &globals->cGlobals, TIMER_COMMS ) ) { - board_draw( globals->cGlobals.game.board ); - } - - return (gint)0; -} -#endif - -#ifdef XWFEATURE_SLOW_ROBOT -static gint -slowrob_timer_func( gpointer data ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)data; - - if ( linuxFireTimer( &globals->cGlobals, TIMER_SLOWROBOT ) ) { - board_draw( globals->cGlobals.game.board ); - } - - return (gint)0; -} -#endif - -static void -gtk_util_setTimer( XW_UtilCtxt* uc, XWTimerReason why, - XP_U16 XP_UNUSED_STANDALONE(when), - XWTimerProc proc, void* closure ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - guint newSrc; - - cancelTimer( globals, why ); - - if ( why == TIMER_PENDOWN ) { - if ( 0 != globals->timerSources[why-1] ) { - g_source_remove( globals->timerSources[why-1] ); - } - newSrc = g_timeout_add( 1000, pen_timer_func, globals ); - } else if ( why == TIMER_TIMERTICK ) { - /* one second */ - globals->scoreTimerInterval = 100 * 10000; - - (void)gettimeofday( &globals->scoreTv, NULL ); - - newSrc = g_timeout_add( 1000, score_timer_func, globals ); -#ifndef XWFEATURE_STANDALONE_ONLY - } else if ( why == TIMER_COMMS ) { - newSrc = g_timeout_add( 1000 * when, comms_timer_func, globals ); -#endif -#ifdef XWFEATURE_SLOW_ROBOT - } else if ( why == TIMER_SLOWROBOT ) { - newSrc = g_timeout_add( 1000 * when, slowrob_timer_func, globals ); -#endif - } else { - XP_ASSERT( 0 ); - } - - globals->cGlobals.timerInfo[why].proc = proc; - globals->cGlobals.timerInfo[why].closure = closure; - XP_ASSERT( newSrc != 0 ); - globals->timerSources[why-1] = newSrc; -} /* gtk_util_setTimer */ - -static void -gtk_util_clearTimer( XW_UtilCtxt* uc, XWTimerReason why ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - globals->cGlobals.timerInfo[why].proc = NULL; -} - -static gint -idle_func( gpointer data ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)data; -/* XP_DEBUGF( "idle_func called\n" ); */ - - /* remove before calling server_do. If server_do puts up a dialog that - calls gtk_main, then this idle proc will also apply to that event loop - and bad things can happen. So kill the idle proc asap. */ - gtk_idle_remove( globals->idleID ); - - if ( server_do( globals->cGlobals.game.server ) ) { - if ( !!globals->cGlobals.game.board ) { - board_draw( globals->cGlobals.game.board ); - } - } - return 0; /* 0 will stop it from being called again */ -} /* idle_func */ - -static void -gtk_util_requestTime( XW_UtilCtxt* uc ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - globals->idleID = gtk_idle_add( idle_func, globals ); -} /* gtk_util_requestTime */ - -static XP_Bool -gtk_util_warnIllegalWord( XW_UtilCtxt* uc, BadWordInfo* bwi, XP_U16 player, - XP_Bool turnLost ) -{ - XP_Bool result; - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - char buf[300]; - - if ( turnLost ) { - char wordsBuf[256]; - XP_U16 i; - XP_UCHAR* name = globals->cGlobals.params->gi.players[player].name; - XP_ASSERT( !!name ); - - for ( i = 0, wordsBuf[0] = '\0'; ; ) { - char wordBuf[18]; - sprintf( wordBuf, "\"%s\"", bwi->words[i] ); - strcat( wordsBuf, wordBuf ); - if ( ++i == bwi->nWords ) { + if ( 0 != (G_IO_IN & condition) ) { + GtkAppGlobals* apg = (GtkAppGlobals*)data; + int socket = g_io_channel_unix_get_fd( source ); + GList* iter; + for ( iter = apg->sources; !!iter; iter = iter->next ) { + SourceData* sd = (SourceData*)iter->data; + if ( sd->channel == source ) { + (*sd->proc)( sd->procClosure, socket ); break; } - strcat( wordsBuf, ", " ); } - - sprintf( buf, "Player %d (%s) played illegal word[s] %s; loses turn.", - player+1, name, wordsBuf ); - - if ( globals->cGlobals.params->skipWarnings ) { - XP_LOGF( "%s", buf ); - } else { - gtkUserError( globals, buf ); - } - result = XP_TRUE; - } else { - XP_ASSERT( bwi->nWords == 1 ); - sprintf( buf, "Word \"%s\" not in the current dictionary (%s). " - "Use it anyway?", bwi->words[0], bwi->dictName ); - result = gtkask( globals->window, buf, GTK_BUTTONS_YES_NO ); + XP_ASSERT( !!iter ); /* didn't fail to find it */ } - - return result; -} /* gtk_util_warnIllegalWord */ - -static void -gtk_util_remSelected( XW_UtilCtxt* uc ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - XWStreamCtxt* stream; - XP_UCHAR* text; - - stream = mem_stream_make( MEMPOOL - globals->cGlobals.params->vtMgr, - globals, CHANNEL_NONE, NULL ); - board_formatRemainingTiles( globals->cGlobals.game.board, stream ); - text = strFromStream( stream ); - stream_destroy( stream ); - - (void)gtkask( globals->window, text, GTK_BUTTONS_OK ); - free( text ); -} - -#ifndef XWFEATURE_STANDALONE_ONLY -static XWStreamCtxt* -gtk_util_makeStreamFromAddr(XW_UtilCtxt* uc, XP_PlayerAddr channelNo ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - - XWStreamCtxt* stream = mem_stream_make( MEMPOOL - globals->cGlobals.params->vtMgr, - &globals->cGlobals, channelNo, - sendOnClose ); - return stream; -} /* gtk_util_makeStreamFromAddr */ - -#ifdef XWFEATURE_CHAT -static void -gtk_util_showChat( XW_UtilCtxt* uc, const XP_UCHAR* const msg ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - (void)gtkask( globals->window, msg, GTK_BUTTONS_OK ); -} -#endif -#endif - -#ifdef XWFEATURE_SEARCHLIMIT -static XP_Bool -gtk_util_getTraySearchLimits( XW_UtilCtxt* XP_UNUSED(uc), - XP_U16* XP_UNUSED(min), XP_U16* max ) -{ - *max = askNTiles( MAX_TRAY_TILES, *max ); - return XP_TRUE; -} -#endif - -#ifndef XWFEATURE_MINIWIN -static void -gtk_util_bonusSquareHeld( XW_UtilCtxt* uc, XWBonusType bonus ) -{ - LOG_FUNC(); - XP_USE( uc ); - XP_USE( bonus ); + return TRUE; } static void -gtk_util_playerScoreHeld( XW_UtilCtxt* uc, XP_U16 player ) -{ - LOG_FUNC(); - - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - - XP_UCHAR scoreExpl[48] = {0}; - XP_U16 explLen = sizeof(scoreExpl); - - if ( model_getPlayersLastScore( globals->cGlobals.game.model, - player, scoreExpl, &explLen ) ) { - XP_LOGF( "got: %s", scoreExpl ); - } -} -#endif - -#ifdef XWFEATURE_BOARDWORDS -static void -gtk_util_cellSquareHeld( XW_UtilCtxt* uc, XWStreamCtxt* words ) -{ - XP_USE( uc ); - catOnClose( words, NULL ); - fprintf( stderr, "\n" ); -} -#endif - -static void -gtk_util_userError( XW_UtilCtxt* uc, UtilErrID id ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - XP_Bool silent; - const XP_UCHAR* message = linux_getErrString( id, &silent ); - - XP_LOGF( "%s(%d)", __func__, id ); - - if ( silent ) { - XP_LOGF( "%s", message ); - } else { - gtkUserError( globals, message ); - } -} /* gtk_util_userError */ - -static XP_Bool -gtk_util_userQuery( XW_UtilCtxt* uc, UtilQueryID id, - XWStreamCtxt* stream ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - XP_Bool result; - char* question; - XP_Bool freeMe = XP_FALSE; - GtkButtonsType buttons = GTK_BUTTONS_YES_NO; - - switch( id ) { - - case QUERY_COMMIT_TURN: - question = strFromStream( stream ); - freeMe = XP_TRUE; - break; - case QUERY_ROBOT_TRADE: - question = strFromStream( stream ); - freeMe = XP_TRUE; - buttons = GTK_BUTTONS_OK; - break; - - default: - XP_ASSERT( 0 ); - return XP_FALSE; - } - - result = gtkask( globals->window, question, buttons ); - - if ( freeMe ) { - free( question ); - } - - return result; -} /* gtk_util_userQuery */ - -static XP_Bool -gtk_util_confirmTrade( XW_UtilCtxt* uc, - const XP_UCHAR** tiles, XP_U16 nTiles ) -{ - GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; - char question[256]; - formatConfirmTrade( tiles, nTiles, question, sizeof(question) ); - return gtkask( globals->window, question, GTK_BUTTONS_YES_NO ); -} - -static GtkWidget* -makeShowButtonFromBitmap( void* closure, const gchar* filename, - const gchar* alt, GCallback func ) -{ - GtkWidget* widget; - GtkWidget* button; - - if ( file_exists( filename ) ) { - widget = gtk_image_new_from_file( filename ); - } else { - widget = gtk_label_new( alt ); - } - gtk_widget_show( widget ); - - button = gtk_button_new(); - gtk_container_add (GTK_CONTAINER (button), widget ); - gtk_widget_show (button); - - if ( func != NULL ) { - g_signal_connect( GTK_OBJECT(button), "clicked", func, closure ); - } - - return button; -} /* makeShowButtonFromBitmap */ - -static GtkWidget* -makeVerticalBar( GtkAppGlobals* globals, GtkWidget* XP_UNUSED(window) ) -{ - GtkWidget* vbox; - GtkWidget* button; - - vbox = gtk_vbutton_box_new(); - - button = makeShowButtonFromBitmap( globals, "../flip.xpm", "f", - G_CALLBACK(handle_flip_button) ); - gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); - globals->flip_button = button; - - button = makeShowButtonFromBitmap( globals, "../value.xpm", "v", - G_CALLBACK(handle_value_button) ); - gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); - - button = makeShowButtonFromBitmap( globals, "../hint.xpm", "?-", - G_CALLBACK(handle_prevhint_button) ); - globals->prevhint_button = button; - gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); - button = makeShowButtonFromBitmap( globals, "../hint.xpm", "?+", - G_CALLBACK(handle_nexthint_button) ); - globals->nexthint_button = button; - gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); - - button = makeShowButtonFromBitmap( globals, "../hintNum.xpm", "n", - G_CALLBACK(handle_nhint_button) ); - gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); - - button = makeShowButtonFromBitmap( globals, "../colors.xpm", "c", - G_CALLBACK(handle_colors_button) ); - gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); - - /* undo and redo buttons */ - button = makeShowButtonFromBitmap( globals, "../undo.xpm", "U", - G_CALLBACK(handle_undo_button) ); - gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); - button = makeShowButtonFromBitmap( globals, "../redo.xpm", "R", - G_CALLBACK(handle_redo_button) ); - gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); - - button = makeShowButtonFromBitmap( globals, "", "u/r", - G_CALLBACK(handle_toggle_undo) ); - globals->toggle_undo_button = button; - gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); - - /* the four buttons that on palm are beside the tray */ - button = makeShowButtonFromBitmap( globals, "../juggle.xpm", "j", - G_CALLBACK(handle_juggle_button) ); - gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); - - button = makeShowButtonFromBitmap( globals, "../trade.xpm", "t", - G_CALLBACK(handle_trade_button) ); - gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); - button = makeShowButtonFromBitmap( globals, "../done.xpm", "d", - G_CALLBACK(handle_done_button) ); - gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); - button = makeShowButtonFromBitmap( globals, "../done.xpm", "+", - G_CALLBACK(handle_zoomin_button) ); - globals->zoomin_button = button; - gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); - button = makeShowButtonFromBitmap( globals, "../done.xpm", "-", - G_CALLBACK(handle_zoomout_button) ); - globals->zoomout_button = button; - gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); -#ifdef XWFEATURE_CHAT - button = makeShowButtonFromBitmap( globals, "", "chat", - G_CALLBACK(handle_chat_button) ); - globals->chat_button = button; - gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); -#endif - - gtk_widget_show( vbox ); - return vbox; -} /* makeVerticalBar */ - -static GtkWidget* -makeButtons( GtkAppGlobals* globals, int XP_UNUSED(argc), - char** XP_UNUSED(argv) ) -{ - short i; - GtkWidget* hbox; - GtkWidget* button; - - struct { - char* name; - GCallback func; - } buttons[] = { - /* { "Flip", handle_flip_button }, */ - { "Grid", G_CALLBACK(handle_grid_button) }, - { "Hide", G_CALLBACK(handle_hide_button) }, - { "Commit", G_CALLBACK(handle_commit_button) }, - }; - - hbox = gtk_hbox_new( FALSE, 0 ); - - for ( i = 0; i < sizeof(buttons)/sizeof(*buttons); ++i ) { - button = gtk_button_new_with_label( buttons[i].name ); - gtk_widget_show( button ); - g_signal_connect( GTK_OBJECT(button), "clicked", - G_CALLBACK(buttons[i].func), globals ); - - gtk_box_pack_start( GTK_BOX(hbox), button, FALSE, TRUE, 0); - } - - gtk_widget_show( hbox ); - return hbox; -} /* makeButtons */ - -static void -setupGtkUtilCallbacks( GtkAppGlobals* globals, XW_UtilCtxt* util ) -{ - util->vtable->m_util_userError = gtk_util_userError; - util->vtable->m_util_userQuery = gtk_util_userQuery; - util->vtable->m_util_confirmTrade = gtk_util_confirmTrade; - util->vtable->m_util_getVTManager = gtk_util_getVTManager; - util->vtable->m_util_userPickTileBlank = gtk_util_userPickTileBlank; - util->vtable->m_util_userPickTileTray = gtk_util_userPickTileTray; - util->vtable->m_util_askPassword = gtk_util_askPassword; - util->vtable->m_util_trayHiddenChange = gtk_util_trayHiddenChange; - util->vtable->m_util_yOffsetChange = gtk_util_yOffsetChange; - util->vtable->m_util_informMove = gtk_util_informMove; - util->vtable->m_util_informUndo = gtk_util_informUndo; - util->vtable->m_util_notifyGameOver = gtk_util_notifyGameOver; - util->vtable->m_util_informNetDict = gtk_util_informNetDict; -#ifdef XWFEATURE_HILITECELL - util->vtable->m_util_hiliteCell = gtk_util_hiliteCell; -#endif - util->vtable->m_util_altKeyDown = gtk_util_altKeyDown; - util->vtable->m_util_engineProgressCallback = - gtk_util_engineProgressCallback; - util->vtable->m_util_setTimer = gtk_util_setTimer; - util->vtable->m_util_clearTimer = gtk_util_clearTimer; - util->vtable->m_util_requestTime = gtk_util_requestTime; - util->vtable->m_util_warnIllegalWord = gtk_util_warnIllegalWord; - util->vtable->m_util_remSelected = gtk_util_remSelected; -#ifndef XWFEATURE_STANDALONE_ONLY - util->vtable->m_util_makeStreamFromAddr = gtk_util_makeStreamFromAddr; -#endif -#ifdef XWFEATURE_CHAT - util->vtable->m_util_showChat = gtk_util_showChat; -#endif -#ifdef XWFEATURE_SEARCHLIMIT - util->vtable->m_util_getTraySearchLimits = gtk_util_getTraySearchLimits; -#endif - -#ifndef XWFEATURE_MINIWIN - util->vtable->m_util_bonusSquareHeld = gtk_util_bonusSquareHeld; - util->vtable->m_util_playerScoreHeld = gtk_util_playerScoreHeld; -#endif -#ifdef XWFEATURE_BOARDWORDS - util->vtable->m_util_cellSquareHeld = gtk_util_cellSquareHeld; -#endif - - util->closure = globals; -} /* setupGtkUtilCallbacks */ - -#ifndef XWFEATURE_STANDALONE_ONLY -static gboolean -newConnectionInput( GIOChannel *source, - GIOCondition condition, - gpointer data ) -{ - gboolean keepSource = TRUE; - int sock = g_io_channel_unix_get_fd( source ); - GtkAppGlobals* globals = (GtkAppGlobals*)data; - - XP_LOGF( "%s(%p):condition = 0x%x", __func__, source, (int)condition ); - -/* XP_ASSERT( sock == globals->cGlobals.socket ); */ - - if ( (condition & G_IO_IN) != 0 ) { - ssize_t nRead; - unsigned char buf[512]; - CommsAddrRec* addrp = NULL; -#if defined XWFEATURE_IP_DIRECT || defined XWFEATURE_SMS - CommsAddrRec addr; -#endif - - switch ( comms_getConType( globals->cGlobals.game.comms ) ) { -#ifdef XWFEATURE_RELAY - case COMMS_CONN_RELAY: - XP_ASSERT( globals->cGlobals.socket == sock ); - nRead = linux_relay_receive( &globals->cGlobals, buf, sizeof(buf) ); - break; -#endif -#ifdef XWFEATURE_BLUETOOTH - case COMMS_CONN_BT: - nRead = linux_bt_receive( sock, buf, sizeof(buf) ); - break; -#endif -#ifdef XWFEATURE_SMS - case COMMS_CONN_SMS: - addrp = &addr; - nRead = linux_sms_receive( &globals->cGlobals, sock, - buf, sizeof(buf), addrp ); - break; -#endif -#ifdef XWFEATURE_IP_DIRECT - case COMMS_CONN_IP_DIRECT: - addrp = &addr; - nRead = linux_udp_receive( sock, buf, sizeof(buf), addrp, &globals->cGlobals ); - break; -#endif - default: - XP_ASSERT( 0 ); - } - - if ( !globals->dropIncommingMsgs && nRead > 0 ) { - XWStreamCtxt* inboundS; - XP_Bool redraw = XP_FALSE; - - inboundS = stream_from_msgbuf( &globals->cGlobals, buf, nRead ); - if ( !!inboundS ) { - XP_LOGF( "%s: got %d bytes", __func__, nRead ); - if ( comms_checkIncomingStream( globals->cGlobals.game.comms, - inboundS, addrp ) ) { - redraw = - server_receiveMessage(globals->cGlobals.game.server, - inboundS ); - if ( redraw ) { - saveGame( &globals->cGlobals ); - } - } - 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 ) { - gtk_util_requestTime( globals->cGlobals.params->util ); - } else { - redraw = server_do( globals->cGlobals.game.server ); - } - if ( redraw ) { - board_draw( globals->cGlobals.game.board ); - } - } else { - XP_LOGF( "errno from read: %d/%s", errno, strerror(errno) ); - } - } - - if ( (condition & (G_IO_HUP | G_IO_ERR)) != 0 ) { - XP_LOGF( "dropping socket %d", sock ); - close( sock ); -#ifdef XWFEATURE_RELAY - globals->cGlobals.socket = -1; -#endif - if ( 0 ) { -#ifdef XWFEATURE_BLUETOOTH - } else if ( COMMS_CONN_BT == globals->cGlobals.params->conType ) { - linux_bt_socketclosed( &globals->cGlobals, sock ); -#endif -#ifdef XWFEATURE_IP_DIRECT - } else if ( COMMS_CONN_IP_DIRECT == globals->cGlobals.params->conType ) { - linux_udp_socketclosed( &globals->cGlobals, sock ); -#endif - } - keepSource = FALSE; /* remove the event source */ - } - - return keepSource; /* FALSE means to remove event source */ -} /* newConnectionInput */ - -typedef struct SockInfo { - GIOChannel* channel; - guint watch; - int socket; -} SockInfo; - -static void -gtk_socket_changed( void* closure, int oldSock, int newSock, void** storage ) +gtkSocketChanged( void* closure, int newSock, int XP_UNUSED(oldSock), + SockReceiver proc, void* procClosure ) { +#if 1 + GtkAppGlobals* apg = (GtkAppGlobals*)closure; + SourceData* sd = g_malloc( sizeof(*sd) ); + sd->channel = g_io_channel_unix_new( newSock ); + sd->watch = g_io_add_watch( sd->channel, G_IO_IN | G_IO_ERR, + gtk_app_socket_proc, apg ); + sd->proc = proc; + sd->procClosure = procClosure; + apg->sources = g_list_append( apg->sources, sd ); +#else GtkAppGlobals* globals = (GtkAppGlobals*)closure; SockInfo* info = (SockInfo*)*storage; XP_LOGF( "%s(old:%d; new:%d)", __func__, oldSock, newSock ); @@ -2204,250 +431,149 @@ gtk_socket_changed( void* closure, int oldSock, int newSock, void** storage ) if ( (comms != NULL) && (comms_getConType(comms) == COMMS_CONN_BT) ) { comms_resendAll( comms, XP_FALSE ); } +#endif LOG_RETURN_VOID(); } /* gtk_socket_changed */ -static gboolean -acceptorInput( GIOChannel* source, GIOCondition condition, gpointer data ) -{ - gboolean keepSource; - CommonGlobals* globals = (CommonGlobals*)data; - LOG_FUNC(); - - if ( (condition & G_IO_IN) != 0 ) { - int listener = g_io_channel_unix_get_fd( source ); - XP_LOGF( "%s: input on socket %d", __func__, listener ); - keepSource = (*globals->acceptor)( listener, data ); - } else { - keepSource = FALSE; - } - - return keepSource; -} /* acceptorInput */ - static void -gtk_socket_acceptor( int listener, Acceptor func, CommonGlobals* globals, - void** storage ) +gtkGotBuf( void* closure, const XP_U8* buf, XP_U16 len ) { - SockInfo* info = (SockInfo*)*storage; - GIOChannel* channel; - guint watch; - LOG_FUNC(); + GtkAppGlobals* apg = (GtkAppGlobals*)closure; + XP_U32 gameToken; + XP_ASSERT( sizeof(gameToken) < len ); + gameToken = ntohl(*(XP_U32*)&buf[0]); + buf += sizeof(gameToken); + len -= sizeof(gameToken); - if ( listener == -1 ) { - XP_ASSERT( !!globals->acceptor ); - globals->acceptor = NULL; - XP_ASSERT( !!info ); -#ifdef DEBUG - int oldSock = info->socket; -#endif - g_source_remove( info->watch ); - g_io_channel_unref( info->channel ); - XP_FREE( globals->params->util->mpool, info ); - *storage = NULL; - XP_LOGF( "Removed listener %d from gtk's list of listened-to sockets", oldSock ); + GtkGameGlobals* globals = findGame( apg, gameToken ); + if ( !!globals ) { + gameGotBuf( &globals->cGlobals, XP_TRUE, buf, len ); } else { - XP_ASSERT( !globals->acceptor || (func == globals->acceptor) ); - globals->acceptor = func; - - channel = g_io_channel_unix_new( listener ); - g_io_channel_set_close_on_unref( channel, TRUE ); - watch = g_io_add_watch( channel, - G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_PRI, - acceptorInput, globals ); - g_io_channel_unref( channel ); /* only main loop holds it now */ - XP_LOGF( "%s: g_io_add_watch(%d) => %d", __func__, listener, watch ); - - XP_ASSERT( NULL == info ); - info = XP_MALLOC( globals->params->util->mpool, sizeof(*info) ); - info->channel = channel; - info->watch = watch; - info->socket = listener; - *storage = info; + GtkGameGlobals tmpGlobals; + if ( loadGameNoDraw( &tmpGlobals, apg->params, gameToken ) ) { + gameGotBuf( &tmpGlobals.cGlobals, XP_FALSE, buf, len ); + saveGame( &tmpGlobals.cGlobals ); + } + freeGlobals( &tmpGlobals ); } -} /* gtk_socket_acceptor */ +} + +static gint +requestMsgs( gpointer data ) +{ + GtkAppGlobals* apg = (GtkAppGlobals*)data; + XP_UCHAR devIDBuf[64] = {0}; + db_fetch( apg->params->pDb, KEY_RDEVID, devIDBuf, sizeof(devIDBuf) ); + if ( '\0' != devIDBuf[0] ) { + relaycon_requestMsgs( apg->params, devIDBuf ); + } else { + XP_LOGF( "%s: not requesting messages as don't have relay id", __func__ ); + } + return 0; /* don't run again */ +} static void -drop_msg_toggle( GtkWidget* toggle, GtkAppGlobals* globals ) +gtkNoticeRcvd( void* closure ) { - globals->dropIncommingMsgs = gtk_toggle_button_get_active( - GTK_TOGGLE_BUTTON(toggle) ); -} /* drop_msg_toggle */ -#endif + LOG_FUNC(); + GtkAppGlobals* apg = (GtkAppGlobals*)closure; + (void)g_idle_add( requestMsgs, apg ); +} + +static void +gtkDevIDChanged( void* closure, const XP_UCHAR* devID ) +{ + GtkAppGlobals* apg = (GtkAppGlobals*)closure; + LaunchParams* params = apg->params; + if ( !!devID ) { + XP_LOGF( "%s(devID=%s)", __func__, devID ); + db_store( params->pDb, KEY_RDEVID, devID ); + } else { + XP_LOGF( "%s: bad relayid", __func__ ); + db_remove( params->pDb, KEY_RDEVID ); + + DevIDType typ; + const XP_UCHAR* devID = linux_getDevID( params, &typ ); + relaycon_reg( params, devID, typ ); + } +} + +static void +gtkErrorMsgRcvd( void* closure, const XP_UCHAR* msg ) +{ + GtkAppGlobals* apg = (GtkAppGlobals*)closure; + (void)gtkask( apg->window, msg, GTK_BUTTONS_OK ); +} + +void +onGameSaved( void* closure, sqlite3_int64 rowid, + XP_Bool firstTime ) +{ + GtkGameGlobals* globals = (GtkGameGlobals*)closure; + GtkAppGlobals* apg = globals->apg; + /* May not be recorded */ + if ( !!apg ) { + onNewData( apg, rowid, firstTime ); + } +} + +sqlite3_int64 +getSelRow( const GtkAppGlobals* apg ) +{ + sqlite3_int64 result = -1; + guint len = apg->selRows->len; + if ( 1 == len ) { + result = g_array_index( apg->selRows, sqlite3_int64, 0 ); + } + return result; +} + +static GtkAppGlobals* g_globals_for_signal = NULL; static void handle_sigintterm( int XP_UNUSED(sig) ) { LOG_FUNC(); - gtk_main_quit(); /* ok to call from signal handler? I bet not. */ + handle_destroy( NULL, g_globals_for_signal ); } int -gtkmain( LaunchParams* params, int argc, char *argv[] ) +gtkmain( LaunchParams* params ) { - short width, height; - GtkWidget* window; - GtkWidget* drawing_area; - GtkWidget* menubar; - GtkWidget* buttonbar; - GtkWidget* vbox; - GtkWidget* hbox; - GtkAppGlobals globals; -#ifndef XWFEATURE_STANDALONE_ONLY - GtkWidget* dropCheck; -#endif + GtkAppGlobals apg = {0}; + + g_globals_for_signal = &apg; struct sigaction act = { .sa_handler = handle_sigintterm }; sigaction( SIGINT, &act, NULL ); sigaction( SIGTERM, &act, NULL ); - memset( &globals, 0, sizeof(globals) ); + apg.selRows = g_array_new( FALSE, FALSE, sizeof( sqlite3_int64 ) ); + apg.params = params; + params->pDb = openGamesDB( params->dbName ); - globals.cGlobals.params = params; - globals.cGlobals.lastNTilesToUse = MAX_TRAY_TILES; -#ifndef XWFEATURE_STANDALONE_ONLY -# ifdef XWFEATURE_RELAY - globals.cGlobals.socket = -1; -# endif + RelayConnProcs procs = { + .msgReceived = gtkGotBuf, + .msgNoticeReceived = gtkNoticeRcvd, + .devIDChanged = gtkDevIDChanged, + .msgErrorMsg = gtkErrorMsgRcvd, + .socketChanged = gtkSocketChanged, + }; - globals.cGlobals.socketChanged = gtk_socket_changed; - globals.cGlobals.socketChangedClosure = &globals; - globals.cGlobals.addAcceptor = gtk_socket_acceptor; -#endif + relaycon_init( params, &procs, &apg, + params->connInfo.relay.relayName, + params->connInfo.relay.defaultSendPort ); - globals.cGlobals.cp.showBoardArrow = XP_TRUE; - globals.cGlobals.cp.hideTileValues = params->hideValues; - globals.cGlobals.cp.skipCommitConfirm = params->skipCommitConfirm; - globals.cGlobals.cp.sortNewTiles = params->sortNewTiles; - globals.cGlobals.cp.showColors = params->showColors; - globals.cGlobals.cp.allowPeek = params->allowPeek; - globals.cGlobals.cp.showRobotScores = params->showRobotScores; -#ifdef XWFEATURE_SLOW_ROBOT - globals.cGlobals.cp.robotThinkMin = params->robotThinkMin; - globals.cGlobals.cp.robotThinkMax = params->robotThinkMax; - globals.cGlobals.cp.robotTradePct = params->robotTradePct; -#endif -#ifdef XWFEATURE_CROSSHAIRS - globals.cGlobals.cp.hideCrosshairs = params->hideCrosshairs; -#endif + DevIDType typ; + const XP_UCHAR* devID = linux_getDevID( params, &typ ); + relaycon_reg( params, devID, typ ); - setupGtkUtilCallbacks( &globals, params->util ); + apg.window = makeGamesWindow( &apg ); + gtk_main(); - /* Now set up the gtk stuff. This is necessary before we make the - draw ctxt */ - gtk_init( &argc, &argv ); - - globals.window = window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); - if ( !!params->fileName ) { - gtk_window_set_title( GTK_WINDOW(window), params->fileName ); - } - - vbox = gtk_vbox_new (FALSE, 0); - gtk_container_add( GTK_CONTAINER(window), vbox ); - gtk_widget_show( vbox ); - - g_signal_connect( G_OBJECT (window), "destroy", - G_CALLBACK( quit ), &globals ); - - menubar = makeMenus( &globals, argc, argv ); - gtk_box_pack_start( GTK_BOX(vbox), menubar, FALSE, TRUE, 0); - -#ifndef XWFEATURE_STANDALONE_ONLY - dropCheck = gtk_check_button_new_with_label( "drop incoming messages" ); - g_signal_connect( GTK_OBJECT(dropCheck), - "toggled", G_CALLBACK(drop_msg_toggle), &globals ); - gtk_box_pack_start( GTK_BOX(vbox), dropCheck, FALSE, TRUE, 0); - gtk_widget_show( dropCheck ); -#endif - - buttonbar = makeButtons( &globals, argc, argv ); - gtk_box_pack_start( GTK_BOX(vbox), buttonbar, FALSE, TRUE, 0); - - drawing_area = gtk_drawing_area_new(); - globals.drawing_area = drawing_area; - gtk_widget_show( drawing_area ); - - width = GTK_HOR_SCORE_WIDTH + GTK_TIMER_WIDTH + GTK_TIMER_PAD; - if ( globals.cGlobals.params->verticalScore ) { - width += GTK_VERT_SCORE_WIDTH; - } - height = 196; - if ( globals.cGlobals.params->nHidden == 0 ) { - height += GTK_MIN_SCALE * GTK_TRAY_HT_ROWS; - } - - gtk_widget_set_size_request( GTK_WIDGET(drawing_area), width, height ); - - hbox = gtk_hbox_new( FALSE, 0 ); - gtk_box_pack_start( GTK_BOX (hbox), drawing_area, TRUE, TRUE, 0); - - /* install scrollbar even if not needed -- since zooming can make it - needed later */ - GtkWidget* vscrollbar; - gint nRows = globals.cGlobals.params->gi.boardSize; - globals.adjustment = (GtkAdjustment*) - gtk_adjustment_new( 0, 0, nRows, 1, 2, - nRows - globals.cGlobals.params->nHidden ); - vscrollbar = gtk_vscrollbar_new( globals.adjustment ); - g_signal_connect( GTK_OBJECT(globals.adjustment), "value_changed", - G_CALLBACK(scroll_value_changed), &globals ); - gtk_widget_show( vscrollbar ); - gtk_box_pack_start( GTK_BOX(hbox), vscrollbar, TRUE, TRUE, 0 ); - - gtk_box_pack_start( GTK_BOX (hbox), - makeVerticalBar( &globals, window ), - FALSE, TRUE, 0 ); - gtk_widget_show( hbox ); - - gtk_box_pack_start( GTK_BOX(vbox), hbox/* drawing_area */, TRUE, TRUE, 0); - - g_signal_connect( GTK_OBJECT(drawing_area), "expose_event", - G_CALLBACK(expose_event), &globals ); - g_signal_connect( GTK_OBJECT(drawing_area),"configure_event", - G_CALLBACK(configure_event), &globals ); - g_signal_connect( GTK_OBJECT(drawing_area), "button_press_event", - G_CALLBACK(button_press_event), &globals ); - g_signal_connect( GTK_OBJECT(drawing_area), "motion_notify_event", - G_CALLBACK(motion_notify_event), &globals ); - g_signal_connect( GTK_OBJECT(drawing_area), "button_release_event", - G_CALLBACK(button_release_event), &globals ); - - setOneSecondTimer( &globals.cGlobals ); - -#ifdef KEY_SUPPORT -# ifdef KEYBOARD_NAV - g_signal_connect( GTK_OBJECT(window), "key_press_event", - G_CALLBACK(key_press_event), &globals ); -# endif - g_signal_connect( GTK_OBJECT(window), "key_release_event", - G_CALLBACK(key_release_event), &globals ); -#endif - - gtk_widget_set_events( drawing_area, GDK_EXPOSURE_MASK - | GDK_LEAVE_NOTIFY_MASK - | GDK_BUTTON_PRESS_MASK - | GDK_POINTER_MOTION_MASK - | GDK_BUTTON_RELEASE_MASK -#ifdef KEY_SUPPORT -# ifdef KEYBOARD_NAV - | GDK_KEY_PRESS_MASK -# endif - | GDK_KEY_RELEASE_MASK -#endif -/* | GDK_POINTER_MOTION_HINT_MASK */ - ); - - if ( !!params->pipe && !!params->fileName ) { - read_pipe_then_close( &globals.cGlobals, NULL ); - } else { - gtk_widget_show( window ); - - gtk_main(); - } - /* MONCONTROL(1); */ - - cleanup( &globals ); + closeGamesDB( params->pDb ); + relaycon_cleanup( params ); return 0; } /* gtkmain */ diff --git a/xwords4/linux/gtkmain.h b/xwords4/linux/gtkmain.h index d663a127a..547ce5df4 100644 --- a/xwords4/linux/gtkmain.h +++ b/xwords4/linux/gtkmain.h @@ -1,5 +1,5 @@ -/* -*- mode: C; fill-column: 78; c-basic-offset: 4; -*- */ -/* Copyright 1997 - 2005 by Eric House (xwords@eehouse.org) All rights +/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ +/* Copyright 1997 - 2013 by Eric House (xwords@eehouse.org) All rights * reserved. * * This program is free software; you can redistribute it and/or @@ -20,155 +20,11 @@ #ifndef _GTKMAIN_H_ #define _GTKMAIN_H_ -#ifdef PLATFORM_GTK -#include -#include -#include -#include - -#include "draw.h" #include "main.h" -#include "game.h" -#include "dictnry.h" +#include "gtkboard.h" -enum { - LAYOUT_BOARD - ,LAYOUT_SMALL - ,LAYOUT_LARGE - ,LAYOUT_NLAYOUTS -}; - -#define MAX_SCORE_LEN 31 - -typedef struct GtkDrawCtx { - DrawCtxVTable* vtable; - -/* GdkDrawable* pixmap; */ - GtkWidget* drawing_area; - struct GtkAppGlobals* globals; - -#ifdef USE_CAIRO - cairo_t* cr; -#else - GdkGC* drawGC; -#endif - - GdkColor black; - GdkColor white; - GdkColor grey; - GdkColor red; /* for pending tiles */ - GdkColor tileBack; /* for pending tiles */ - GdkColor cursor; - GdkColor bonusColors[4]; - GdkColor playerColors[MAX_NUM_PLAYERS]; - - /* new for gtk 2.0 */ - PangoContext* pangoContext; - GList* fontsPerSize; - - struct { - XP_UCHAR str[MAX_SCORE_LEN+1]; - XP_U16 fontHt; - } scoreCache[MAX_NUM_PLAYERS]; - - XP_U16 trayOwner; - XP_U16 cellWidth; - XP_U16 cellHeight; - - XP_Bool scoreIsVertical; -} GtkDrawCtx; - -typedef struct ClientStreamRec { - XWStreamCtxt* stream; - guint key; - int sock; -} ClientStreamRec; - -typedef struct GtkAppGlobals { - CommonGlobals cGlobals; - GtkWidget* window; - GtkDrawCtx* draw; - -/* GdkPixmap* pixmap; */ - GtkWidget* drawing_area; - - GtkWidget* flip_button; - GtkWidget* zoomin_button; - GtkWidget* zoomout_button; - GtkWidget* toggle_undo_button; - GtkWidget* prevhint_button; - GtkWidget* nexthint_button; - -#ifdef XWFEATURE_CHAT - GtkWidget* chat_button; -#endif - - EngineCtxt* engine; - - guint idleID; - - struct timeval scoreTv; /* for timer */ - XP_U32 scoreTimerInterval; - - GtkAdjustment* adjustment; - - ClientStreamRec clientRecs[MAX_NUM_PLAYERS]; - - guint timerSources[NUM_TIMERS_PLUS_ONE - 1]; - -#ifndef XWFEATURE_STANDALONE_ONLY - XP_U16 netStatLeft, netStatTop; - XP_UCHAR stateChar; -#endif - - XP_Bool gridOn; - XP_Bool dropIncommingMsgs; - XP_Bool mouseDown; - XP_Bool altKeyDown; -#ifdef KEYBOARD_NAV - XP_Bool keyDown; -#endif -} GtkAppGlobals; - -/* DictionaryCtxt* gtk_dictionary_make(); */ -int gtkmain( LaunchParams* params, int argc, char *argv[] ); - -#define GTK_MIN_SCALE 12 /* was 14 */ - -#define GTK_MIN_TRAY_SCALEH 24 -#define GTK_MIN_TRAY_SCALEV GTK_MIN_TRAY_SCALEH -#define GTK_TRAYPAD_WIDTH 2 - -#define GTK_TOP_MARGIN 0 /* was 2 */ -#define GTK_BOARD_LEFT_MARGIN 2 -#define GTK_TRAY_LEFT_MARGIN 2 -#define GTK_SCORE_BOARD_PADDING 0 - -#define GTK_HOR_SCORE_LEFT (GTK_BOARD_LEFT_MARGIN) -#define GTK_HOR_SCORE_HEIGHT 12 -#define GTK_TIMER_HEIGHT GTK_HOR_SCORE_HEIGHT -#define GTK_HOR_SCORE_TOP (GTK_TOP_MARGIN) -#define GTK_TIMER_PAD 10 -#define GTK_VERT_SCORE_TOP (GTK_TIMER_HEIGHT + GTK_TIMER_PAD) -#define GTK_VERT_SCORE_HEIGHT ((MIN_SCALE*MAX_COLS) - GTK_TIMER_HEIGHT - \ - GTK_TIMER_PAD) -#define GTK_TIMER_WIDTH 40 -#define GTK_NETSTAT_WIDTH 20 -#define GTK_TIMER_TOP GTK_HOR_SCORE_TOP -#define GTK_HOR_SCORE_WIDTH ((GTK_MIN_SCALE*20)-GTK_TIMER_PAD) -#define GTK_VERT_SCORE_WIDTH 40 - -#define GTK_BOARD_TOP (GTK_SCORE_TOP + GTK_SCORE_HEIGHT \ - + GTK_SCORE_BOARD_PADDING ) -#define GTK_BOARD_LEFT (GTK_BOARD_LEFT_MARGIN) - -#define GTK_TRAY_LEFT GTK_TRAY_LEFT_MARGIN - -#define GTK_DIVIDER_WIDTH 5 - -#define GTK_BOTTOM_MARGIN GTK_TOP_MARGIN -#define GTK_RIGHT_MARGIN GTK_BOARD_LEFT_MARGIN - -#endif /* PLATFORM_GTK */ +int gtkmain( LaunchParams* params ); +void windowDestroyed( GtkGameGlobals* globals ); +void onGameSaved( void* closure, sqlite3_int64 rowid, XP_Bool firstTime ); #endif diff --git a/xwords4/linux/gtknewgame.c b/xwords4/linux/gtknewgame.c index ad1f29c7a..dace64d3f 100644 --- a/xwords4/linux/gtknewgame.c +++ b/xwords4/linux/gtknewgame.c @@ -1,6 +1,6 @@ -/* -*-mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make MEMDEBUG=TRUE"; -*- */ +/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ /* - * Copyright 2001-2008 by Eric House (xwords@eehouse.org). All rights + * Copyright 2001-2013 by Eric House (xwords@eehouse.org). All rights * reserved. * * This program is free software; you can redistribute it and/or @@ -22,16 +22,18 @@ #include #include "linuxutl.h" +#include "linuxmain.h" #include "gtknewgame.h" #include "strutils.h" #include "nwgamest.h" #include "gtkconnsdlg.h" #include "gtkutils.h" -#define MAX_SIZE_CHOICES 10 +#define MAX_SIZE_CHOICES 32 typedef struct GtkNewGameState { - GtkAppGlobals* globals; + GtkGameGlobals* globals; + CurGameInfo* gi; NewGameCtx* newGameCtxt; CommsAddrRec addr; @@ -43,6 +45,7 @@ typedef struct GtkNewGameState { XP_Bool fireConnDlg; gboolean isNewGame; short nCols; /* for board size */ + gchar* dict; #ifndef XWFEATURE_STANDALONE_ONLY GtkWidget* remoteChecks[MAX_NUM_PLAYERS]; @@ -153,6 +156,14 @@ size_combo_changed( GtkComboBox* combo, gpointer gp ) } } /* size_combo_changed */ +static void +dict_combo_changed( GtkComboBox* combo, gpointer gp ) +{ + GtkNewGameState* state = (GtkNewGameState*)gp; + state->dict = gtk_combo_box_get_active_text( GTK_COMBO_BOX(combo) ); + XP_LOGF( "got dict: %s", state->dict ); +} /* size_combo_changed */ + static void handle_ok( GtkWidget* XP_UNUSED(widget), gpointer closure ) { @@ -199,6 +210,7 @@ makeNewGameDialog( GtkNewGameState* state ) #endif GtkWidget* nPlayersCombo; GtkWidget* boardSizeCombo; + GtkWidget* dictCombo; CurGameInfo* gi; short ii; @@ -237,7 +249,7 @@ makeNewGameDialog( GtkNewGameState* state ) nPlayersCombo = gtk_combo_box_new_text(); state->nPlayersCombo = nPlayersCombo; - gi = &state->globals->cGlobals.params->gi; + gi = state->gi; for ( ii = 0; ii < MAX_NUM_PLAYERS; ++ii ) { char buf[2] = { ii + '1', '\0' }; @@ -334,12 +346,26 @@ makeNewGameDialog( GtkNewGameState* state ) gtk_box_pack_start( GTK_BOX(hbox), gtk_label_new("Dictionary: "), FALSE, TRUE, 0 ); + dictCombo = gtk_combo_box_new_text(); + g_signal_connect( GTK_OBJECT(dictCombo), "changed", + G_CALLBACK(dict_combo_changed), state ); + gtk_widget_show( dictCombo ); + gtk_box_pack_start( GTK_BOX(hbox), dictCombo, FALSE, TRUE, 0 ); - if ( !!gi->dictName ) { - gtk_box_pack_start( GTK_BOX(hbox), - gtk_label_new(gi->dictName), - FALSE, TRUE, 0 ); + GSList* dicts = listDicts( state->globals->cGlobals.params ); + GSList* iter; + for ( iter = dicts, ii = 0; !!iter; iter = iter->next, ++ii ) { + const gchar* name = iter->data; + gtk_combo_box_append_text( GTK_COMBO_BOX(dictCombo), name ); + if ( !!gi->dictName ) { + if ( !strcmp( name, gi->dictName ) ) { + gtk_combo_box_set_active( GTK_COMBO_BOX(dictCombo), ii ); + } + } else if ( 0 == ii ) { + gtk_combo_box_set_active( GTK_COMBO_BOX(dictCombo), ii ); + } } + g_slist_free( dicts ); gtk_widget_show( hbox ); @@ -516,17 +542,17 @@ gtk_newgame_attr_set( void* closure, NewGameAttr attr, NGValue value ) } gboolean -newGameDialog( GtkAppGlobals* globals, CommsAddrRec* addr, XP_Bool isNewGame, - XP_Bool fireConnDlg ) +newGameDialog( GtkGameGlobals* globals, CurGameInfo* gi, CommsAddrRec* addr, + XP_Bool isNewGame, XP_Bool fireConnDlg ) { GtkNewGameState state; XP_MEMSET( &state, 0, sizeof(state) ); state.globals = globals; - state.newGameCtxt = newg_make( MPPARM(globals->cGlobals.params - ->util->mpool) + state.gi = gi; + state.newGameCtxt = newg_make( MPPARM(globals->cGlobals.util->mpool) isNewGame, - globals->cGlobals.params->util, + globals->cGlobals.util, gtk_newgame_col_enable, gtk_newgame_attr_enable, gtk_newgame_col_get, @@ -542,22 +568,25 @@ newGameDialog( GtkAppGlobals* globals, CommsAddrRec* addr, XP_Bool isNewGame, state.revert = FALSE; state.loaded = XP_FALSE; - state.nCols = globals->cGlobals.params->gi.boardSize; - state.role = globals->cGlobals.params->gi.serverRole; + state.nCols = gi->boardSize; + if ( 0 == state.nCols ) { + state.nCols = globals->cGlobals.params->pgi.boardSize; + } + state.role = gi->serverRole; XP_MEMCPY( &state.addr, addr, sizeof(state.addr) ); dialog = makeNewGameDialog( &state ); - newg_load( state.newGameCtxt, - &globals->cGlobals.params->gi ); + newg_load( state.newGameCtxt, gi ); state.loaded = XP_TRUE; gtk_main(); if ( !state.cancelled && !state.revert ) { - if ( newg_store( state.newGameCtxt, &globals->cGlobals.params->gi, - XP_TRUE ) ) { - globals->cGlobals.params->gi.boardSize = state.nCols; + if ( newg_store( state.newGameCtxt, gi, XP_TRUE ) ) { + gi->boardSize = state.nCols; + replaceStringIfDifferent( globals->cGlobals.util->mpool, + &gi->dictName, state.dict ); } else { /* Do it again if we warned user of inconsistency. */ state.revert = XP_TRUE; diff --git a/xwords4/linux/gtknewgame.h b/xwords4/linux/gtknewgame.h index 5d156333e..0f2aed6fc 100644 --- a/xwords4/linux/gtknewgame.h +++ b/xwords4/linux/gtknewgame.h @@ -1,6 +1,6 @@ -/* -*- compile-command: "make MEMDEBUG=TRUE"; -*- */ +/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ /* - * Copyright 2000-2009 by Eric House (xwords@eehouse.org). All rights + * Copyright 2000-2013 by Eric House (xwords@eehouse.org). All rights * reserved. * * This program is free software; you can redistribute it and/or @@ -24,10 +24,11 @@ #ifndef _GTKNEWGAME_H_ #define _GTKNEWGAME_H_ -#include "gtkmain.h" +#include "gtkboard.h" -gboolean newGameDialog( GtkAppGlobals* globals, CommsAddrRec* addr, - XP_Bool isNewGame, XP_Bool fireConnDlg ); +gboolean newGameDialog( GtkGameGlobals* globals, CurGameInfo* gi, + CommsAddrRec* addr, XP_Bool isNewGame, + XP_Bool fireConnDlg ); #endif /* _GTKNEWGAME_H_ */ #endif /* PLATFORM_GTK */ diff --git a/xwords4/linux/gtkntilesask.h b/xwords4/linux/gtkntilesask.h index c4c1548a3..bdf1427cd 100644 --- a/xwords4/linux/gtkntilesask.h +++ b/xwords4/linux/gtkntilesask.h @@ -23,7 +23,7 @@ #ifndef _GTKNTILESASK_H_ #define _GTKNTILESASK_H_ -#include "gtkmain.h" +#include "gtkboard.h" XP_U16 askNTiles( XP_U16 nTilesMax, XP_U16 deflt ); diff --git a/xwords4/linux/gtkutils.h b/xwords4/linux/gtkutils.h index 04eb4c8a8..e80f8c3b5 100644 --- a/xwords4/linux/gtkutils.h +++ b/xwords4/linux/gtkutils.h @@ -24,7 +24,7 @@ #ifndef _GTKUTILS_H_ #define _GTKUTILS_H_ -#include "gtkmain.h" +#include "gtkboard.h" GtkWidget* makeButton( char* text, GCallback func, gpointer data ); GtkWidget* makeLabeledField( const char* labelText, GtkWidget** field ); diff --git a/xwords4/linux/linuxbt.c b/xwords4/linux/linuxbt.c index 66dcbdd63..534b6ca53 100644 --- a/xwords4/linux/linuxbt.c +++ b/xwords4/linux/linuxbt.c @@ -360,7 +360,7 @@ linux_bt_open( CommonGlobals* globals, XP_Bool amMaster ) LinBtStuff* btStuff = globals->btStuff; if ( !btStuff ) { btStuff = globals->btStuff - = lbt_make( MPPARM(globals->params->util->mpool) amMaster ); + = lbt_make( MPPARM(globals->util->mpool) amMaster ); btStuff->globals = globals; btStuff->socket = -1; @@ -413,7 +413,7 @@ linux_bt_close( CommonGlobals* globals ) (void)close( btStuff->socket ); } - XP_FREE( globals->params->util->mpool, btStuff ); + XP_FREE( globals->util->mpool, btStuff ); globals->btStuff = NULL; } } /* linux_bt_close */ diff --git a/xwords4/linux/linuxdict.h b/xwords4/linux/linuxdict.h new file mode 100644 index 000000000..0e111ce90 --- /dev/null +++ b/xwords4/linux/linuxdict.h @@ -0,0 +1,29 @@ +/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ +/* + * Copyright 2000 - 2013 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#ifndef _LINUXDICT_H_ +#define _LINUXDICT_H_ + +DictionaryCtxt* linux_dictionary_make( MPFORMAL + const LaunchParams* mainParams, + const char* dictFileName, + XP_Bool useMMap ); +#endif diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index 6be58b0da..cd46ffa3e 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -55,10 +55,14 @@ #include "linuxudp.h" #include "dictiter.h" #include "main.h" +#include "gamesdb.h" +#include "linuxdict.h" +#include "relaycon.h" #ifdef PLATFORM_NCURSES # include "cursesmain.h" #endif #ifdef PLATFORM_GTK +# include "gtkboard.h" # include "gtkmain.h" #endif #include "model.h" @@ -70,16 +74,18 @@ #include "memstream.h" #include "LocalizedStrIncludes.h" -#define DEFAULT_PORT 10999 +#define DEFAULT_PORT 10997 #define DEFAULT_LISTEN_PORT 4998 +static int blocking_read( int fd, unsigned char* buf, const int len ); + XP_Bool file_exists( const char* fileName ) { struct stat statBuf; int statResult = stat( fileName, &statBuf ); - XP_LOGF( "%s(%s)=>%d", __func__, fileName, statResult == 0 ); + // XP_LOGF( "%s(%s)=>%d", __func__, fileName, statResult == 0 ); return statResult == 0; } /* file_exists */ @@ -99,7 +105,7 @@ streamFromFile( CommonGlobals* cGlobals, char* name, void* closure ) } fclose( f ); - stream = mem_stream_make( MPPARM(cGlobals->params->util->mpool) + stream = mem_stream_make( MPPARM(cGlobals->util->mpool) cGlobals->params->vtMgr, closure, CHANNEL_NONE, NULL ); stream_putBytes( stream, buf, statBuf.st_size ); @@ -127,7 +133,8 @@ streamFromDB( CommonGlobals* cGlobals, void* closure ) XP_U8 buf[size]; res = sqlite3_blob_read( ppBlob, buf, size, 0 ); if ( SQLITE_OK == res ) { - stream = mem_stream_make( MPPARM(params->util->mpool) params->vtMgr, + stream = mem_stream_make( MPPARM(cGlobals->util->mpool) + params->vtMgr, closure, CHANNEL_NONE, NULL ); stream_putBytes( stream, buf, size ); } @@ -146,6 +153,73 @@ streamFromDB( CommonGlobals* cGlobals, void* closure ) } #endif +DictionaryCtxt* +makeDictForStream( CommonGlobals* cGlobals, XWStreamCtxt* stream ) +{ + CurGameInfo gi = {0}; + XWStreamPos pos = stream_getPos( stream, POS_READ ); + if ( !game_makeFromStream( MPPARM(cGlobals->util->mpool) stream, NULL, &gi, + NULL, NULL, NULL, NULL, NULL, NULL ) ) { + XP_ASSERT(0); + } + stream_setPos( stream, POS_READ, pos ); + + DictionaryCtxt* dict = + linux_dictionary_make( MPPARM(cGlobals->util->mpool) cGlobals->params, + gi.dictName, XP_TRUE ); + gi_disposePlayerInfo( MPPARM(cGlobals->util->mpool) &gi ); + XP_ASSERT( !!dict ); + return dict; +} + +void +gameGotBuf( CommonGlobals* cGlobals, XP_Bool hasDraw, const XP_U8* buf, + XP_U16 len ) +{ + XP_LOGF( "%s(hasDraw=%d)", __func__, hasDraw ); + XP_Bool redraw = XP_FALSE; + XWGame* game = &cGlobals->game; + XWStreamCtxt* stream = stream_from_msgbuf( cGlobals, buf, len ); + if ( !!stream ) { + if ( comms_checkIncomingStream( game->comms, stream, NULL ) ) { + redraw = server_receiveMessage( game->server, stream ); + if ( redraw ) { + saveGame( cGlobals ); + } + } + stream_destroy( stream ); + + /* 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 ( hasDraw && redraw ) { + util_requestTime( cGlobals->util ); + } else { + for ( int ii = 0; ii < 4; ++ii ) { + redraw = server_do( game->server ) || redraw; + } + } + if ( hasDraw && redraw ) { + board_draw( game->board ); + } + } +} + +gint +requestMsgsIdle( gpointer data ) +{ + CommonGlobals* cGlobals = (CommonGlobals*)data; + XP_UCHAR devIDBuf[64] = {0}; + db_fetch( cGlobals->pDb, KEY_RDEVID, devIDBuf, sizeof(devIDBuf) ); + if ( '\0' != devIDBuf[0] ) { + relaycon_requestMsgs( cGlobals->params, devIDBuf ); + } else { + XP_LOGF( "%s: not requesting messages as don't have relay id", __func__ ); + } + return 0; /* don't run again */ +} + void writeToFile( XWStreamCtxt* stream, void* closure ) { @@ -208,7 +282,7 @@ catGameHistory( CommonGlobals* cGlobals ) if ( !!cGlobals->game.model ) { XP_Bool gameOver = server_getGameIsOver( cGlobals->game.server ); XWStreamCtxt* stream = - mem_stream_make( MPPARM(cGlobals->params->util->mpool) + mem_stream_make( MPPARM(cGlobals->util->mpool) cGlobals->params->vtMgr, NULL, CHANNEL_NONE, catOnClose ); model_writeGameHistory( cGlobals->game.model, stream, @@ -222,15 +296,15 @@ void catFinalScores( const CommonGlobals* cGlobals, XP_S16 quitter ) { XWStreamCtxt* stream; - XP_ASSERT( quitter < cGlobals->params->gi.nPlayers ); + XP_ASSERT( quitter < cGlobals->gi->nPlayers ); - stream = mem_stream_make( MPPARM(cGlobals->params->util->mpool) + stream = mem_stream_make( MPPARM(cGlobals->util->mpool) cGlobals->params->vtMgr, NULL, CHANNEL_NONE, catOnClose ); if ( -1 != quitter ) { XP_UCHAR buf[128]; XP_SNPRINTF( buf, VSIZE(buf), "Player %s resigned\n", - cGlobals->params->gi.players[quitter].name ); + cGlobals->gi->players[quitter].name ); stream_catString( stream, buf ); } server_writeFinalScores( cGlobals->game.server, stream ); @@ -252,33 +326,39 @@ strFromStream( XWStreamCtxt* stream ) void saveGame( CommonGlobals* cGlobals ) { - if ( !!cGlobals->params->fileName ) { + LOG_FUNC(); + if ( !!cGlobals->params->fileName || !!cGlobals->pDb ) { XP_Bool doSave = XP_TRUE; - if ( 0 < cGlobals->params->saveFailPct - /* don't fail to save first time! */ - && file_exists( cGlobals->params->fileName ) ) { + XP_Bool newGame = !file_exists( cGlobals->params->fileName ) + || -1 == cGlobals->selRow; + /* don't fail to save first time! */ + if ( 0 < cGlobals->params->saveFailPct && !newGame ) { XP_U16 pct = XP_RANDOM() % 100; doSave = pct >= cGlobals->params->saveFailPct; } if ( doSave ) { XWStreamCtxt* outStream; - + MemStreamCloseCallback onClose = !!cGlobals->pDb? + writeToDB : writeToFile; outStream = - mem_stream_make_sized( MPPARM(cGlobals->params->util->mpool) + mem_stream_make_sized( MPPARM(cGlobals->util->mpool) cGlobals->params->vtMgr, cGlobals->lastStreamSize, - cGlobals, 0, writeToFile ); + cGlobals, 0, onClose ); stream_open( outStream ); - game_saveToStream( &cGlobals->game, - &cGlobals->params->gi, + game_saveToStream( &cGlobals->game, cGlobals->gi, outStream, ++cGlobals->curSaveToken ); cGlobals->lastStreamSize = stream_getSize( outStream ); stream_destroy( outStream ); game_saveSucceeded( &cGlobals->game, cGlobals->curSaveToken ); XP_LOGF( "%s: saved", __func__ ); + + if ( !!cGlobals->pDb ) { + summarize( cGlobals ); + } } else { XP_LOGF( "%s: simulating save failure", __func__ ); } @@ -297,10 +377,10 @@ handle_messages_from( CommonGlobals* cGlobals, const TransportProcs* procs, #ifdef DEBUG XP_Bool opened = #endif - game_makeFromStream( MPPARM(cGlobals->params->util->mpool) + game_makeFromStream( MPPARM(cGlobals->util->mpool) stream, &cGlobals->game, - ¶ms->gi, params->dict, - ¶ms->dicts, params->util, + cGlobals->gi, cGlobals->dict, + &cGlobals->dicts, cGlobals->util, NULL /*draw*/, &cGlobals->cp, procs ); XP_ASSERT( opened ); @@ -325,7 +405,7 @@ handle_messages_from( CommonGlobals* cGlobals, const TransportProcs* procs, XP_LOGF( "%s: 2: unexpected nRead: %d", __func__, nRead ); break; } - stream = mem_stream_make( MPPARM(cGlobals->params->util->mpool) + stream = mem_stream_make( MPPARM(cGlobals->util->mpool) params->vtMgr, cGlobals, CHANNEL_NONE, NULL ); stream_putBytes( stream, buf, len ); @@ -358,10 +438,10 @@ read_pipe_then_close( CommonGlobals* cGlobals, const TransportProcs* procs ) #ifdef DEBUG XP_Bool opened = #endif - game_makeFromStream( MPPARM(cGlobals->params->util->mpool) + game_makeFromStream( MPPARM(cGlobals->util->mpool) stream, &cGlobals->game, - ¶ms->gi, params->dict, - ¶ms->dicts, params->util, + cGlobals->gi, cGlobals->dict, + &cGlobals->dicts, cGlobals->util, NULL /*draw*/, &cGlobals->cp, procs ); XP_ASSERT( opened ); @@ -389,7 +469,7 @@ read_pipe_then_close( CommonGlobals* cGlobals, const TransportProcs* procs ) XP_LOGF( "%s: 2: unexpected nRead: %d", __func__, nRead ); break; } - stream = mem_stream_make( MPPARM(cGlobals->params->util->mpool) + stream = mem_stream_make( MPPARM(cGlobals->util->mpool) params->vtMgr, cGlobals, CHANNEL_NONE, NULL ); stream_putBytes( stream, buf, len ); @@ -513,9 +593,11 @@ typedef enum { #ifdef XWFEATURE_DEVID ,CMD_DEVID ,CMD_RDEVID + ,CMD_NOANONDEVID #endif ,CMD_GAMESEED ,CMD_GAMEFILE + ,CMD_DBFILE ,CMD_SAVEFAIL_PCT #ifdef USE_SQLITE ,CMD_GAMEDB_FILE @@ -613,9 +695,12 @@ static CmdInfoRec CmdInfoRecs[] = { #ifdef XWFEATURE_DEVID ,{ CMD_DEVID, true, "devid", "device ID (for testing GCM stuff)" } ,{ CMD_RDEVID, true, "rdevid", "relay's converted device ID (for testing GCM stuff)" } + ,{CMD_NOANONDEVID, false, "no-anon-devid", + "override default of using anonymous devid registration when no id provided" } #endif ,{ CMD_GAMESEED, true, "game-seed", "game seed (for relay play)" } ,{ CMD_GAMEFILE, true, "file", "file to save to/read from" } + ,{ CMD_DBFILE, true, "db", "sqlite3 db to store game data" } ,{ CMD_SAVEFAIL_PCT, true, "savefail-pct", "How often, at random, does save fail?" } #ifdef USE_SQLITE ,{ CMD_GAMEDB_FILE, true, "game-db-file", @@ -773,6 +858,30 @@ linShiftFocus( CommonGlobals* cGlobals, XP_Key key, const BoardObjectType* order } /* linShiftFocus */ #endif +const XP_UCHAR* +linux_getDevID( LaunchParams* params, DevIDType* typ ) +{ + const XP_UCHAR* result = NULL; + + /* commandline takes precedence over stored values */ + + if ( !!params->rDevID ) { + result = params->rDevID; + *typ = ID_TYPE_RELAY; + } else if ( !!params->devID ) { + result = params->devID; + *typ = ID_TYPE_LINUX; + } else if ( db_fetch( params->pDb, KEY_RDEVID, params->devIDStore, + sizeof(params->devIDStore) ) ) { + result = params->devIDStore; + *typ = ID_TYPE_RELAY; + } else if ( !params->noAnonDevid ) { + *typ = ID_TYPE_ANON; + result = ""; + } + return result; +} + #ifdef XWFEATURE_RELAY static int linux_init_relay_socket( CommonGlobals* cGlobals, const CommsAddrRec* addrRec ) @@ -911,35 +1020,40 @@ send_per_params( const XP_U8* buf, const XP_U16 buflen, } static XP_S16 -linux_tcp_send( const XP_U8* buf, XP_U16 buflen, - CommonGlobals* globals, const CommsAddrRec* addrRec ) +linux_tcp_send( CommonGlobals* cGlobals, const XP_U8* buf, XP_U16 buflen, + const CommsAddrRec* addrRec ) { XP_S16 result = 0; - int sock = globals->socket; - - if ( sock == -1 ) { - XP_LOGF( "%s: socket uninitialized", __func__ ); - sock = linux_init_relay_socket( globals, addrRec ); - if ( sock != -1 ) { - assert( globals->socket == sock ); - (*globals->socketChanged)( globals->socketChangedClosure, - -1, sock, &globals->storage ); - } - } - - if ( sock != -1 ) { - XP_U16 netLen = htons( buflen ); - XP_U8 tmp[buflen + sizeof(netLen)]; - XP_MEMCPY( &tmp[0], &netLen, sizeof(netLen) ); - XP_MEMCPY( &tmp[sizeof(netLen)], buf, buflen ); - - if ( send_per_params( tmp, buflen + sizeof(netLen), globals ) ) { - result = buflen; - } + if ( !!cGlobals->pDb ) { + XP_ASSERT( -1 != cGlobals->selRow ); + result = relaycon_send( cGlobals->params, buf, buflen, + cGlobals->selRow, addrRec ); } else { - XP_LOGF( "%s: socket still -1", __func__ ); + int sock = cGlobals->socket; + + if ( sock == -1 ) { + XP_LOGF( "%s: socket uninitialized", __func__ ); + sock = linux_init_relay_socket( cGlobals, addrRec ); + if ( sock != -1 ) { + assert( cGlobals->socket == sock ); + (*cGlobals->socketChanged)( cGlobals->socketChangedClosure, + -1, sock, &cGlobals->storage ); + } + } + + if ( sock != -1 ) { + XP_U16 netLen = htons( buflen ); + XP_U8 tmp[buflen + sizeof(netLen)]; + XP_MEMCPY( &tmp[0], &netLen, sizeof(netLen) ); + XP_MEMCPY( &tmp[sizeof(netLen)], buf, buflen ); + + if ( send_per_params( tmp, buflen + sizeof(netLen), cGlobals ) ) { + result = buflen; + } + } else { + XP_LOGF( "%s: socket still -1", __func__ ); + } } - return result; } /* linux_tcp_send */ #endif /* XWFEATURE_RELAY */ @@ -981,54 +1095,53 @@ linux_reset( void* closure ) #endif XP_S16 -linux_send( const XP_U8* buf, XP_U16 buflen, - const CommsAddrRec* addrRec, +linux_send( const XP_U8* buf, XP_U16 buflen, const CommsAddrRec* addrRec, XP_U32 XP_UNUSED(gameID), void* closure ) { XP_S16 nSent = -1; - CommonGlobals* globals = (CommonGlobals*)closure; + CommonGlobals* cGlobals = (CommonGlobals*)closure; CommsConnType conType; if ( !!addrRec ) { conType = addrRec->conType; } else { - conType = globals->params->conType; + conType = cGlobals->params->conType; } if ( 0 ) { #ifdef XWFEATURE_RELAY } else if ( conType == COMMS_CONN_RELAY ) { - nSent = linux_tcp_send( buf, buflen, globals, addrRec ); - if ( nSent == buflen && globals->params->duplicatePackets ) { + nSent = linux_tcp_send( cGlobals, buf, buflen, addrRec ); + if ( nSent == buflen && cGlobals->params->duplicatePackets ) { #ifdef DEBUG XP_S16 sentAgain = #endif - linux_tcp_send( buf, buflen, globals, addrRec ); + linux_tcp_send( cGlobals, buf, buflen, addrRec ); XP_ASSERT( sentAgain == nSent ); } #endif #if defined XWFEATURE_BLUETOOTH } else if ( conType == COMMS_CONN_BT ) { - XP_Bool isServer = comms_getIsServer( globals->game.comms ); - linux_bt_open( globals, isServer ); - nSent = linux_bt_send( buf, buflen, addrRec, globals ); + XP_Bool isServer = comms_getIsServer( cGlobals->game.comms ); + linux_bt_open( cGlobals, isServer ); + nSent = linux_bt_send( buf, buflen, addrRec, cGlobals ); #endif #if defined XWFEATURE_IP_DIRECT } else if ( conType == COMMS_CONN_IP_DIRECT ) { CommsAddrRec addr; - comms_getAddr( globals->game.comms, &addr ); - linux_udp_open( globals, &addr ); - nSent = linux_udp_send( buf, buflen, addrRec, globals ); + comms_getAddr( cGlobals->game.comms, &addr ); + linux_udp_open( cGlobals, &addr ); + nSent = linux_udp_send( buf, buflen, addrRec, cGlobals ); #endif #if defined XWFEATURE_SMS } else if ( COMMS_CONN_SMS == conType ) { CommsAddrRec addr; if ( !addrRec ) { - comms_getAddr( globals->game.comms, &addr ); + comms_getAddr( cGlobals->game.comms, &addr ); addrRec = &addr; } - nSent = linux_sms_send( globals, buf, buflen, + nSent = linux_sms_send( cGlobals, buf, buflen, addrRec->u.sms.phone, addrRec->u.sms.port ); #endif } else { @@ -1046,27 +1159,36 @@ linux_close_socket( CommonGlobals* cGlobals ) (*cGlobals->socketChanged)( cGlobals->socketChangedClosure, socket, -1, &cGlobals->storage ); - XP_ASSERT( -1 == cGlobals->socket ); + XP_ASSERT( -1 == cGlobals->socket ); - XP_LOGF( "linux_close_socket" ); close( socket ); } -int +static int blocking_read( int fd, unsigned char* buf, const int len ) { assert( -1 != fd ); int nRead = 0; - while ( nRead < len ) { - ssize_t siz = read( fd, buf + nRead, len - nRead ); - if ( siz <= 0 ) { - XP_LOGF( "%s: read => %d, errno=%d (\"%s\")", __func__, nRead, - errno, strerror(errno) ); - nRead = -1; + int tries; + for ( tries = 5; nRead < len && tries > 0; --tries ) { + // XP_LOGF( "%s: blocking for %d bytes", __func__, len ); + ssize_t nGot = read( fd, buf + nRead, len - nRead ); + if ( nGot == 0 ) { + XP_LOGF( "%s: read 0; let's try again (%d more times)", __func__, + tries ); + usleep( 10000 ); + } else if ( nGot < 0 ) { + XP_LOGF( "read => %d (wanted %d), errno=%d (\"%s\")", nRead, + len - nRead, errno, strerror(errno) ); break; } - nRead += siz; + nRead += nGot; } + + if ( nRead < len ) { + nRead = -1; + } + XP_LOGF( "%s(fd=%d, sought=%d) => %d", __func__, fd, len, nRead ); return nRead; } @@ -1074,17 +1196,30 @@ blocking_read( int fd, unsigned char* buf, const int len ) int linux_relay_receive( CommonGlobals* cGlobals, unsigned char* buf, int bufSize ) { - ssize_t nRead = -1; + LOG_FUNC(); int sock = cGlobals->socket; - if ( -1 != sock ) { - unsigned short tmp; - nRead = blocking_read( sock, (unsigned char*)&tmp, sizeof(tmp) ); - if ( nRead != 2 ) { - nRead = -1; - } else { - unsigned short packetSize = ntohs( tmp ); - if ( 0 == packetSize || bufSize <= packetSize ) { - nRead = -1; + unsigned short tmp; + ssize_t nRead = blocking_read( sock, (unsigned char*)&tmp, sizeof(tmp) ); + if ( nRead != 2 ) { + linux_close_socket( cGlobals ); + comms_transportFailed( cGlobals->game.comms ); + nRead = -1; + } else { + unsigned short packetSize = ntohs( tmp ); + XP_LOGF( "%s: got packet of size %d", __func__, packetSize ); + assert( packetSize <= bufSize ); + nRead = blocking_read( sock, buf, packetSize ); + if ( nRead == packetSize ) { + LaunchParams* params = cGlobals->params; + ++params->nPacketsRcvd; + if ( params->dropNthRcvd == 0 ) { + /* do nothing */ + } else if ( params->dropNthRcvd > 0 ) { + if ( params->nPacketsRcvd == params->dropNthRcvd ) { + XP_LOGF( "%s: dropping %dth packet per --drop-nth-packet", + __func__, params->nPacketsRcvd ); + nRead = -1; + } } else { nRead = blocking_read( sock, buf, packetSize ); if ( nRead != packetSize ) { @@ -1126,11 +1261,11 @@ linux_relay_receive( CommonGlobals* cGlobals, unsigned char* buf, int bufSize ) information specific to our platform's comms layer (return address, say) */ XWStreamCtxt* -stream_from_msgbuf( CommonGlobals* globals, unsigned char* bufPtr, +stream_from_msgbuf( CommonGlobals* globals, const unsigned char* bufPtr, XP_U16 nBytes ) { XWStreamCtxt* result; - result = mem_stream_make( MPPARM(globals->params->util->mpool) + result = mem_stream_make( MPPARM(globals->util->mpool) globals->params->vtMgr, globals, CHANNEL_NONE, NULL ); stream_putBytes( result, bufPtr, nBytes ); @@ -1221,14 +1356,27 @@ linux_util_addrChange( XW_UtilCtxt* uc, } } -static void -linux_util_setIsServer( XW_UtilCtxt* uc, XP_Bool isServer ) +void +linuxSetIsServer( CommonGlobals* cGlobals, XP_Bool isServer ) { - XP_LOGF( "%s(%d)", __func__, isServer ); - CommonGlobals* cGlobals = (CommonGlobals*)uc->closure; + XP_LOGF( "%s(isServer=%d)", __func__, isServer ); DeviceRole newRole = isServer? SERVER_ISSERVER : SERVER_ISCLIENT; cGlobals->params->serverRole = newRole; - cGlobals->params->gi.serverRole = newRole; + cGlobals->gi->serverRole = newRole; +} + +void +linuxChangeRoles( CommonGlobals* cGlobals ) +{ + ServerCtxt* server = cGlobals->game.server; + server_reset( server, cGlobals->game.comms ); + if ( SERVER_ISCLIENT == cGlobals->gi->serverRole ) { + XWStreamCtxt* stream = + mem_stream_make( MPPARM(cGlobals->util->mpool) cGlobals->params->vtMgr, + cGlobals, CHANNEL_NONE, sendOnClose ); + server_initClientConnection( server, stream ); + } + (void)server_do( server ); } #endif @@ -1311,6 +1459,7 @@ static void tmp_noop_sigintterm( int XP_UNUSED(sig) ) { LOG_FUNC(); + exit(0); } #ifdef XWFEATURE_WALKDICT @@ -1353,8 +1502,7 @@ testGetNthWord( const DictionaryCtxt* dict, char** XP_UNUSED_DBG(words), } static void -walk_dict_test( const LaunchParams* XP_UNUSED_DBG(params), - const DictionaryCtxt* dict, +walk_dict_test( MPFORMAL const DictionaryCtxt* dict, GSList* testPrefixes, const char* testMinMax ) { /* This is just to test that the dict-iterating code works. The words are @@ -1427,18 +1575,18 @@ walk_dict_test( const LaunchParams* XP_UNUSED_DBG(params), XP_U16 maxCount = dict_numTileFaces( dict ); IndexData data; data.count = maxCount * maxCount; - data.indices = XP_MALLOC( params->util->mpool, + data.indices = XP_MALLOC( mpool, data.count * depth * sizeof(data.indices[0]) ); - data.prefixes = XP_MALLOC( params->util->mpool, + data.prefixes = XP_MALLOC( mpool, depth * data.count * sizeof(data.prefixes[0]) ); XP_LOGF( "making index..." ); dict_makeIndex( &iter, depth, &data ); XP_LOGF( "DONE making index" ); - data.indices = XP_REALLOC( params->util->mpool, data.indices, + data.indices = XP_REALLOC( mpool, data.indices, data.count * depth * sizeof(*data.indices) ); - data.prefixes = XP_REALLOC( params->util->mpool, data.prefixes, + data.prefixes = XP_REALLOC( mpool, data.prefixes, depth * data.count * sizeof(*data.prefixes) ); #if 0 for ( ii = 0; ii < nIndices; ++ii ) { @@ -1494,14 +1642,14 @@ walk_dict_test( const LaunchParams* XP_UNUSED_DBG(params), } } - XP_FREE( params->util->mpool, data.indices ); - XP_FREE( params->util->mpool, data.prefixes ); + XP_FREE( mpool, data.indices ); + XP_FREE( mpool, data.prefixes ); } XP_LOGF( "done" ); } static void -walk_dict_test_all( const LaunchParams* params, GSList* testDicts, +walk_dict_test_all( MPFORMAL const LaunchParams* params, GSList* testDicts, GSList* testPrefixes, const char* testMinMax ) { int ii; @@ -1509,11 +1657,11 @@ walk_dict_test_all( const LaunchParams* params, GSList* testDicts, for ( ii = 0; ii < count; ++ii ) { gchar* name = (gchar*)g_slist_nth_data( testDicts, ii ); DictionaryCtxt* dict = - linux_dictionary_make( MPPARM(params->util->mpool) params, name, + linux_dictionary_make( MPPARM(mpool) params, name, params->useMmap ); if ( NULL != dict ) { XP_LOGF( "walk_dict_test(%s)", name ); - walk_dict_test( params, dict, testPrefixes, testMinMax ); + walk_dict_test( MPPARM(mpool) dict, testPrefixes, testMinMax ); dict_destroy( dict ); } } @@ -1575,10 +1723,10 @@ getDictPath( const LaunchParams *params, const char* name, char* result, int resultLen ) { XP_Bool success = XP_FALSE; - GSList* dictDirs; + GSList* iter; result[0] = '\0'; - for ( dictDirs = params->dictDirs; !!dictDirs; dictDirs = dictDirs->next ) { - const char* path = dictDirs->data; + for ( iter = params->dictDirs; !!iter; iter = iter->next ) { + const char* path = iter->data; char buf[256]; int len = snprintf( buf, VSIZE(buf), "%s/%s.xwd", path, name ); if ( len < VSIZE(buf) && file_exists( buf ) ) { @@ -1587,7 +1735,7 @@ getDictPath( const LaunchParams *params, const char* name, break; } } - LOG_RETURNF( "%d", success ); + XP_LOGF( "%s(%s)=>%d", __func__, name, success ); return success; } @@ -1617,13 +1765,106 @@ listDicts( const LaunchParams *params ) return result; } +void +setupLinuxUtilCallbacks( XW_UtilCtxt* util ) +{ +#ifndef XWFEATURE_STANDALONE_ONLY + util->vtable->m_util_informMissing = linux_util_informMissing; + util->vtable->m_util_addrChange = linux_util_addrChange; +#endif +} + +/* Set up cGlobals->gi and cGlobals->addr based on params fields */ +void +initFromParams( CommonGlobals* cGlobals, LaunchParams* params ) +{ + LOG_FUNC(); + /* CurGameInfo */ + cGlobals->gi = ¶ms->pgi; + + /* addr */ + CommsAddrRec* addr = &cGlobals->addr; + XP_MEMSET( addr, 0, sizeof(*addr) ); + if ( 0 ) { +#ifdef XWFEATURE_RELAY + } else if ( params->conType == COMMS_CONN_RELAY ) { + addr->conType = COMMS_CONN_RELAY; + addr->u.ip_relay.ipAddr = 0; /* ??? */ + addr->u.ip_relay.port = params->connInfo.relay.defaultSendPort; + addr->u.ip_relay.seeksPublicRoom = + params->connInfo.relay.seeksPublicRoom; + addr->u.ip_relay.advertiseRoom = params->connInfo.relay.advertiseRoom; + XP_STRNCPY( addr->u.ip_relay.hostName, + params->connInfo.relay.relayName, + sizeof(addr->u.ip_relay.hostName) - 1 ); + XP_STRNCPY( addr->u.ip_relay.invite, params->connInfo.relay.invite, + sizeof(addr->u.ip_relay.invite) - 1 ); +#endif +#ifdef XWFEATURE_SMS + } else if ( params->conType == COMMS_CONN_SMS ) { + addr->conType = COMMS_CONN_SMS; + XP_STRNCPY( addr->u.sms.phone, params->connInfo.sms.serverPhone, + sizeof(addr->u.sms.phone) - 1 ); + addr->u.sms.port = params->connInfo.sms.port; +#endif +#ifdef XWFEATURE_BLUETOOTH + } else if ( params->conType == COMMS_CONN_BT ) { + addr->conType = COMMS_CONN_BT; + XP_ASSERT( sizeof(addr->u.bt.btAddr) + >= sizeof(params->connInfo.bt.hostAddr)); + XP_MEMCPY( &addr->u.bt.btAddr, ¶ms->connInfo.bt.hostAddr, + sizeof(params->connInfo.bt.hostAddr) ); +#endif + } +} + +void +setupUtil( CommonGlobals* cGlobals ) +{ + XW_UtilCtxt* util = calloc( 1, sizeof(*util) ); + cGlobals->util = util; + linux_util_vt_init( MPPARM(cGlobals->params->mpool) util ); + util->gameInfo = cGlobals->gi; + setupLinuxUtilCallbacks( util ); +} + +static void +initParams( LaunchParams* params ) +{ + memset( params, 0, sizeof(*params) ); + + // params->util = calloc( 1, sizeof(*params->util) ); + /* XP_MEMSET( params->util, 0, sizeof(params->util) ); */ + +#ifdef MEM_DEBUG + params->mpool = mpool_make(); +#endif + + params->vtMgr = make_vtablemgr(MPPARM_NOCOMMA(params->mpool)); + // linux_util_vt_init( MPPARM(params->mpool) params->util ); +#ifndef XWFEATURE_STANDALONE_ONLY + /* params->util->vtable->m_util_informMissing = linux_util_informMissing; */ + /* params->util->vtable->m_util_addrChange = linux_util_addrChange; */ + /* params->util->vtable->m_util_setIsServer = linux_util_setIsServer; */ +#endif +} + +static void +freeParams( LaunchParams* params ) +{ + vtmgr_destroy( MPPARM(params->mpool) params->vtMgr ); + + gi_disposePlayerInfo( MPPARM(params->mpool) ¶ms->pgi ); + mpool_destroy( params->mpool ); +} + static int dawg2dict( const LaunchParams* params, GSList* testDicts ) { guint count = g_slist_length( testDicts ); for ( int ii = 0; ii < count; ++ii ) { DictionaryCtxt* dict = - linux_dictionary_make( MPPARM(params->util->mpool) params, + linux_dictionary_make( MPPARM(params->mpool) params, g_slist_nth_data( testDicts, ii ), params->useMmap ); if ( NULL != dict ) { @@ -1647,7 +1888,7 @@ main( int argc, char** argv ) LaunchParams mainParams; XP_U16 nPlayerDicts = 0; XP_U16 robotCount = 0; - XP_U16 ii; + /* XP_U16 ii; */ #ifdef XWFEATURE_WALKDICT GSList* testDicts = NULL; GSList* testPrefixes = NULL; @@ -1688,36 +1929,25 @@ main( int argc, char** argv ) } #endif - memset( &mainParams, 0, sizeof(mainParams) ); - - mainParams.util = malloc( sizeof(*mainParams.util) ); - XP_MEMSET( mainParams.util, 0, sizeof(*mainParams.util) ); - -#ifdef MEM_DEBUG - mainParams.util->mpool = mpool_make(); -#endif - - mainParams.vtMgr = make_vtablemgr(MPPARM_NOCOMMA(mainParams.util->mpool)); - - /* fprintf( stdout, "press to start\n" ); */ - /* (void)fgetc( stdin ); */ + initParams( &mainParams ); /* defaults */ #ifdef XWFEATURE_RELAY mainParams.connInfo.relay.defaultSendPort = DEFAULT_PORT; + mainParams.connInfo.relay.relayName = "localhost"; mainParams.connInfo.relay.invite = "INVITE"; #endif #ifdef XWFEATURE_IP_DIRECT mainParams.connInfo.ip.port = DEFAULT_PORT; mainParams.connInfo.ip.hostName = "localhost"; #endif - mainParams.gi.boardSize = 15; + mainParams.pgi.boardSize = 15; mainParams.quitAfter = -1; mainParams.sleepOnAnchor = XP_FALSE; mainParams.printHistory = XP_FALSE; mainParams.undoWhenDone = XP_FALSE; - mainParams.gi.timerEnabled = XP_FALSE; - mainParams.gi.dictLang = -1; + mainParams.pgi.timerEnabled = XP_FALSE; + mainParams.pgi.dictLang = -1; mainParams.noHeartbeat = XP_FALSE; mainParams.nHidden = 0; mainParams.needsNewGame = XP_FALSE; @@ -1730,7 +1960,8 @@ main( int argc, char** argv ) mainParams.showRobotScores = XP_FALSE; mainParams.useMmap = XP_TRUE; - char* envDictPath = getenv( "XW_DICTSPATH" ); + char* envDictPath = getenv( "XW_DICTDIR" ); + XP_LOGF( "%s: envDictPath=%s", __func__, envDictPath ); if ( !!envDictPath ) { char *saveptr; for ( ; ; ) { @@ -1773,6 +2004,7 @@ main( int argc, char** argv ) conType == COMMS_CONN_RELAY ); mainParams.connInfo.relay.invite = optarg; conType = COMMS_CONN_RELAY; + // isServer = XP_TRUE; /* implicit */ break; #endif case CMD_HOSTIP: @@ -1783,7 +2015,7 @@ main( int argc, char** argv ) break; case CMD_DICT: trimDictPath( optarg, dictbuf, VSIZE(dictbuf), &path, &dict ); - mainParams.gi.dictName = copyString( mainParams.util->mpool, dict ); + mainParams.pgi.dictName = copyString( mainParams.mpool, dict ); if ( !path ) { path = "."; } @@ -1821,6 +2053,8 @@ main( int argc, char** argv ) case CMD_RDEVID: mainParams.rDevID = optarg; break; + case CMD_NOANONDEVID: + mainParams.noAnonDevid = true; #endif case CMD_GAMESEED: mainParams.gameSeed = atoi(optarg); @@ -1828,6 +2062,9 @@ main( int argc, char** argv ) case CMD_GAMEFILE: mainParams.fileName = optarg; break; + case CMD_DBFILE: + mainParams.dbName = optarg; + break; case CMD_SAVEFAIL_PCT: mainParams.saveFailPct = atoi( optarg ); break; @@ -1860,13 +2097,13 @@ main( int argc, char** argv ) mainParams.skipWarnings = 1; break; case CMD_LOCALPWD: - mainParams.gi.players[mainParams.nLocalPlayers-1].password + mainParams.pgi.players[mainParams.nLocalPlayers-1].password = (XP_UCHAR*)optarg; break; case CMD_LOCALSMARTS: - index = mainParams.gi.nPlayers - 1; - XP_ASSERT( LP_IS_ROBOT( &mainParams.gi.players[index] ) ); - mainParams.gi.players[index].robotIQ = atoi(optarg); + index = mainParams.pgi.nPlayers - 1; + XP_ASSERT( LP_IS_ROBOT( &mainParams.pgi.players[index] ) ); + mainParams.pgi.players[index].robotIQ = atoi(optarg); break; #ifdef XWFEATURE_SMS case CMD_SMSNUMBER: /* SMS phone number */ @@ -1882,22 +2119,22 @@ main( int argc, char** argv ) mainParams.dropNthRcvd = atoi( optarg ); break; case CMD_NOHINTS: - mainParams.gi.hintsNotAllowed = XP_TRUE; + mainParams.pgi.hintsNotAllowed = XP_TRUE; break; case CMD_PICKTILESFACEUP: - mainParams.gi.allowPickTiles = XP_TRUE; + mainParams.pgi.allowPickTiles = XP_TRUE; break; case CMD_PLAYERNAME: - index = mainParams.gi.nPlayers++; + index = mainParams.pgi.nPlayers++; ++mainParams.nLocalPlayers; - mainParams.gi.players[index].robotIQ = 0; /* means human */ - mainParams.gi.players[index].isLocal = XP_TRUE; - mainParams.gi.players[index].name = - copyString( mainParams.util->mpool, (XP_UCHAR*)optarg); + mainParams.pgi.players[index].robotIQ = 0; /* means human */ + mainParams.pgi.players[index].isLocal = XP_TRUE; + mainParams.pgi.players[index].name = + copyString( mainParams.mpool, (XP_UCHAR*)optarg); break; case CMD_REMOTEPLAYER: - index = mainParams.gi.nPlayers++; - mainParams.gi.players[index].isLocal = XP_FALSE; + index = mainParams.pgi.nPlayers++; + mainParams.pgi.players[index].isLocal = XP_FALSE; ++mainParams.info.serverInfo.nRemotePlayers; break; case CMD_PORT: @@ -1906,12 +2143,12 @@ main( int argc, char** argv ) break; case CMD_ROBOTNAME: ++robotCount; - index = mainParams.gi.nPlayers++; + index = mainParams.pgi.nPlayers++; ++mainParams.nLocalPlayers; - mainParams.gi.players[index].robotIQ = 1; /* real smart by default */ - mainParams.gi.players[index].isLocal = XP_TRUE; - mainParams.gi.players[index].name = - copyString( mainParams.util->mpool, (XP_UCHAR*)optarg); + mainParams.pgi.players[index].robotIQ = 1; /* real smart by default */ + mainParams.pgi.players[index].isLocal = XP_TRUE; + mainParams.pgi.players[index].name = + copyString( mainParams.mpool, (XP_UCHAR*)optarg); break; case CMD_SORTNEW: mainParams.sortNewTiles = XP_TRUE; @@ -1923,8 +2160,8 @@ main( int argc, char** argv ) mainParams.sleepOnAnchor = XP_TRUE; break; case CMD_TIMERMINUTES: - mainParams.gi.gameSeconds = atoi(optarg) * 60; - mainParams.gi.timerEnabled = XP_TRUE; + mainParams.pgi.gameSeconds = atoi(optarg) * 60; + mainParams.pgi.timerEnabled = XP_TRUE; break; case CMD_UNDOWHENDONE: mainParams.undoWhenDone = XP_TRUE; @@ -1950,13 +2187,13 @@ main( int argc, char** argv ) case CMD_PHONIES: switch( atoi(optarg) ) { case 0: - mainParams.gi.phoniesAction = PHONIES_IGNORE; + mainParams.pgi.phoniesAction = PHONIES_IGNORE; break; case 1: - mainParams.gi.phoniesAction = PHONIES_WARN; + mainParams.pgi.phoniesAction = PHONIES_WARN; break; case 2: - mainParams.gi.phoniesAction = PHONIES_DISALLOW; + mainParams.pgi.phoniesAction = PHONIES_DISALLOW; break; default: usage( argv[0], "phonies takes 0 or 1 or 2" ); @@ -1973,7 +2210,7 @@ main( int argc, char** argv ) mainParams.quitAfter = atoi(optarg); break; case CMD_BOARDSIZE: - mainParams.gi.boardSize = atoi(optarg); + mainParams.pgi.boardSize = atoi(optarg); break; #ifdef XWFEATURE_BLUETOOTH case CMD_BTADDR: @@ -2058,21 +2295,21 @@ main( int argc, char** argv ) } } - int result; + int result = 0; if ( g_str_has_suffix( argv[0], "dawg2dict" ) ) { result = dawg2dict( &mainParams, testDicts ); } else { - XP_ASSERT( mainParams.gi.nPlayers == mainParams.nLocalPlayers + XP_ASSERT( mainParams.pgi.nPlayers == mainParams.nLocalPlayers + mainParams.info.serverInfo.nRemotePlayers ); if ( isServer ) { if ( mainParams.info.serverInfo.nRemotePlayers == 0 ) { - mainParams.gi.serverRole = SERVER_STANDALONE; + mainParams.pgi.serverRole = SERVER_STANDALONE; } else { - mainParams.gi.serverRole = SERVER_ISSERVER; + mainParams.pgi.serverRole = SERVER_ISSERVER; } } else { - mainParams.gi.serverRole = SERVER_ISCLIENT; + mainParams.pgi.serverRole = SERVER_ISCLIENT; } /* sanity checks */ @@ -2089,21 +2326,21 @@ main( int argc, char** argv ) } } - if ( !!mainParams.gi.dictName ) { + if ( !!mainParams.pgi.dictName ) { /* char path[256]; */ /* getDictPath( &mainParams, mainParams.gi.dictName, path, VSIZE(path) ); */ - mainParams.dict = - linux_dictionary_make( MPPARM(mainParams.util->mpool) &mainParams, - mainParams.gi.dictName, - mainParams.useMmap ); - XP_ASSERT( !!mainParams.dict ); - mainParams.gi.dictLang = dict_getLangCode( mainParams.dict ); + /* mainParams.dict = */ + /* linux_dictionary_make( MPPARM(mainParams.mpool) &mainParams, */ + /* mainParams.pgi.dictName, */ + /* mainParams.useMmap ); */ + /* XP_ASSERT( !!mainParams.dict ); */ + /* mainParams.pgi.dictLang = dict_getLangCode( mainParams.dict ); */ } else if ( isServer ) { #ifdef STUBBED_DICT - mainParams.dict = make_stubbed_dict( - MPPARM_NOCOMMA(mainParams.util->mpool) ); + mainParams.dict = + make_stubbed_dict( MPPARM_NOCOMMA(mainParams.util->mpool) ); XP_WARNF( "no dictionary provided: using English stub dict\n" ); - mainParams.gi.dictLang = dict_getLangCode( mainParams.dict ); + mainParams.pgi.dictLang = dict_getLangCode( mainParams.dict ); #else if ( 0 == nPlayerDicts ) { mainParams.needsNewGame = XP_TRUE; @@ -2114,7 +2351,7 @@ main( int argc, char** argv ) } if ( 0 < mainParams.info.serverInfo.nRemotePlayers - && SERVER_STANDALONE == mainParams.gi.serverRole ) { + && SERVER_STANDALONE == mainParams.pgi.serverRole ) { mainParams.needsNewGame = XP_TRUE; } @@ -2122,20 +2359,20 @@ main( int argc, char** argv ) given. It's an error to give too many, or not to give enough if there's no game-dict */ if ( 0 < nPlayerDicts ) { - XP_U16 nextDict = 0; - for ( ii = 0; ii < mainParams.gi.nPlayers; ++ii ) { - if ( mainParams.gi.players[ii].isLocal ) { - const XP_UCHAR* name = mainParams.playerDictNames[nextDict++]; - XP_ASSERT( !!name ); - mainParams.dicts.dicts[ii] = - linux_dictionary_make( MPPARM(mainParams.util->mpool) - &mainParams, name, mainParams.useMmap ); - } - } - if ( nextDict < nPlayerDicts ) { - usage( argv[0], " --player-dict used more times than there are " - "local players" ); - } + /* XP_U16 nextDict = 0; */ + /* for ( ii = 0; ii < mainParams.gi.nPlayers; ++ii ) { */ + /* if ( mainParams.gi.players[ii].isLocal ) { */ + /* const XP_UCHAR* name = mainParams.playerDictNames[nextDict++]; */ + /* XP_ASSERT( !!name ); */ + /* mainParams.dicts.dicts[ii] = */ + /* linux_dictionary_make( MPPARM(mainParams.util->mpool) */ + /* &mainParams, name, mainParams.useMmap ); */ + /* } */ + /* } */ + /* if ( nextDict < nPlayerDicts ) { */ + /* usage( argv[0], " --player-dict used more times than there are " */ + /* "local players" ); */ + /* } */ } /* if ( !isServer ) { */ @@ -2145,7 +2382,8 @@ main( int argc, char** argv ) /* } */ #ifdef XWFEATURE_WALKDICT if ( !!testDicts ) { - walk_dict_test_all( &mainParams, testDicts, testPrefixes, testMinMax ); + walk_dict_test_all( MPPARM(mainParams.mpool) &mainParams, testDicts, + testPrefixes, testMinMax ); exit( 0 ); } #endif @@ -2210,15 +2448,7 @@ main( int argc, char** argv ) /* mainParams.util->vtable->m_util_makeStreamFromAddr = */ /* linux_util_makeStreamFromAddr; */ - mainParams.util->gameInfo = &mainParams.gi; - - linux_util_vt_init( MPPARM(mainParams.util->mpool) mainParams.util ); - -#ifndef XWFEATURE_STANDALONE_ONLY - mainParams.util->vtable->m_util_informMissing = linux_util_informMissing; - mainParams.util->vtable->m_util_addrChange = linux_util_addrChange; - mainParams.util->vtable->m_util_setIsServer = linux_util_setIsServer; -#endif + // mainParams.util->gameInfo = &mainParams.pgi; srandom( seed ); /* init linux random number generator */ XP_LOGF( "seeded srandom with %d", seed ); @@ -2240,36 +2470,31 @@ main( int argc, char** argv ) mainParams.serverRole = SERVER_ISCLIENT; } - if ( mainParams.needsNewGame ) { - gi_initPlayerInfo( MPPARM(mainParams.util->mpool) - &mainParams.gi, NULL ); - } + /* if ( mainParams.needsNewGame ) { */ + /* gi_disposePlayerInfo( MPPARM(mainParams.mpool) &mainParams.pgi ); */ + /* gi_initPlayerInfo( MPPARM(mainParams.mpool) &mainParams.pgi, NULL ); */ + /* } */ - /* curses doesn't have newgame dialog */ - if ( useCurses && !mainParams.needsNewGame ) { + if ( useCurses ) { + if ( mainParams.needsNewGame ) { + /* curses doesn't have newgame dialog */ + usage( argv[0], "game params required for curses version" ); + } else { #if defined PLATFORM_NCURSES - cursesmain( isServer, &mainParams ); -#endif - } else if ( !useCurses ) { -#if defined PLATFORM_GTK - gtkmain( &mainParams, argc, argv ); + cursesmain( isServer, &mainParams ); #endif + } } else { - usage( argv[0], "rtfm" ); +#if defined PLATFORM_GTK + gtk_init( &argc, &argv ); + gtkmain( &mainParams ); +#endif } - vtmgr_destroy( MPPARM(mainParams.util->mpool) mainParams.vtMgr ); - - linux_util_vt_destroy( mainParams.util ); - - mpool_destroy( mainParams.util->mpool ); - - free( mainParams.util ); - - result = 0; + freeParams( &mainParams ); } - XP_LOGF( "%s exiting main", argv[0] ); + XP_LOGF( "%s exiting main, returning %d", argv[0], result ); return result; } /* main */ diff --git a/xwords4/linux/linuxmain.h b/xwords4/linux/linuxmain.h index 44cf263e5..7429b2fe2 100644 --- a/xwords4/linux/linuxmain.h +++ b/xwords4/linux/linuxmain.h @@ -55,7 +55,7 @@ XP_Bool linuxFireTimer( CommonGlobals* cGlobals, XWTimerReason why ); XWStreamCtxt* stream_from_msgbuf( CommonGlobals* cGlobals, - unsigned char* bufPtr, XP_U16 nBytes ); + const unsigned char* bufPtr, XP_U16 nBytes ); XP_UCHAR* strFromStream( XWStreamCtxt* stream ); void catGameHistory( CommonGlobals* cGlobals ); @@ -73,8 +73,6 @@ XP_Bool getDictPath( const LaunchParams *params, const char* name, GSList* listDicts( const LaunchParams *params ); void saveGame( CommonGlobals* cGlobals ); -int blocking_read( int fd, unsigned char* buf, int len ); - void linux_close_socket( CommonGlobals* cGlobals ); #ifdef KEYBOARD_NAV @@ -94,4 +92,23 @@ void setOneSecondTimer( CommonGlobals* cGlobals ); # define setOneSecondTimer( cGlobals ) #endif +void setupLinuxUtilCallbacks( XW_UtilCtxt* util ); +void initFromParams( CommonGlobals* cGlobals, LaunchParams* params ); +void setupUtil( CommonGlobals* cGlobals ); + +DictionaryCtxt* makeDictForStream( CommonGlobals* cGlobals, + XWStreamCtxt* stream ); +void linuxSetIsServer( CommonGlobals* cGlobals, XP_Bool isServer ); +void linuxChangeRoles( CommonGlobals* cGlobals ); + +void sendRelayReg( LaunchParams* params, sqlite3* pDb ); +void gameGotBuf( CommonGlobals* globals, XP_Bool haveDraw, + const XP_U8* buf, XP_U16 len ); +gboolean app_socket_proc( GIOChannel* source, GIOCondition condition, + gpointer data ); +const XP_UCHAR* linux_getDevID( LaunchParams* params, DevIDType* typ ); + +/* void initParams( LaunchParams* params ); */ +/* void freeParams( LaunchParams* params ); */ + #endif diff --git a/xwords4/linux/linuxsms.c b/xwords4/linux/linuxsms.c index b69f751c6..8d4954783 100644 --- a/xwords4/linux/linuxsms.c +++ b/xwords4/linux/linuxsms.c @@ -1,4 +1,4 @@ -/* -*-mode: C; compile-command: "make -j MEMDEBUG=TRUE";-*- */ +/* -*- compile-command: "make -j MEMDEBUG=TRUE";-*- */ /* * Copyright 2006-2009 by Eric House (xwords@eehouse.org). All rights * reserved. @@ -93,7 +93,7 @@ linux_sms_init( CommonGlobals* globals, const CommsAddrRec* addr ) { LinSMSData* data = globals->smsData; if ( !data ) { - data = XP_MALLOC( globals->params->util->mpool, sizeof(*data) ); + data = XP_MALLOC( globals->util->mpool, sizeof(*data) ); XP_ASSERT( !!data ); XP_MEMSET( data, 0, sizeof(*data) ); globals->smsData = data; @@ -125,7 +125,7 @@ linux_sms_close( CommonGlobals* globals ) { LinSMSData* data = globals->smsData; if ( !!data ) { - XP_FREE( globals->params->util->mpool, data ); + XP_FREE( globals->util->mpool, data ); globals->smsData = NULL; } } /* linux_sms_close */ diff --git a/xwords4/linux/linuxutl.c b/xwords4/linux/linuxutl.c index 650c0c892..3da49dde6 100644 --- a/xwords4/linux/linuxutl.c +++ b/xwords4/linux/linuxutl.c @@ -31,6 +31,8 @@ #include "linuxutl.h" #include "main.h" +#include "linuxdict.h" +#include "linuxmain.h" #include "LocalizedStrIncludes.h" #ifdef DEBUG @@ -148,7 +150,7 @@ setSquareBonuses( const CommonGlobals* cGlobals ) { XP_U16 nBonuses; XWBonusType* bonuses = - bonusesFor( cGlobals->params->gi.boardSize, &nBonuses ); + bonusesFor( cGlobals->gi->boardSize, &nBonuses ); if ( !!bonuses ) { model_setSquareBonuses( cGlobals->game.model, bonuses, nBonuses ); } @@ -348,19 +350,8 @@ linux_util_getUserString( XW_UtilCtxt* XP_UNUSED(uc), XP_U16 code ) static const XP_UCHAR* linux_util_getDevID( XW_UtilCtxt* uc, DevIDType* typ ) { - XP_UCHAR* result; CommonGlobals* cGlobals = (CommonGlobals*)uc->closure; - if ( !!cGlobals->params->rDevID ) { - *typ = ID_TYPE_RELAY; - result = cGlobals->params->rDevID; - } else if ( !!cGlobals->params->devID ) { - *typ = ID_TYPE_LINUX; - result = cGlobals->params->devID; - } else { - *typ = ID_TYPE_NONE; - result = NULL; - } - return result; + return linux_getDevID( cGlobals->params, typ ); } static void @@ -390,6 +381,9 @@ linux_util_deviceRegistered( XW_UtilCtxt* uc, DevIDType typ, void linux_util_vt_init( MPFORMAL XW_UtilCtxt* util ) { +#ifdef MEM_DEBUG + util->mpool = mpool; +#endif util->vtable = XP_MALLOC( mpool, sizeof(UtilVtable) ); util->vtable->m_util_makeEmptyDict = linux_util_makeEmptyDict; @@ -611,7 +605,7 @@ writeNoConnMsgs( CommonGlobals* cGlobals, int fd ) XP_ASSERT( 0 < nMsgs ); XWStreamCtxt* stream = - mem_stream_make( MPPARM(cGlobals->params->util->mpool) + mem_stream_make( MPPARM(cGlobals->util->mpool) cGlobals->params->vtMgr, cGlobals, CHANNEL_NONE, NULL ); stream_putU16( stream, 1 ); /* number of relayIDs */ diff --git a/xwords4/linux/linuxutl.h b/xwords4/linux/linuxutl.h index 781031a32..91ea0ed42 100644 --- a/xwords4/linux/linuxutl.h +++ b/xwords4/linux/linuxutl.h @@ -32,9 +32,6 @@ void linux_debugf(const char*, ...) __attribute__ ((format (printf, 1, 2))); #endif -DictionaryCtxt* linux_dictionary_make( MPFORMAL const LaunchParams* mainParams, - const char* dictFileName, XP_Bool useMMap ); - void linux_util_vt_init( MPFORMAL XW_UtilCtxt* util ); void linux_util_vt_destroy( XW_UtilCtxt* util ); @@ -49,6 +46,7 @@ XP_Bool storeNoConnMsg( CommonGlobals* cGlobals, const XP_U8* msg, XP_U16 len, const XP_UCHAR* relayID ); void writeNoConnMsgs( CommonGlobals* cGlobals, int fd ); + #ifdef STREAM_VERS_BIGBOARD void setSquareBonuses( const CommonGlobals* cGlobals ); #else diff --git a/xwords4/linux/main.h b/xwords4/linux/main.h index fa354b52c..a546f20fd 100644 --- a/xwords4/linux/main.h +++ b/xwords4/linux/main.h @@ -1,6 +1,6 @@ -/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ /* - * Copyright 2001-2007 by Eric House (xwords@eehouse.org). All rights + * Copyright 2001-2013 by Eric House (xwords@eehouse.org). All rights * reserved. * * This program is free software; you can redistribute it and/or @@ -25,6 +25,8 @@ # include /* for bdaddr_t, which should move */ #endif +#include + #include "comtypes.h" #include "util.h" #include "game.h" @@ -42,26 +44,33 @@ typedef struct LinuxUtilCtxt { UtilVtable* vtable; } LinuxUtilCtxt; +typedef void (*SockReceiver)( void* closure, int socket ); +typedef void (*NewSocketProc)( void* closure, int newSock, int oldSock, + SockReceiver proc, void* procClosure ); + typedef struct LaunchParams { /* CommPipeCtxt* pipe; */ - XW_UtilCtxt* util; - DictionaryCtxt* dict; - CurGameInfo gi; - PlayerDicts dicts; + CurGameInfo pgi; + GSList* dictDirs; char* fileName; + char* dbName; + sqlite3* pDb; /* null unless opened */ XP_U16 saveFailPct; const XP_UCHAR* playerDictNames[MAX_NUM_PLAYERS]; #ifdef USE_SQLITE char* dbFileName; XP_U32 dbFileID; #endif + void* relayConStorage; /* opaque outside of relaycon.c */ char* pipe; char* nbs; char* bonusFile; #ifdef XWFEATURE_DEVID char* devID; char* rDevID; + XP_Bool noAnonDevid; + XP_UCHAR devIDStore[16]; #endif VTableMgr* vtMgr; XP_U16 nLocalPlayers; @@ -140,7 +149,7 @@ typedef struct LaunchParams { ServerInfo serverInfo; ClientInfo clientInfo; } info; - + MPSLOT } LaunchParams; typedef struct CommonGlobals CommonGlobals; @@ -165,17 +174,29 @@ typedef struct _TimerInfo { #endif } TimerInfo; +typedef void (*OnSaveFunc)( void* closure, sqlite3_int64 rowid, + XP_Bool firstTime ); + struct CommonGlobals { LaunchParams* params; CommonPrefs cp; + XW_UtilCtxt* util; XWGame game; + CurGameInfo* gi; + CommsAddrRec addr; + DictionaryCtxt* dict; + PlayerDicts dicts; XP_U16 lastNTilesToUse; XP_U16 lastStreamSize; XP_Bool manualFinal; /* use asked for final scores */ + sqlite3* pDb; + sqlite3_int64 selRow; SocketChangedFunc socketChanged; void* socketChangedClosure; + OnSaveFunc onSave; + void* onSaveClosure; GSList* packetQueue; XP_U32 nextPacketID; /* for debugging */ @@ -210,4 +231,24 @@ struct CommonGlobals { XP_U16 curSaveToken; }; +typedef struct _SourceData { + GIOChannel* channel; + gint watch; + SockReceiver proc; + void* procClosure; +} SourceData; + +typedef struct _GtkAppGlobals { + GArray* selRows; + LaunchParams* params; + GSList* globalsList; + GList* sources; + GtkWidget* window; + GtkWidget* listWidget; + GtkWidget* openButton; + GtkWidget* deleteButton; +} GtkAppGlobals; + +sqlite3_int64 getSelRow( const GtkAppGlobals* apg ); + #endif diff --git a/xwords4/linux/relaycon.c b/xwords4/linux/relaycon.c new file mode 100644 index 000000000..4c3aaf753 --- /dev/null +++ b/xwords4/linux/relaycon.c @@ -0,0 +1,364 @@ +/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ +/* + * Copyright 2013 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include + +#include "relaycon.h" +#include "comtypes.h" + +typedef struct _RelayConStorage { + int socket; + RelayConnProcs procs; + void* procsClosure; + struct sockaddr_in saddr; +} RelayConStorage; + +typedef struct _MsgHeader { + XWRelayReg cmd; + uint32_t packetID; +} MsgHeader; + +static RelayConStorage* getStorage( LaunchParams* params ); +static XP_U32 hostNameToIP( const XP_UCHAR* name ); +static void relaycon_receive( void* closure, int socket ); +static ssize_t sendIt( RelayConStorage* storage, const XP_U8* msgbuf, XP_U16 len ); +static size_t addStrWithLength( XP_U8* buf, XP_U8* end, const XP_UCHAR* str ); +static void getNetString( const XP_U8** ptr, XP_U16 len, XP_UCHAR* buf ); +static XP_U16 getNetShort( const XP_U8** ptr ); +static XP_U32 getNetLong( const XP_U8** ptr ); +static int writeHeader( XP_U8* dest, XWRelayReg cmd ); +static bool readHeader( const XP_U8** buf, MsgHeader* header ); +static size_t writeDevID( XP_U8* buf, size_t len, const XP_UCHAR* str ); + + +void +relaycon_init( LaunchParams* params, const RelayConnProcs* procs, + void* procsClosure, const char* host, int port ) +{ + XP_ASSERT( !params->relayConStorage ); + RelayConStorage* storage = getStorage( params ); + XP_MEMCPY( &storage->procs, procs, sizeof(storage->procs) ); + storage->procsClosure = procsClosure; + + storage->socket = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP ); + (*procs->socketChanged)( procsClosure, storage->socket, -1, + relaycon_receive, params ); + + XP_MEMSET( &storage->saddr, 0, sizeof(storage->saddr) ); + storage->saddr.sin_family = PF_INET; + storage->saddr.sin_addr.s_addr = htonl( hostNameToIP(host) ); + storage->saddr.sin_port = htons(port); +} + +void +relaycon_reg( LaunchParams* params, const XP_UCHAR* devID, DevIDType typ ) +{ + LOG_FUNC(); + XP_U8 tmpbuf[32]; + int indx = 0; + + RelayConStorage* storage = getStorage( params ); + XP_ASSERT( !!devID || typ == ID_TYPE_ANON ); + indx += writeHeader( tmpbuf, XWPDEV_REG ); + tmpbuf[indx++] = typ; + indx += writeDevID( &tmpbuf[indx], sizeof(tmpbuf) - indx, devID ); + + sendIt( storage, tmpbuf, indx ); +} + +XP_S16 +relaycon_send( LaunchParams* params, const XP_U8* buf, XP_U16 buflen, + XP_U32 gameToken, const CommsAddrRec* XP_UNUSED(addrRec) ) +{ + XP_ASSERT( 0 != gameToken ); + ssize_t nSent = -1; + RelayConStorage* storage = getStorage( params ); + + XP_U8 tmpbuf[1 + 4 + 1 + sizeof(gameToken) + buflen]; + int indx = 0; + indx += writeHeader( tmpbuf, XWPDEV_MSG ); + XP_U32 inNBO = htonl(gameToken); + XP_MEMCPY( &tmpbuf[indx], &inNBO, sizeof(inNBO) ); + indx += sizeof(inNBO); + XP_MEMCPY( &tmpbuf[indx], buf, buflen ); + indx += buflen; + nSent = sendIt( storage, tmpbuf, indx ); + if ( nSent > buflen ) { + nSent = buflen; + } + LOG_RETURNF( "%d", nSent ); + return nSent; +} + +XP_S16 +relaycon_sendnoconn( LaunchParams* params, const XP_U8* buf, XP_U16 buflen, + const XP_UCHAR* relayID, XP_U32 gameToken ) +{ + XP_LOGF( "%s(relayID=%s)", __func__, relayID ); + XP_ASSERT( 0 != gameToken ); + XP_U16 indx = 0; + ssize_t nSent = -1; + RelayConStorage* storage = getStorage( params ); + + XP_U16 idLen = XP_STRLEN( relayID ); + XP_U8 tmpbuf[1 + 4 + 1 + + 1 + idLen + + sizeof(gameToken) + buflen]; + indx += writeHeader( tmpbuf, XWPDEV_MSGNOCONN ); + gameToken = htonl( gameToken ); + XP_MEMCPY( &tmpbuf[indx], &gameToken, sizeof(gameToken) ); + indx += sizeof(gameToken); + XP_MEMCPY( &tmpbuf[indx], relayID, idLen ); + indx += idLen; + tmpbuf[indx++] = '\n'; + XP_MEMCPY( &tmpbuf[indx], buf, buflen ); + nSent = sendIt( storage, tmpbuf, sizeof(tmpbuf) ); + if ( nSent > buflen ) { + nSent = buflen; + } + LOG_RETURNF( "%d", nSent ); + return nSent; +} + +void +relaycon_requestMsgs( LaunchParams* params, const XP_UCHAR* devID ) +{ + XP_LOGF( "%s(devID=%s)", __func__, devID ); + RelayConStorage* storage = getStorage( params ); + + XP_U8 tmpbuf[128]; + int indx = 0; + indx += writeHeader( tmpbuf, XWPDEV_RQSTMSGS ); + indx += addStrWithLength( &tmpbuf[indx], tmpbuf + sizeof(tmpbuf), devID ); + + sendIt( storage, tmpbuf, indx ); +} + +void +relaycon_deleted( LaunchParams* params, const XP_UCHAR* devID, + XP_U32 gameToken ) +{ + LOG_FUNC(); + RelayConStorage* storage = getStorage( params ); + XP_U8 tmpbuf[128]; + int indx = 0; + indx += writeHeader( tmpbuf, XWPDEV_DELGAME ); + indx += writeDevID( &tmpbuf[indx], sizeof(tmpbuf) - indx, devID ); + gameToken = htonl( gameToken ); + memcpy( &tmpbuf[indx], &gameToken, sizeof(gameToken) ); + indx += sizeof( gameToken ); + + sendIt( storage, tmpbuf, indx ); +} + +static void +sendAckIf( RelayConStorage* storage, const MsgHeader* header ) +{ + if ( header->cmd != XWPDEV_ACK ) { + XP_U8 tmpbuf[16]; + int indx = writeHeader( tmpbuf, XWPDEV_ACK ); + uint32_t msgID = htonl( header->packetID ); + memcpy( &tmpbuf[indx], &msgID, sizeof(msgID) ); + indx += sizeof(msgID); + sendIt( storage, tmpbuf, indx ); + } +} + +static void +relaycon_receive( void* closure, int socket ) +{ + LaunchParams* params = (LaunchParams*)closure; + XP_ASSERT( !!params->relayConStorage ); + RelayConStorage* storage = getStorage( params ); + XP_U8 buf[512]; + struct sockaddr_in from; + socklen_t fromlen = sizeof(from); + + XP_LOGF( "%s: calling recvfrom on socket %d", __func__, socket ); + + ssize_t nRead = recvfrom( socket, buf, sizeof(buf), 0, /* flags */ + (struct sockaddr*)&from, &fromlen ); + XP_LOGF( "%s: read %d bytes", __func__, nRead ); + if ( 0 <= nRead ) { + const XP_U8* ptr = buf; + const XP_U8* end = buf + nRead; + MsgHeader header; + if ( readHeader( &ptr, &header ) ) { + sendAckIf( storage, &header ); + switch( header.cmd ) { + case XWPDEV_REGRSP: { + XP_U16 len = getNetShort( &ptr ); + XP_UCHAR devID[len+1]; + getNetString( &ptr, len, devID ); + (*storage->procs.devIDChanged)( storage->procsClosure, devID ); + } + break; + case XWPDEV_MSG: + (*storage->procs.msgReceived)( storage->procsClosure, + ptr, end - ptr ); + break; + case XWPDEV_BADREG: + (*storage->procs.devIDChanged)( storage->procsClosure, NULL ); + break; + case XWPDEV_HAVEMSGS: { + (*storage->procs.msgNoticeReceived)( storage->procsClosure ); + break; + } + case XWPDEV_ALERT: { + XP_U16 len = getNetShort( &ptr ); + XP_UCHAR buf[len+1]; + getNetString( &ptr, len, buf ); + (*storage->procs.msgErrorMsg)( storage->procsClosure, buf ); + break; + } + case XWPDEV_ACK: { + XP_U32 packetID = getNetLong( &ptr ); + XP_LOGF( "got ack for packetID %ld", packetID ); + break; + } + default: + XP_LOGF( "%s: Unexpected cmd %d", __func__, header.cmd ); + XP_ASSERT( 0 ); + } + } + } else { + XP_LOGF( "%s: error reading udp socket: %d (%s)", __func__, + errno, strerror(errno) ); + } +} + +void +relaycon_cleanup( LaunchParams* params ) +{ + XP_FREEP( params->mpool, ¶ms->relayConStorage ); +} + +static RelayConStorage* +getStorage( LaunchParams* params ) +{ + RelayConStorage* storage = (RelayConStorage*)params->relayConStorage; + if ( NULL == storage ) { + storage = XP_CALLOC( params->mpool, sizeof(*storage) ); + storage->socket = -1; + params->relayConStorage = storage; + } + return storage; +} + +static XP_U32 +hostNameToIP( const XP_UCHAR* name ) +{ + XP_U32 ip; + struct hostent* host; + XP_LOGF( "%s: looking up %s", __func__, name ); + host = gethostbyname( name ); + if ( NULL == host ) { + XP_WARNF( "gethostbyname returned NULL\n" ); + } else { + XP_MEMCPY( &ip, host->h_addr_list[0], sizeof(ip) ); + ip = ntohl(ip); + } + XP_LOGF( "%s found %lx for %s", __func__, ip, name ); + return ip; +} + +static ssize_t +sendIt( RelayConStorage* storage, const XP_U8* msgbuf, XP_U16 len ) +{ + ssize_t nSent = sendto( storage->socket, msgbuf, len, 0, /* flags */ + (struct sockaddr*)&storage->saddr, + sizeof(storage->saddr) ); + XP_LOGF( "%s()=>%d", __func__, nSent ); + return nSent; +} + +static size_t +addStrWithLength( XP_U8* buf, XP_U8* end, const XP_UCHAR* str ) +{ + XP_U16 len = !!str? XP_STRLEN( str ) : 0; + if ( buf + len + sizeof(len) <= end ) { + XP_U16 lenNBO = htons( len ); + XP_MEMCPY( buf, &lenNBO, sizeof(lenNBO) ); + buf += sizeof(lenNBO); + XP_MEMCPY( buf, str, len ); + } + return len + sizeof(len); +} + +static size_t +writeDevID( XP_U8* buf, size_t len, const XP_UCHAR* str ) +{ + return addStrWithLength( buf, buf + len, str ); +} + +static XP_U16 +getNetShort( const XP_U8** ptr ) +{ + XP_U16 result; + memcpy( &result, *ptr, sizeof(result) ); + *ptr += sizeof(result); + return ntohs( result ); +} + +static XP_U32 +getNetLong( const XP_U8** ptr ) +{ + XP_U32 result; + memcpy( &result, *ptr, sizeof(result) ); + *ptr += sizeof(result); + return ntohl( result ); +} + +static void +getNetString( const XP_U8** ptr, XP_U16 len, XP_UCHAR* buf ) +{ + memcpy( buf, *ptr, len ); + *ptr += len; + buf[len] = '\0'; +} + +static int +writeHeader( XP_U8* dest, XWRelayReg cmd ) +{ + int indx = 0; + dest[indx++] = XWPDEV_PROTO_VERSION; + uint32_t packetNum = htonl(0); + memcpy( &dest[indx], &packetNum, sizeof(packetNum) ); + indx += sizeof(packetNum); + dest[indx++] = cmd; + return indx; +} + +static bool +readHeader( const XP_U8** buf, MsgHeader* header ) +{ + const XP_U8* ptr = *buf; + bool ok = XWPDEV_PROTO_VERSION == *ptr++; + assert( ok ); + uint32_t packetID; + memcpy( &packetID, ptr, sizeof(packetID) ); + ptr += sizeof(packetID); + header->packetID = ntohl( packetID ); + XP_LOGF( "%s: got packet %d", __func__, header->packetID ); + header->cmd = *ptr++; + *buf = ptr; + return ok; +} diff --git a/xwords4/linux/relaycon.h b/xwords4/linux/relaycon.h new file mode 100644 index 000000000..8b21f6df5 --- /dev/null +++ b/xwords4/linux/relaycon.h @@ -0,0 +1,47 @@ +/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ +/* + * Copyright 2013 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _RELAYCON_H_ +#define _RELAYCON_H_ + +#include "main.h" + +typedef struct _Procs { + void (*msgReceived)( void* closure, const XP_U8* buf, XP_U16 len ); + void (*msgNoticeReceived)( void* closure ); + void (*devIDChanged)( void* closure, const XP_UCHAR* devID ); + void (*msgErrorMsg)( void* closure, const XP_UCHAR* msg ); + void (*socketChanged)( void* closure, int newSock, int oldSock, + SockReceiver proc, void* procClosure ); + +} RelayConnProcs; + +void relaycon_init( LaunchParams* params, const RelayConnProcs* procs, + void* procsClosure, const char* host, int port ); +void relaycon_reg( LaunchParams* params, const XP_UCHAR* devID, DevIDType typ ); +XP_S16 relaycon_send( LaunchParams* params, const XP_U8* buf, XP_U16 buflen, + XP_U32 gameID, const CommsAddrRec* addrRec ); +XP_S16 relaycon_sendnoconn( LaunchParams* params, const XP_U8* buf, XP_U16 buflen, + const XP_UCHAR* relayID, XP_U32 gameToken ); +void relaycon_requestMsgs( LaunchParams* params, const XP_UCHAR* devID ); +void relaycon_deleted( LaunchParams* params, const XP_UCHAR* devID, + XP_U32 gameToken ); + +void relaycon_cleanup( LaunchParams* params ); +#endif diff --git a/xwords4/linux/scripts/discon_ok2.sh b/xwords4/linux/scripts/discon_ok2.sh index 77082de60..8c7dfdc3c 100755 --- a/xwords4/linux/scripts/discon_ok2.sh +++ b/xwords4/linux/scripts/discon_ok2.sh @@ -6,6 +6,7 @@ APP_NEW="" DO_CLEAN="" APP_NEW_PARAMS="" NGAMES="" +UDP_PCT=0 UPGRADE_ODDS="" NROOMS="" HOST="" @@ -188,7 +189,13 @@ build_cmds() { DEV=0 for NLOCALS in ${LOCALS[@]}; do DEV=$((DEV + 1)) - FILE="${LOGDIR}/GAME_${GAME}_${DEV}.xwg" + if [ $((RANDOM % 100)) -gt $UDP_PCT ]; then + FILE="${LOGDIR}/GAME_${GAME}_${DEV}.xwg" + USE_UDP="" + else + FILE="${LOGDIR}/GAME_${GAME}_${DEV}.sql3" + USE_UDP=1 + fi LOG=${LOGDIR}/${GAME}_${DEV}_LOG.txt > $LOG # clear the log @@ -209,10 +216,15 @@ build_cmds() { PARAMS="$PARAMS $BOARD_SIZE --room $ROOM --trade-pct 20 --sort-tiles " [ $UNDO_PCT -gt 0 ] && PARAMS="$PARAMS --undo-pct $UNDO_PCT " PARAMS="$PARAMS --game-dict $DICT --port $PORT --host $HOST " - PARAMS="$PARAMS --file $FILE --slow-robot 1:3 --skip-confirm" + PARAMS="$PARAMS --slow-robot 1:3 --skip-confirm" + if [ -n "$USE_UDP" ]; then + PARAMS="$PARAMS --db $FILE" + else + PARAMS="$PARAMS --file $FILE" + fi PARAMS="$PARAMS --drop-nth-packet $DROP_N $PLAT_PARMS" # PARAMS="$PARAMS --split-packets 2" - if [ -n $SEND_CHAT ]; then + if [ -n "$SEND_CHAT" ]; then PARAMS="$PARAMS --send-chat $SEND_CHAT" fi # PARAMS="$PARAMS --savefail-pct 10" @@ -316,7 +328,7 @@ kill_from_log() { if [ -n "$RELAYID" ]; then OBITS="$OBITS -d $RELAYID" if [ 0 -eq $(($RANDOM%2)) ]; then - ../relay/rq -a $HOST $OBITS 2>/dev/null || true + ../relay/rq -a $HOST $OBITS 2>/dev/null || /bin/true OBITS="" fi return 0 # success @@ -332,7 +344,7 @@ maybe_resign() { if grep -q XWRELAY_ALLHERE $LOG; then if [ 0 -eq $(($RANDOM % $RESIGN_RATIO)) ]; then echo "making $LOG $(connName $LOG) resign..." - kill_from_log $LOG && close_device $KEY $DEADDIR "resignation forced" || true + kill_from_log $LOG && close_device $KEY $DEADDIR "resignation forced" || /bin/true fi fi fi @@ -380,18 +392,18 @@ check_game() { # kill_from_logs $OTHERS $KEY for ID in $OTHERS $KEY; do echo -n "${LOGS[$ID]}, " - kill_from_log ${LOGS[$ID]} || true + kill_from_log ${LOGS[$ID]} || /bin/true close_device $ID $DONEDIR "game over" done echo "" # XWRELAY_ERROR_DELETED may be old elif grep -q 'relay_error_curses(XWRELAY_ERROR_DELETED)' $LOG; then echo "deleting $LOG $(connName $LOG) b/c another resigned" - kill_from_log $LOG || true + kill_from_log $LOG || /bin/true close_device $KEY $DEADDIR "other resigned" elif grep -q 'relay_error_curses(XWRELAY_ERROR_DEADGAME)' $LOG; then echo "deleting $LOG $(connName $LOG) b/c another resigned" - kill_from_log $LOG || true + kill_from_log $LOG || /bin/true close_device $KEY $DEADDIR "other resigned" else maybe_resign $KEY @@ -423,7 +435,7 @@ get_relayid() { update_devid_cmd() { KEY=$1 - HELP="$(${APPS[$KEY]} --help 2>&1 || true)" + HELP="$(${APPS[$KEY]} --help 2>&1 || /bin/true)" if echo $HELP | grep -q '\-\-devid'; then CMD="--devid LINUX_TEST_$(printf %.5d ${KEY})" LOG=${LOGS[$KEY]} @@ -467,7 +479,8 @@ run_cmds() { try_upgrade $KEY launch $KEY & PID=$! - renice +1 $PID >/dev/null + # renice doesn't work on one of my machines... + renice -n 1 -p $PID >/dev/null 2>&1 || /bin/true PIDS[$KEY]=$PID ROOM_PIDS[$ROOM]=$PID MINEND[$KEY]=$(($NOW + $MINRUN)) @@ -476,7 +489,7 @@ run_cmds() { if [ -d /proc/$PID ]; then SLEEP=$((${MINEND[$KEY]} - $NOW)) [ $SLEEP -gt 0 ] && sleep $SLEEP - kill $PID || true + kill $PID || /bin/true wait $PID fi PIDS[$KEY]=0 @@ -487,7 +500,7 @@ run_cmds() { fi done - [ -n "$OBITS" ] && ../relay/rq -a $HOST $OBITS 2>/dev/null || true + [ -n "$OBITS" ] && ../relay/rq -a $HOST $OBITS 2>/dev/null || /bin/true # kill any remaining games if [ $COUNT -gt 0 ]; then @@ -528,7 +541,7 @@ run_via_rq() { launch $KEY & PID=$! sleep 2 - kill $PID || true + kill $PID || /bin/true wait $PID fi [ "$DROP_N" -ge 0 ] && increment_drop $KEY @@ -544,6 +557,7 @@ function getArg() { function usage() { [ $# -gt 0 ] && echo "Error: $1" >&2 echo "Usage: $(basename $0) \\" >&2 + echo " [--via-udp ] \\" >&2 echo " [--clean-start] \\" >&2 echo " [--game-dict ]* \\" >&2 echo " [--old-app &2 @@ -571,6 +585,10 @@ function usage() { while [ "$#" -gt 0 ]; do case $1 in + --via-udp) + UDP_PCT=$(getArg $*) + shift + ;; --clean-start) DO_CLEAN=1 ;; @@ -626,14 +644,14 @@ while [ "$#" -gt 0 ]; do UNDO_PCT=$(getArg $*) shift ;; - --send-chat) + --send-chat) SEND_CHAT=$(getArg $*) shift ;; - --resign-ratio) - RESIGN_RATIO=$(getArg $*) - shift - ;; + --resign-ratio) + RESIGN_RATIO=$(getArg $*) + shift + ;; --help) usage ;; diff --git a/xwords4/relay/scripts/list-psql-users.sh b/xwords4/relay/scripts/list-psql-users.sh new file mode 100755 index 000000000..d6f1b8fbd --- /dev/null +++ b/xwords4/relay/scripts/list-psql-users.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +# Show pids of processes holding onto the xwgames DB in some way + +# from http://newstrib.com/main.asp?SectionID=2&SubSectionID=27&ArticleID=26068 + +echo "select pg_class.relname,pg_locks.* from pg_class,pg_locks where pg_class.relfilenode=pg_locks.relation;" | psql xwgames diff --git a/xwords4/relay/xwrelay.cpp b/xwords4/relay/xwrelay.cpp index 395bdcd2d..c695a6484 100644 --- a/xwords4/relay/xwrelay.cpp +++ b/xwords4/relay/xwrelay.cpp @@ -1053,7 +1053,7 @@ handlePutMessage( SafeCref& scr, HostID hid, const AddrInfo* addr, if ( getNetByte( bufp, end, &cmd ) && getNetByte( bufp, end, &src ) && getNetByte( bufp, end, &dest ) ) { - success = true; // meaning, buffer content looks ok + success = true; // meaning, buffer content looks ok *bufp = start + len; if ( ( cmd == XWRELAY_MSG_TORELAY_NOCONN ) && ( hid == dest ) ) { scr.PutMsg( src, addr, dest, start, len ); @@ -1092,7 +1092,7 @@ handleProxyMsgs( int sock, const AddrInfo* addr, const unsigned char* bufp, unsigned short nMsgs; if ( getNetShort( &bufp, end, &nMsgs ) ) { SafeCref scr( connName ); - while ( nMsgs-- > 0 ) { + while ( scr.IsValid() && nMsgs-- > 0 ) { unsigned short len; if ( getNetShort( &bufp, end, &len ) ) { if ( handlePutMessage( scr, hid, addr, len, &bufp, end ) ) {