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 ) ) {