auto import from //depot/cupcake/@135843
diff --git a/simulator/wrapsim/SimMgr.c b/simulator/wrapsim/SimMgr.c
new file mode 100644
index 0000000..a35c7c6
--- /dev/null
+++ b/simulator/wrapsim/SimMgr.c
@@ -0,0 +1,988 @@
+/*
+ * Copyright 2007 The Android Open Source Project
+ *
+ * Simulator interactions.
+ *
+ * TODO: for multi-process we probably need to switch to a new process
+ * group if we are the first process (could be runtime, could be gdb),
+ * rather than wait for the simulator to tell us to switch.
+ */
+#include "Common.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#include <sys/sem.h>
+#include <sys/un.h>
+#include <signal.h>
+#include <assert.h>
+
+// fwd
+static int connectToSim(void);
+static void listenToSim(void);
+
+/*
+ * Env var to restrict who tries to talk to the front end.
+ */
+#define kWrapSimConnectedEnv    "WRAP_SIM_CONNECTED"
+
+
+/*
+ * Signal the main thread that we're ready to continue.
+ */
+static void signalMainThread(void)
+{
+    int cc;
+
+    cc = pthread_mutex_lock(&gWrapSim.startLock);
+    assert(cc == 0);
+
+    gWrapSim.startReady = 1;
+
+    cc = pthread_cond_signal(&gWrapSim.startCond);
+    assert(cc == 0);
+
+    cc = pthread_mutex_unlock(&gWrapSim.startLock);
+    assert(cc == 0);
+}
+
+
+/*
+ * Entry point for the sim management thread.
+ *
+ * Once we have established a connection to the simulator and are ready
+ * for other threads to send messages, we signal the main thread.
+ */
+static void* simThreadEntry(void* arg)
+{
+    wsLog("--- sim manager thread started\n");
+
+    /*
+     * Establish a connection to the simulator front-end.  If we can't do
+     * that, we have no access to input or output devices, and we might
+     * as well give up.
+     */
+    if (connectToSim() != 0) {
+        signalMainThread();
+        return NULL;
+    }
+
+    /* success! */
+    wsLog("--- sim manager thread ready\n");
+    gWrapSim.simulatorInitFailed = 0;
+    signalMainThread();
+
+    listenToSim();
+
+    wsLog("--- sim manager thread exiting\n");
+
+    return NULL;
+}
+
+/*
+ * If we think we're not yet connected to the sim, do so now.  We only
+ * want to do this once per process *group*, so we control access with
+ * an environment variable.
+ */
+int wsSimConnect(void)
+{
+    /*
+     * If the environment variable hasn't been set, assume we're the first
+     * to get here, and should attach to the simulator.  We set the env
+     * var here whether or not we succeed in connecting to the sim.
+     *
+     * (For correctness we should wrap the getenv/setenv in a semaphore.)
+     */
+    if (getenv(kWrapSimConnectedEnv) == NULL) {
+        pthread_attr_t threadAttr;
+        pthread_t threadHandle;
+        int cc;
+
+        gWrapSim.simulatorInitFailed = 1;
+        setenv(kWrapSimConnectedEnv, "1", 1);
+
+        cc = pthread_mutex_lock(&gWrapSim.startLock);
+        assert(cc == 0);
+
+        pthread_attr_init(&threadAttr);
+        pthread_attr_setdetachstate(&threadAttr, PTHREAD_CREATE_DETACHED);
+        cc = pthread_create(&threadHandle, &threadAttr, simThreadEntry, NULL);
+        if (cc != 0) {
+            wsLog("Unable to create new thread: %s\n", strerror(errno));
+            abort();
+        }
+
+        while (!gWrapSim.startReady) {
+            cc = pthread_cond_wait(&gWrapSim.startCond, &gWrapSim.startLock);
+            assert(cc == 0);
+        }
+
+        cc = pthread_mutex_unlock(&gWrapSim.startLock);
+        assert(cc == 0);
+
+        if (gWrapSim.simulatorInitFailed) {
+            wsLog("Simulator initialization failed, bailing\n");
+
+            /* this *should* be okay to do */
+            fprintf(stderr, "Fatal error:"
+                " unable to connect to sim front-end (not running?)\n");
+            abort();
+        }
+    }
+
+    wsLog("+++ continuing\n");
+    return 0;
+}
+
+
+/*
+ * ===========================================================================
+ *      Message / MessageStream
+ * ===========================================================================
+ */
+
+/*
+ * This is a quick & dirty rewrite of the C++ Message and MessageStream
+ * classes, ported to C, reduced in generality, with syscalls stubbed
+ * where necessary.  I didn't fix the API to make it more sensible in C
+ * (which lacks destructors), so some of this is a little fragile or
+ * awkward.
+ */
+
+/* values for message type byte; must match android::Message constants */
+typedef enum MessageType {
+    kTypeUnknown = 0,
+    kTypeRaw,           // chunk of raw data
+    kTypeConfig,        // send a name=value pair to peer
+    kTypeCommand,       // simple command w/arg
+    kTypeCommandExt,    // slightly more complicated command
+    kTypeLogBundle,     // multi-part log message
+} MessageType;
+
+/*
+ * Reusable message object.
+ */
+typedef struct Message {
+    MessageType     mType;
+    unsigned char*  mData;
+    int             mLength;
+} Message;
+
+/* magic init messages; must match android::MessageStream constants */
+enum {
+    kHelloMsg       = 0x4e303047,       // 'N00G'
+    kHelloAckMsg    = 0x31455221,       // '1ER!'
+};
+
+
+/*
+ * Clear out a Message.
+ */
+static void Message_clear(Message* msg)
+{
+    memset(msg, 0, sizeof(Message));
+}
+
+/*
+ * Keep reading until we get all bytes or hit EOF/error.  "fd" is expected
+ * to be in blocking mode.
+ *
+ * Returns 0 on success.
+ */
+static int readAll(int fd, void* buf, size_t count)
+{
+    ssize_t actual;
+    ssize_t have;
+
+    have = 0;
+    while (have != (ssize_t) count) {
+        actual = _ws_read(fd, ((char*) buf) + have, count - have);
+        if (actual < 0) {
+            if (errno == EINTR)
+                continue;
+            wsLog("read %d failed: %s\n", fd, strerror(errno));
+        } else if (actual == 0) {
+            wsLog("early EOF on %d\n", fd);
+            return -1;
+        } else {
+            have += actual;
+        }
+
+        assert(have <= (ssize_t)count);
+    }
+
+    return 0;
+}
+
+#if 0
+/*
+ * Keep writing until we put all bytes or hit an error.  "fd" is expected
+ * to be in blocking mode.
+ *
+ * Returns 0 on success.
+ */
+static int writeAll(int fd, const void* buf, size_t count)
+{
+    ssize_t actual;
+    ssize_t have;
+
+    have = 0;
+    while (have != count) {
+        actual = _ws_write(fd, ((const char*) buf) + have, count - have);
+        if (actual < 0) {
+            if (errno == EINTR)
+                continue;
+            wsLog("write %d failed: %s\n", fd, strerror(errno));
+        } else if (actual == 0) {
+            wsLog("wrote zero on %d\n", fd);
+            return -1;
+        } else {
+            have += actual;
+        }
+
+        assert(have <= count);
+    }
+
+    return 0;
+}
+#endif
+
+/*
+ * Read a message from the specified file descriptor.
+ *
+ * The caller must Message_release(&msg).
+ *
+ * We guarantee 32-bit alignment for msg->mData.
+ */
+static int Message_read(Message* msg, int fd)
+{
+    unsigned char header[4];
+
+    readAll(fd, header, 4);
+
+    msg->mType = (MessageType) header[2];
+    msg->mLength = header[0] | header[1] << 8;
+    msg->mLength -= 2;   // we already read two of them in the header
+
+    if (msg->mLength > 0) {
+        int actual;
+
+        /* Linux malloc guarantees at least 32-bit alignment */
+        msg->mData = (unsigned char*) malloc(msg->mLength);
+        if (msg->mData == NULL) {
+            wsLog("alloc %d failed\n", msg->mLength);
+            return -1;
+        }
+
+        if (readAll(fd, msg->mData, msg->mLength) != 0) {
+            wsLog("failed reading message body (wanted %d)\n", msg->mLength);
+            return -1;
+        }
+    } else {
+        msg->mData = NULL;
+    }
+
+    return 0;
+}
+
+/*
+ * Write a message to the specified file descriptor.
+ *
+ * The caller must Message_release(&msg).
+ */
+static int Message_write(Message* msg, int fd)
+{
+    struct iovec writeVec[2];
+    unsigned char header[4];
+    int len, numVecs;
+    ssize_t actual;
+
+    len = msg->mLength + 2;
+    header[0] = len & 0xff;
+    header[1] = (len >> 8) & 0xff;
+    header[2] = msg->mType;
+    header[3] = 0;
+    writeVec[0].iov_base = header;
+    writeVec[0].iov_len = 4;
+    numVecs = 1;
+
+    if (msg->mLength > 0) {
+        assert(msg->mData != NULL);
+        writeVec[1].iov_base = msg->mData;
+        writeVec[1].iov_len = msg->mLength;
+        numVecs++;
+    }
+
+    /* write it all in one shot; not worrying about partial writes for now */
+    actual = _ws_writev(fd, writeVec, numVecs);
+    if (actual != len+2) {
+        wsLog("failed writing message to fd %d: %d of %d %s\n",
+            fd, actual, len+2, strerror(errno));
+        return -1;
+    }
+
+    return 0;
+}
+
+/*
+ * Release storage associated with a Message.
+ */
+static void Message_release(Message* msg)
+{
+    free(msg->mData);
+    msg->mData = NULL;
+}
+
+/*
+ * Extract a name/value pair from a message.
+ */
+static int getConfig(const Message* msg, const char** name, const char** val)
+{
+    if (msg->mLength < 2) {
+        wsLog("message len (%d) is too short\n", msg->mLength);
+        return -1;
+    }
+    const char* ptr = (const char*) msg->mData;
+
+    *name = (const char*) ptr;
+    *val = (const char*) (ptr + strlen((char*)ptr) +1);
+    return 0;
+}
+
+/*
+ * Extract a command from a message.
+ */
+static int getCommand(const Message* msg, int* pCmd, int* pArg)
+{
+    if (msg->mLength != sizeof(int) * 2) {
+        wsLog("message len (%d) is wrong for cmd (%d)\n",
+            msg->mLength, sizeof(int) * 2);
+        return -1;
+    }
+
+    /* this assumes 32-bit alignment on mData */
+    const int* ptr = (const int*) msg->mData;
+
+    *pCmd = ptr[0];
+    *pArg = ptr[1];
+
+    return 0;
+}
+
+/*
+ * Extract an extended command from a message.
+ */
+static int getCommandExt(const Message* msg, int* pCmd, int* pArg0,
+    int* pArg1, int* pArg2)
+{
+    if (msg->mLength != sizeof(int) * 4) {
+        wsLog("message len (%d) is wrong for cmd (%d)\n",
+            msg->mLength, sizeof(int) * 4);
+        return -1;
+    }
+
+    /* this assumes 32-bit alignment on mData */
+    const int* ptr = (const int*) msg->mData;
+
+    *pCmd = ptr[0];
+    *pArg0 = ptr[1];
+    *pArg1 = ptr[2];
+    *pArg2 = ptr[3];
+
+    return 0;
+}
+
+/*
+ * Attach 8 bytes of data with "cmd" and "arg" to "msg".
+ *
+ * "msg->mData" will need to be freed by the caller.  (This approach made
+ * more sense when C++ destructors were available, but it's just not worth
+ * reworking it.)
+ */
+static int setCommand(Message* msg, int cmd, int arg)
+{
+    Message_clear(msg);
+
+    msg->mLength = 8;
+    msg->mData = malloc(msg->mLength);
+    msg->mType = kTypeCommand;
+
+    /* assumes 32-bit alignment on malloc blocks */
+    int* pInt = (int*) msg->mData;
+    pInt[0] = cmd;
+    pInt[1] = arg;
+
+    return 0;
+}
+
+/*
+ * Construct the full path.  The caller must free() the return value.
+ */
+static char* makeFilename(const char* name)
+{
+    static const char* kBasePath = "/tmp/android-";
+    char* fileName;
+
+    assert(name != NULL && name[0] != '\0');
+
+    fileName = (char*) malloc(strlen(kBasePath) + strlen(name) + 1);
+    strcpy(fileName, kBasePath);
+    strcat(fileName, name);
+
+    return fileName;
+}
+
+/*
+ * Attach to a SysV shared memory segment.
+ */
+static int attachToShmem(int key, int* pShmid, void** pAddr, long* pLength)
+{
+    int shmid;
+
+    shmid = shmget(key, 0, 0);
+    if (shmid == -1) {
+        wsLog("ERROR: failed to find shmem key=%d\n", key);
+        return -1;
+    }
+
+    void* addr = shmat(shmid, NULL, 0);
+    if (addr == (void*) -1) {
+        wsLog("ERROR: could not attach to key=%d shmid=%d\n", key, shmid);
+        return -1;
+    }
+
+    struct shmid_ds shmids;
+    int cc;
+
+    cc = shmctl(shmid, IPC_STAT, &shmids);
+    if (cc != 0) {
+        wsLog("ERROR: could not IPC_STAT shmid=%d\n", shmid);
+        return -1;
+    }
+    *pLength = shmids.shm_segsz;
+
+    *pAddr = addr;
+    *pShmid = shmid;
+    return 0;
+}
+
+/*
+ * Attach to a SysV semaphore.
+ */
+static int attachToSem(int key, int* pSemid)
+{
+    int semid;
+
+    semid = semget(key, 0, 0);
+    if (semid == -1) {
+        wsLog("ERROR: failed to attach to semaphore key=%d\n", key);
+        return -1;
+    }
+
+    *pSemid = semid;
+    return 0;
+}
+
+/*
+ * "Adjust" a semaphore.
+ */
+static int adjustSem(int semid, int adj)
+{
+    const int wait = 1;
+    struct sembuf op;
+    int cc;
+
+    op.sem_num = 0;
+    op.sem_op = adj;
+    op.sem_flg = SEM_UNDO;
+    if (!wait)
+        op.sem_flg |= IPC_NOWAIT;
+
+    cc = semop(semid, &op, 1);
+    if (cc != 0) {
+        if (wait || errno != EAGAIN) {
+            wsLog("Warning:"
+                " semaphore adjust by %d failed for semid=%d (errno=%d)\n",
+                adj, semid, errno);
+        }
+        return -1;
+    }
+
+    return 0;
+}
+
+/*
+ * Acquire the semaphore associated with a display.
+ */
+void wsLockDisplay(int displayIdx)
+{
+    assert(displayIdx >= 0 && displayIdx < gWrapSim.numDisplays);
+    int semid = gWrapSim.display[displayIdx].semid;
+
+    (void) adjustSem(semid, -1);
+}
+
+/*
+ * Acquire the semaphore associated with a display.
+ */
+void wsUnlockDisplay(int displayIdx)
+{
+    assert(displayIdx >= 0 && displayIdx < gWrapSim.numDisplays);
+    int semid = gWrapSim.display[displayIdx].semid;
+
+    (void) adjustSem(semid, 1);
+}
+
+/*
+ * Process the display config from the simulator
+ *
+ * Right now this is a blob of raw data that looks like this:
+ *  +00 magic number
+ *  +04 #of displays
+ *  +08 display 0:
+ *      +00 width
+ *      +04 height
+ *      +08 format
+ *      +0c refresh rate
+ *      +10 shmem key
+ *  +1c display 1...
+ */
+static int handleDisplayConfig(const int* pData, int length)
+{
+    int numDisplays;
+
+    if (length < 8) {
+        wsLog("Bad display config: length is %d\n", length);
+        return -1;
+    }
+    assert(*pData == kDisplayConfigMagic);
+
+    /*
+     * Pull out the #of displays.  If it looks valid, configure the runtime.
+     */
+    pData++;        // skip over magic
+    numDisplays = *pData++;
+
+    if (numDisplays <= 0 || numDisplays > kMaxDisplays) {
+        wsLog("Bizarre display count %d\n", numDisplays);
+        return -1;
+    }
+    if (length != 8 + numDisplays * kValuesPerDisplay * (int)sizeof(int)) {
+        wsLog("Bad display config: length is %d (expected %d)\n",
+            length, 8 + numDisplays * kValuesPerDisplay * (int)sizeof(int));
+        return -1;
+    }
+
+    /*
+     * Extract the config values.
+     *
+     * The runtime doesn't support multiple devices, so we don't either.
+     */
+    int i;
+    for (i = 0; i < numDisplays; i++) {
+        gWrapSim.display[i].width = pData[0];
+        gWrapSim.display[i].height = pData[1];
+        gWrapSim.display[i].shmemKey = pData[4];
+        /* format/refresh no longer needed */
+
+        void* addr;
+        int shmid, semid;
+        long length;
+        if (attachToShmem(gWrapSim.display[i].shmemKey, &shmid, &addr,
+                &length) != 0)
+        {
+            wsLog("Unable to connect to shared memory\n");
+            return -1;
+        }
+
+        if (attachToSem(gWrapSim.display[i].shmemKey, &semid) != 0) {
+            wsLog("Unable to attach to sempahore\n");
+            return -1;
+        }
+
+        gWrapSim.display[i].shmid = shmid;
+        gWrapSim.display[i].addr = addr;
+        gWrapSim.display[i].length = length;
+        gWrapSim.display[i].semid = semid;
+
+        wsLog("Display %d: width=%d height=%d\n",
+            i,
+            gWrapSim.display[i].width,
+            gWrapSim.display[i].height);
+        wsLog("  shmem=0x%08x addr=%p len=%ld semid=%d\n",
+            gWrapSim.display[i].shmemKey,
+            gWrapSim.display[i].addr,
+            gWrapSim.display[i].length,
+            gWrapSim.display[i].semid);
+
+        pData += kValuesPerDisplay;
+    }
+    gWrapSim.numDisplays = numDisplays;
+
+    return 0;
+}
+
+
+/*
+ * Initialize our connection to the simulator, which will be listening on
+ * a UNIX domain socket.
+ *
+ * On success, this configures gWrapSim.simulatorFd and returns 0.
+ */
+static int openSimConnection(const char* name)
+{
+    int result = -1;
+    char* fileName = NULL;
+    int sock = -1;
+    int cc;
+
+    assert(gWrapSim.simulatorFd == -1);
+
+    fileName = makeFilename(name);
+
+    struct sockaddr_un addr;
+    
+    sock = socket(AF_UNIX, SOCK_STREAM, 0);
+    if (sock < 0) {
+        wsLog("UNIX domain socket create failed (errno=%d)\n", errno);
+        goto bail;
+    }
+
+    /* connect to socket; fails if file doesn't exist */
+    strcpy(addr.sun_path, fileName);    // max 108 bytes
+    addr.sun_family = AF_UNIX;
+    cc = connect(sock, (struct sockaddr*) &addr, SUN_LEN(&addr));
+    if (cc < 0) {
+        // ENOENT means socket file doesn't exist
+        // ECONNREFUSED means socket exists but nobody is listening
+        wsLog("AF_UNIX connect failed for '%s': %s\n",
+            fileName, strerror(errno));
+        goto bail;
+    }
+
+    gWrapSim.simulatorFd = sock;
+    sock = -1;
+
+    result = 0;
+    wsLog("+++ connected to '%s'\n", fileName);
+
+bail:
+    if (sock >= 0)
+        _ws_close(sock);
+    free(fileName);
+    return result;
+}
+
+/*
+ * Prepare communication with the front end.  We wait for a "hello" from
+ * the other side, and respond in kind.
+ */
+static int prepSimConnection(void)
+{
+    /* NOTE: this is endian-specific; we're x86 Linux only, so no problem */
+    static const unsigned int hello = kHelloMsg;
+    static const unsigned int helloAck = kHelloAckMsg;
+    Message msg;
+
+    if (Message_read(&msg, gWrapSim.simulatorFd) != 0) {
+        wsLog("hello read failed\n");
+        return -1;
+    }
+
+    if (memcmp(msg.mData, &hello, 4) != 0) {
+        wsLog("Got bad hello from peer\n");
+        return -1;
+    }
+
+    Message_release(&msg);
+
+    msg.mType = kTypeRaw;
+    msg.mData = (unsigned char*) &helloAck;
+    msg.mLength = 4;
+
+    if (Message_write(&msg, gWrapSim.simulatorFd) != 0) {
+        wsLog("hello ack write failed\n");
+        return -1;
+    }
+
+    return 0;
+}
+
+/*
+ * Get the sim front-end configuration.  We loop here until the sim claims
+ * to be done with us.
+ */
+static int getSimConfig(void)
+{
+    Message msg;
+    int joinNewGroup, grabTerminal, done;
+    int result = -1;
+
+    joinNewGroup = grabTerminal = done = 0;
+    Message_clear(&msg);        // clear out msg->mData
+
+    wsLog("+++ awaiting hardware configuration\n");
+    while (!done) {
+        if (Message_read(&msg, gWrapSim.simulatorFd) != 0) {
+            wsLog("failed receiving config from parent\n");
+            goto bail;
+        }
+
+        if (msg.mType == kTypeCommand) {
+            int cmd, arg;
+
+            if (getCommand(&msg, &cmd, &arg) != 0)
+                goto bail;
+
+            switch (cmd) {
+            case kCommandGoAway:
+                wsLog("Simulator front-end is busy\n");
+                goto bail;
+            case kCommandNewPGroup:
+                joinNewGroup = 1;
+                grabTerminal = (arg != 0);
+                wsLog("Simulator wants us to be in a new pgrp (term=%d)\n",
+                    grabTerminal);
+                break;
+            case kCommandConfigDone:
+                done = 1;
+                break;
+            default:
+                wsLog("Got unexpected command %d/%d\n", cmd, arg);
+                break;
+            }
+        } else if (msg.mType == kTypeRaw) {
+            /* assumes 32-bit alignment and identical byte ordering */
+            int* pData = (int*) msg.mData;
+            if (msg.mLength >= 4 && *pData == kDisplayConfigMagic) {
+                if (handleDisplayConfig(pData, msg.mLength) != 0)
+                    goto bail;
+            }
+        } else if (msg.mType == kTypeConfig) {
+            const char *name;
+            const char *val;
+            getConfig(&msg, &name, &val);
+            if(strcmp(name, "keycharmap") == 0) {
+                free((void*)gWrapSim.keyMap);
+                gWrapSim.keyMap = strdup(val);
+            }
+        } else {
+            wsLog("Unexpected msg type %d during startup\n", msg.mType);
+            goto bail;
+        }
+
+        /* clear out the data field if necessary */
+        Message_release(&msg);
+    }
+
+    wsLog("Configuration received from simulator\n");
+
+    if (joinNewGroup) {
+        /* set pgid to pid */
+        pid_t pgid = getpid();
+        setpgid(0, pgid);
+
+        /*
+         * Put our pgrp in the foreground.
+         * tcsetpgrp() from background process causes us to get a SIGTTOU,
+         * which is mostly harmless but makes tcsetpgrp() fail with EINTR.
+         */
+        signal(SIGTTOU, SIG_IGN);
+        if (grabTerminal) {
+            if (tcsetpgrp(fileno(stdin), getpgrp()) != 0) {
+                wsLog("tcsetpgrp(%d, %d) failed (errno=%d)\n",
+                    fileno(stdin), getpgrp(), errno);
+            }
+            wsLog("Set pgrp %d as foreground\n", (int) getpgrp());
+        }
+    
+        /* tell the sim where we're at */
+        Message msg;
+        setCommand(&msg, kCommandNewPGroupCreated, pgid);
+        Message_write(&msg, gWrapSim.simulatorFd);
+        Message_release(&msg);
+    }
+
+    result = 0;
+
+bail:
+    /* make sure the data was freed */
+    Message_release(&msg);
+    //wsLog("bailing, result=%d\n", result);
+    return result;
+}
+
+/*
+ * Connect to the simulator and exchange pleasantries.
+ *
+ * Returns 0 on success.
+ */
+static int connectToSim(void)
+{
+    if (openSimConnection(kAndroidPipeName) != 0)
+        return -1;
+
+    if (prepSimConnection() != 0)
+        return -1;
+
+    if (getSimConfig() != 0)
+        return -1;
+
+    wsLog("+++ sim is ready to go\n");
+
+    return 0;
+}
+
+/*
+ * Listen to the sim forever or until the front end shuts down, whichever
+ * comes first.
+ *
+ * All we're really getting here are key events.
+ */
+static void listenToSim(void)
+{
+    wsLog("--- listening for input events from front end\n");
+
+    while (1) {
+        Message msg;
+
+        Message_clear(&msg);
+        if (Message_read(&msg, gWrapSim.simulatorFd) != 0) {
+            wsLog("--- sim message read failed\n");
+            return;
+        }
+
+        if (msg.mType == kTypeCommand) {
+            int cmd, arg;
+
+            if (getCommand(&msg, &cmd, &arg) != 0) {
+                wsLog("bad command from sim?\n");
+                continue;
+            }
+
+            switch (cmd) {
+            case kCommandQuit:
+                wsLog("--- sim sent us a QUIT message\n");
+                return;
+            case kCommandKeyDown:
+                wsLog("KEY DOWN: %d\n", arg);
+                wsSendSimKeyEvent(arg, 1);
+                break;
+            case kCommandKeyUp:
+                wsLog("KEY UP: %d\n", arg);
+                wsSendSimKeyEvent(arg, 0);
+                break;
+            default:
+                wsLog("--- sim sent unrecognized command %d\n", cmd);
+                break;
+            }
+
+            Message_release(&msg);
+        } else if (msg.mType == kTypeCommandExt) {
+            int cmd, arg0, arg1, arg2;
+
+            if (getCommandExt(&msg, &cmd, &arg0, &arg1, &arg2) != 0) {
+                wsLog("bad ext-command from sim?\n");
+                continue;
+            }
+
+            switch (cmd) {
+            case kCommandTouch:
+                wsSendSimTouchEvent(arg0, arg1, arg2);
+                break;
+            }
+
+            Message_release(&msg);
+        } else {
+            wsLog("--- sim sent non-command message, type=%d\n", msg.mType);
+        }
+    }
+
+    assert(0);      // not reached
+}
+
+
+/*
+ * Tell the simulator front-end that the display has been updated.
+ */
+void wsPostDisplayUpdate(int displayIdx)
+{
+    if (gWrapSim.simulatorFd < 0) {
+        wsLog("Not posting display update -- sim not ready\n");
+        return;
+    }
+
+    Message msg;
+
+    setCommand(&msg, kCommandUpdateDisplay, displayIdx);
+    Message_write(&msg, gWrapSim.simulatorFd);
+    Message_release(&msg);
+}
+
+/*
+ * Send a log message to the front-end.
+ */
+void wsPostLogMessage(int logPrio, const char* tag, const char* message)
+{
+    if (gWrapSim.simulatorFd < 0) {
+        wsLog("Not posting log message -- sim not ready\n");
+        return;
+    }
+
+    time_t when = time(NULL);
+    int pid = (int) getpid();
+    int tagLen, messageLen, totalLen;
+
+    tagLen = strlen(tag) +1;
+    messageLen = strlen(message) +1;
+    totalLen = sizeof(int) * 3 + tagLen + messageLen;
+    unsigned char outBuf[totalLen];
+    unsigned char* cp = outBuf;
+
+    /* See Message::set/getLogBundle() in simulator/MessageStream.cpp. */
+    memcpy(cp, &when, sizeof(int));
+    cp += sizeof(int);
+    memcpy(cp, &logPrio, sizeof(int));
+    cp += sizeof(int);
+    memcpy(cp, &pid, sizeof(int));
+    cp += sizeof(int);
+    memcpy(cp, tag, tagLen);
+    cp += tagLen;
+    memcpy(cp, message, messageLen);
+    cp += messageLen;
+
+    assert(cp - outBuf == totalLen);
+
+    Message msg;
+    msg.mType = kTypeLogBundle;
+    msg.mData = outBuf;
+    msg.mLength = totalLen;
+    Message_write(&msg, gWrapSim.simulatorFd);
+
+    msg.mData = NULL;       // don't free
+    Message_release(&msg);
+}
+
+/*
+ * Turn the vibrating notification device on or off.
+ */
+void wsEnableVibration(int vibrateOn)
+{
+    if (gWrapSim.simulatorFd < 0) {
+        wsLog("Not posting vibrator update -- sim not ready\n");
+        return;
+    }
+
+    Message msg;
+
+    //wsLog("+++ sending vibrate:%d\n", vibrateOn);
+
+    setCommand(&msg, kCommandVibrate, vibrateOn);
+    Message_write(&msg, gWrapSim.simulatorFd);
+    Message_release(&msg);
+}
+