Example #1
0
  /* NOTE: on the slave, this must be called on the EDT. */
  public void writeEvent(KeyEvent event) throws IOException {
    char keyChar;
    int keyCode;
    int keySym;
    int n = 0;

    /* First, try to get the keysym from the keychar */
    keyChar = event.getKeyChar();
    keyCode = event.getKeyCode();
    keySym = UnicodeToKeysym.getKeysym(keyChar);

    /* Next, try to get it from the keycode */
    if (keySym == -1) {
      keySym = KeycodeToKeysym.getKeysym(keyCode);
    }

    /*
     ** If we still don't have it and the keychar is less than 0x20
     ** the control key is pressed and has already been sent. So
     ** just send the key and not its controlled version.
     */
    if (keySym == -1) {
      if (keyChar < 0x20) {
        keySym = keyChar + 0x60;
      }
    }

    if (keySym == -1) {
      AppXrw.logger.warning("Could not find keysym for key event = " + event);
      AppXrw.logger.warning("Key event not sent to remote window server");
      return;
    }

    // AppXrw.logger.info("Send keySym = " + keySym);

    keyEventBuf[n++] = (byte) Proto.ClientMessageType.EVENT_KEY.ordinal();
    keyEventBuf[n++] = (byte) ((event.getID() == KeyEvent.KEY_PRESSED) ? 1 : 0);
    keyEventBuf[n++] = (byte) 0;
    keyEventBuf[n++] = (byte) 0;
    keyEventBuf[n++] = (byte) ((keySym >> 24) & 0xff);
    keyEventBuf[n++] = (byte) ((keySym >> 16) & 0xff);
    keyEventBuf[n++] = (byte) ((keySym >> 8) & 0xff);
    keyEventBuf[n++] = (byte) (keySym & 0xff);
    keyEventBuf[n++] = (byte) ((clientId >> 24) & 0xff);
    keyEventBuf[n++] = (byte) ((clientId >> 16) & 0xff);
    keyEventBuf[n++] = (byte) ((clientId >> 8) & 0xff);
    keyEventBuf[n++] = (byte) (clientId & 0xff);

    slaveSocket.send(sign(keyEventBuf));
  }
Example #2
0
/**
 * An implementation of a ServerProxy which lives in the slave clients and receives messages from an
 * Xremwin master.
 *
 * @author deronj
 */
@ExperimentalAPI
class ServerProxySlave implements ServerProxy {

  interface DisconnectListener {
    public void disconnected();
  }

  private static final boolean debugIO = true;
  private static final int BUTTON4_MASK = 0x08;
  private static final int BUTTON5_MASK = 0x10;
  /** This app's Wonderland session. */
  protected WonderlandSession session;

  private SlaveClientSocket slaveSocket;
  private int clientId;
  private ClientXrwSlave client;
  private AppXrwConnectionInfo connectionInfo;
  private DisconnectListener disconnectListener;
  private int scanLineWidth;
  private byte[] scanLineBuf;

  // security
  private Mac mac;
  private int counter = (int) (Math.random() * Integer.MAX_VALUE);

  // List of byte arrays which have come from the remote window server
  private DataBufferQueue bufQueue = new DataBufferQueue();
  /** Which user is currently controlling the app */
  private String controllingUserName = null;

  private byte[] keyEventBuf =
      new byte[Proto.ClientMessageType.EVENT_KEY.size() + Proto.SIGNATURE_SIZE];
  private byte[] pointerEventBuf =
      new byte[Proto.ClientMessageType.EVENT_POINTER.size() + Proto.SIGNATURE_SIZE];
  private byte[] takeControlBuf =
      new byte[Proto.ClientMessageType.TAKE_CONTROL.size() + Proto.SIGNATURE_SIZE];
  private byte[] releaseControlBuf =
      new byte[Proto.ClientMessageType.RELEASE_CONTROL.size() + Proto.SIGNATURE_SIZE];
  private byte[] setUserDisplBuf =
      new byte[Proto.ClientMessageType.WINDOW_SET_USER_DISPLACEMENT.size() + Proto.SIGNATURE_SIZE];
  private byte[] setSizeBuf =
      new byte[Proto.ClientMessageType.WINDOW_SET_SIZE.size() + Proto.SIGNATURE_SIZE];
  private byte[] setRotateYBuf =
      new byte[Proto.ClientMessageType.WINDOW_SET_ROTATE_Y.size() + Proto.SIGNATURE_SIZE];
  private byte[] toFrontBuf =
      new byte[Proto.ClientMessageType.WINDOW_TO_FRONT.size() + Proto.SIGNATURE_SIZE];
  private byte[] destroyWindowBuf =
      new byte[Proto.ClientMessageType.DESTROY_WINDOW.size() + Proto.SIGNATURE_SIZE];
  private byte[] slaveCloseWindowBuf =
      new byte[Proto.ClientMessageType.SLAVE_CLOSE_WINDOW.size() + Proto.SIGNATURE_SIZE];
  private int lastPointerX = 0;
  private int lastPointerY = 0;

  /** Used to store window parent associations until all windows have been created. */
  private HashMap<WindowXrw, Integer> windowParents = new HashMap<WindowXrw, Integer>();

  /**
   * Create a new instance of ServerProxySlave.
   *
   * @param client The slave client.
   * @param session This app's Wonderland session.
   * @param connectionInfo Subclass-specific data for making a peer-to-peer connection between
   *     master and slave.
   * @param disconnectListener The listener to call when the slave is disconnected.
   */
  public ServerProxySlave(
      ClientXrwSlave client,
      WonderlandSession session,
      AppXrwConnectionInfo connectionInfo,
      DisconnectListener disconnectListener) {
    this.client = client;
    this.session = session;
    this.connectionInfo = connectionInfo;
    this.disconnectListener = disconnectListener;

    try {
      this.mac = Mac.getInstance("HmacSHA1");
      mac.init(connectionInfo.getSecret());
    } catch (NoSuchAlgorithmException nsae) {
      throw new IllegalStateException(nsae);
    } catch (InvalidKeyException ike) {
      throw new IllegalStateException(ike);
    }
  }

  public void connect() throws IOException {
    establishConnection();
    initialHandshake();
  }

  public void disconnect() {
    if (slaveSocket != null) {
      slaveSocket.close(false);
    }
    if (disconnectListener != null) {
      disconnectListener.disconnected();
      disconnectListener = null;
    }
    AppXrw.logger.info("ServerProxySlave disconnected");
  }

  private void establishConnection() throws IOException {

    Socket socket = new Socket(connectionInfo.getHostName(), connectionInfo.getPortNum());

    slaveSocket = new SlaveClientSocket(session.getID(), socket, new MyListener());
    slaveSocket.initialize();
  }

  public void cleanup() {
    disconnect();
    client = null;
    scanLineBuf = null;
    if (bufQueue != null) {
      bufQueue.close();
      bufQueue = null;
    }
    AppXrw.logger.info("ServerProxySlave cleaned up");
  }

  // Debug: Error Insertion: for testing fix for 761.
  // private int msgCounter = 0;

  private class MyListener implements ClientSocketListener {

    private boolean welcomeReceived = false;

    public void receivedMessage(BigInteger otherClientID, byte[] message) {

      // Ignore all messages until the welcome message is received
      if (welcomeReceived) {
        bufQueue.enqueue(message);
      } else {
        if (message[0] == ServerMessageType.WELCOME.ordinal()) {
          bufQueue.enqueue(message);
          welcomeReceived = true;
        }
      }

      /* Debug: Error Insertion: for testing fix for 761.
      if (++msgCounter == 5) {
          bufQueue.close();
      }
      */
    }

    public void otherClientHasLeft(BigInteger otherClientID) {
      cleanup();
      AppXrw.logger.info("Master has disconnected: " + otherClientID);
    }
  }

  private void initialHandshake() throws EOFException {

    String userName = session.getUserID().getUsername();
    int strLen = userName.length();

    // Inform the server  that we have connected by sending a hello message
    // with the name of this user
    byte[] helloBuf =
        new byte[Proto.ClientMessageType.HELLO.size() + strLen + Proto.SIGNATURE_SIZE];
    helloBuf[0] = (byte) Proto.ClientMessageType.HELLO.ordinal();
    helloBuf[1] = (byte) 0; // pad
    helloBuf[2] = (byte) ((strLen >> 8) & 0xff);
    helloBuf[3] = (byte) (strLen & 0xff);
    System.arraycopy(userName.getBytes(), 0, helloBuf, 4, strLen);
    try {
      slaveSocket.send(sign(helloBuf));
    } catch (IOException ex) {
      throw new RuntimeException(ex);
    }

    AppXrw.logger.info("Broadcast slave Hello message for user " + userName);

    // Get the welcome message from the server. This contains the client id
    // Note: because the master hub broadcasts to all slaves, there is a chance
    // there might be irrelevant messages queued up for this
    // slave. These should be ignored. This come from the fact that it takes
    // some time after the slave joins the connection for the master to become
    // aware of it and to compose and send the welcome message. During this time
    // if there are any incoming messages from the X server they will be
    // forwarded to this slave even if it has not yet been officially welcomed.
    // Since the content of these messages will be reflected in the welcome message
    // and the slave can't really do anything until it is welcomed we need
    // to ignore these messages.

    ServerMessageType type = null;
    do {
      type = getMessageType();
    } while (type != ServerMessageType.WELCOME);
    // TODO: eventually we should add a timeout

    // Skip 3 bytes of pad
    bufQueue.nextByte();
    bufQueue.nextShort();

    clientId = bufQueue.nextInt();
    client.setClientId(clientId);

    windowParents.clear();

    // Read the initial window state synchronization
    // part of the welcome message
    int numWins = bufQueue.nextInt();
    AppXrw.logger.info("numWins = " + numWins);
    for (int i = 0; i < numWins; i++) {
      syncWindowStateNext();
    }

    // All windows have been defined. Now assign the parents
    for (WindowXrw win : windowParents.keySet()) {
      Integer parentWid = windowParents.get(win);
      if (parentWid != null) {
        WindowXrw parentWin = client.lookupWindow(parentWid.intValue());
        win.setParent(parentWin);
      }
    }
    windowParents.clear();

    client.updateSlaveWindows();
  }

  private void syncWindowStateNext() throws EOFException {
    AppXrw.logger.info("Enter syncWindowStateNext");

    CreateWindowMsgArgs crtMsgArgs = new CreateWindowMsgArgs();
    WindowXrw win;
    int controllingUserLen;
    int desiredZOrder;
    float rotY; // Currently ignored
    Vector3f userTranslation = new Vector3f();

    crtMsgArgs.wid = bufQueue.nextInt();
    crtMsgArgs.x = (short) bufQueue.nextInt();
    crtMsgArgs.y = (short) bufQueue.nextInt();
    crtMsgArgs.wAndBorder = bufQueue.nextInt();
    crtMsgArgs.hAndBorder = bufQueue.nextInt();
    crtMsgArgs.borderWidth = bufQueue.nextInt();
    controllingUserLen = bufQueue.nextInt();
    desiredZOrder = bufQueue.nextInt();
    rotY = bufQueue.nextFloat(); // Just skipped
    userTranslation.x = bufQueue.nextFloat();
    userTranslation.y = bufQueue.nextFloat();
    userTranslation.z = bufQueue.nextFloat();
    AppXrw.logger.info("userTranslation = " + userTranslation);
    /* TODO: 0.4 protocol:
    int transientFor = bufQueue.nextInt();
    AppXrw.logger.info("transientFor = " + transientFor);
     */
    // TODO: 0.4 protocol: skip isTransient
    int transientFor = bufQueue.nextInt();
    int typeOrdinal = bufQueue.nextInt();
    Window2D.Type type = Window2D.Type.values()[typeOrdinal];
    AppXrw.logger.info("type = " + type);
    int parentWid = bufQueue.nextInt();
    AppXrw.logger.info("parentWid = " + parentWid);

    crtMsgArgs.decorated = (bufQueue.nextByte() == 1) ? true : false;
    AppXrw.logger.info("client = " + client);
    AppXrw.logger.info("crtMsgArgs = " + crtMsgArgs);
    AppXrw.logger.info("desiredZOrder= " + desiredZOrder);

    // Make sure window is ready to receive data on creation
    win = client.createWindow(crtMsgArgs);
    if (win == null) {
      AppXrw.logger.warning("Cannot create slave window for " + crtMsgArgs.wid);
      return;
    }

    if (win.getType() != type) {
      win.setType(type);
    }

    // Defer parent assignment until all windows are created
    if (parentWid != WindowXrw.INVALID_WID) {
      windowParents.put(win, parentWid);
    }

    win.setDesiredZOrder(desiredZOrder);

    CellTransform userTransformCell = new CellTransform(null, null);
    userTransformCell.setTranslation(userTranslation);
    win.setUserTransformCellLocal(userTransformCell);

    boolean show = (bufQueue.nextByte() == 1) ? true : false;
    AppXrw.logger.info("show = " + show);

    if (controllingUserLen > 0) {
      byte[] controllingUserBuf = bufQueue.nextBuffer();
      String controllingUser = new String(controllingUserBuf);
      AppXrw.logger.info("controlling user = "******"msgCode = " + msgCode);
      return Proto.ServerMessageType.values()[msgCode];
    } catch (EOFException ex) {
      return Proto.ServerMessageType.SERVER_DISCONNECT;
    }
  }

  public void getData(CreateWindowMsgArgs msgArgs) throws EOFException {
    msgArgs.decorated = bufQueue.nextByte() != 0;
    bufQueue.nextShort(); // Skip 2 bytes of pad
    msgArgs.wid = bufQueue.nextInt();
    msgArgs.x = bufQueue.nextShort();
    msgArgs.y = bufQueue.nextShort();
    msgArgs.wAndBorder = bufQueue.nextInt();
    msgArgs.hAndBorder = bufQueue.nextInt();
  }

  public void getData(DestroyWindowMsgArgs msgArgs) throws EOFException {
    bufQueue.skipBytes(3); // Skip 3 bytes of pad
    msgArgs.wid = bufQueue.nextInt();
  }

  public void getData(ShowWindowMsgArgs msgArgs) throws EOFException {
    msgArgs.show = (bufQueue.nextByte() != 0);

    // TODO: 0.4 protocol: skip 2 bytes of transient and pad (ignore transient for now)
    // TODO: 0.5 protocol: skip 2 bytes of pad
    bufQueue.nextShort();

    msgArgs.wid = bufQueue.nextInt();

    // TODO: 0.5 protocol: not yet
    // msgArgs.transientFor = bufQueue.nextInt();
  }

  public void getData(ConfigureWindowMsgArgs msgArgs) throws EOFException {
    bufQueue.skipBytes(3); // Skip 3 bytes of pad
    msgArgs.clientId = bufQueue.nextInt();
    msgArgs.wid = bufQueue.nextInt();
    msgArgs.x = bufQueue.nextShort();
    msgArgs.y = bufQueue.nextShort();
    msgArgs.wAndBorder = bufQueue.nextInt();
    msgArgs.hAndBorder = bufQueue.nextInt();
    msgArgs.sibid = bufQueue.nextInt();
  }

  public void getData(PositionWindowMsgArgs msgArgs) throws EOFException {
    bufQueue.skipBytes(3); // Skip 3 bytes of pad
    msgArgs.clientId = bufQueue.nextInt();
    msgArgs.wid = bufQueue.nextInt();
    msgArgs.x = bufQueue.nextShort();
    msgArgs.y = bufQueue.nextShort();
  }

  public void getData(RestackWindowMsgArgs msgArgs) throws EOFException {
    bufQueue.skipBytes(3); // Skip 3 bytes of pad
    msgArgs.clientId = bufQueue.nextInt();
    msgArgs.wid = bufQueue.nextInt();
    msgArgs.sibid = bufQueue.nextInt();
  }

  public void getData(WindowSetDecoratedMsgArgs msgArgs) throws EOFException {
    msgArgs.decorated = (bufQueue.nextByte() == 1) ? true : false;
    bufQueue.nextShort(); // Skip 2 bytes of pad
    msgArgs.wid = bufQueue.nextInt();
  }

  public void getData(WindowSetBorderWidthMsgArgs msgArgs) throws EOFException {
    bufQueue.nextByte(); // Skip 1 byte of pad
    msgArgs.borderWidth = bufQueue.nextShort();
    msgArgs.wid = bufQueue.nextInt();
  }

  public void getData(WindowSetUserDisplMsgArgs msgArgs) throws EOFException {
    bufQueue.skipBytes(3); // Skip 3 bytes of pad
    msgArgs.clientId = bufQueue.nextInt();
    msgArgs.wid = bufQueue.nextInt();
    int ix = bufQueue.nextInt();
    int iy = bufQueue.nextInt();
    int iz = bufQueue.nextInt();
    msgArgs.userDispl =
        new Vector3f(Float.intBitsToFloat(ix), Float.intBitsToFloat(iy), Float.intBitsToFloat(iz));
  }

  public void getData(WindowSetRotateYMsgArgs msgArgs) throws EOFException {
    bufQueue.skipBytes(3); // Skip 3 bytes of pad
    msgArgs.clientId = bufQueue.nextInt();
    msgArgs.wid = bufQueue.nextInt();
    int iroty = bufQueue.nextInt();
    msgArgs.roty = Float.intBitsToFloat(iroty);
  }

  public void getData(DisplayPixelsMsgArgs msgArgs) throws EOFException {
    int encodingCode = bufQueue.nextByte();
    msgArgs.encoding = Proto.PixelEncoding.values()[encodingCode];

    msgArgs.x = bufQueue.nextShort();
    msgArgs.wid = bufQueue.nextInt();
    msgArgs.y = bufQueue.nextShort();
    msgArgs.w = bufQueue.nextShort();
    msgArgs.h = bufQueue.nextShort();

    // Skip 2 bytes of pad
    bufQueue.nextShort();
  }

  public void getData(CopyAreaMsgArgs msgArgs) throws EOFException {
    // Skip 3 bytes of pad
    bufQueue.nextByte();
    bufQueue.nextShort();

    msgArgs.wid = bufQueue.nextInt();
    msgArgs.srcX = bufQueue.nextInt();
    msgArgs.srcY = bufQueue.nextInt();
    msgArgs.width = bufQueue.nextInt();
    msgArgs.height = bufQueue.nextInt();
    msgArgs.dstX = bufQueue.nextInt();
    msgArgs.dstY = bufQueue.nextInt();
  }

  public void getData(ControllerStatusMsgArgs msgArgs) throws EOFException {
    int status = (int) bufQueue.nextByte();
    msgArgs.status = Proto.ControllerStatus.values()[status];

    // Skip 2 bytes of pad
    bufQueue.nextShort();

    msgArgs.clientId = bufQueue.nextInt();
  }

  public void getData(SetWindowTitleMsgArgs msgArgs) throws EOFException {
    // Skip 3 byte of pad
    bufQueue.nextByte();
    bufQueue.nextShort();

    msgArgs.wid = bufQueue.nextInt();
    int strLen = bufQueue.nextInt();

    byte[] bytes = new byte[strLen];
    for (int i = 0; i < strLen; i++) {
      bytes[i] = bufQueue.nextByte();
    }
    msgArgs.title = new String(bytes);
  }

  public void getData(SlaveCloseWindowMsgArgs msgArgs) throws EOFException {
    bufQueue.skipBytes(3); // Skip 3 bytes of pad
    msgArgs.clientId = bufQueue.nextInt();
    msgArgs.wid = bufQueue.nextInt();
  }

  public void getData() {}

  public void getData(UserNameMsgArgs msgArgs) throws EOFException {

    // Skip 1 byte of pad
    bufQueue.nextByte();

    int strLen = bufQueue.nextShort();
    if (strLen > 0) {
      byte[] bytes = new byte[strLen];
      for (int i = 0; i < strLen; i++) {
        bytes[i] = bufQueue.nextByte();
      }
      msgArgs.userName = new String(bytes);
    } else {
      msgArgs.userName = null;
    }

    controllingUserName = msgArgs.userName;
  }

  // Returns null if no user has control
  public String getControllingUser() {
    return controllingUserName;
  }

  public void getData(SetPopupParentMsgArgs msgArgs) throws EOFException {

    // Skip 3 bytes of pad
    bufQueue.nextByte();
    bufQueue.nextShort();

    msgArgs.wid = bufQueue.nextInt();
    msgArgs.parentWid = bufQueue.nextInt();
  }

  // TODO: 0.4 protocol
  public void getData(DisplayCursorMsgArgs msg) throws EOFException {
    // Skip 3 bytes of pad
    bufQueue.nextByte();
    bufQueue.nextShort();

    msg.width = bufQueue.nextShort();
    msg.height = bufQueue.nextShort();
    msg.xhot = bufQueue.nextShort();
    msg.yhot = bufQueue.nextShort();

    int numPixels = msg.width * msg.height;
    msg.pixels = new int[numPixels];
    for (int i : msg.pixels) {
      msg.pixels[i] = bufQueue.nextInt();
    }
  }

  // TODO: 0.4 protocol
  public void getData(MoveCursorMsgArgs msg) throws EOFException {
    // Skip 3 bytes of pad
    bufQueue.nextByte();
    bufQueue.nextShort();

    msg.wid = bufQueue.nextInt();
    msg.x = bufQueue.nextInt();
    msg.y = bufQueue.nextInt();
  }

  // TODO: 0.4 protocol
  public void getData(ShowCursorMsgArgs msg) throws EOFException {
    msg.show = (bufQueue.nextByte() == 1) ? true : false;
  }

  public void setScanLineWidth(int width) {
    // No need to do anything
  }

  public byte[] readScanLine() throws EOFException {
    return bufQueue.nextBuffer();
  }

  public int readRleInt() throws EOFException {
    int value = bufQueue.nextInt();
    return value;
  }

  public void readRleChunk(byte[] buf) throws EOFException {
    bufQueue.nextBytes(buf);
  }

  public void readRleChunk(byte[] buf, int len) throws EOFException {
    bufQueue.nextBytes(buf, len);
  }

  /* NOTE: on the slave, this must be called on the EDT. */
  public void writeEvent(int wid, MouseEvent event) throws IOException {
    int mask = 0;
    int n = 0;

    int modifiers = event.getModifiersEx();
    if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
      mask |= 0x01;
    }
    if ((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) {
      mask |= 0x02;
    }
    if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
      mask |= 0x04;
    }

    int x = event.getX();
    int y = event.getY();
    lastPointerX = x;
    lastPointerY = y;

    // AppXrw.logger.info("Send ptr: xy = " + x + ", " + y +
    //		   ", mask = 0x" + Integer.toHexString(mask));

    pointerEventBuf[n++] = (byte) Proto.ClientMessageType.EVENT_POINTER.ordinal();
    pointerEventBuf[n++] = (byte) mask;
    pointerEventBuf[n++] = (byte) ((x >> 8) & 0xff);
    pointerEventBuf[n++] = (byte) (x & 0xff);
    pointerEventBuf[n++] = (byte) ((y >> 8) & 0xff);
    pointerEventBuf[n++] = (byte) (y & 0xff);
    pointerEventBuf[n++] = (byte) 0;
    pointerEventBuf[n++] = (byte) 0;
    pointerEventBuf[n++] = (byte) ((wid >> 24) & 0xff);
    pointerEventBuf[n++] = (byte) ((wid >> 16) & 0xff);
    pointerEventBuf[n++] = (byte) ((wid >> 8) & 0xff);
    pointerEventBuf[n++] = (byte) (wid & 0xff);
    pointerEventBuf[n++] = (byte) ((clientId >> 24) & 0xff);
    pointerEventBuf[n++] = (byte) ((clientId >> 16) & 0xff);
    pointerEventBuf[n++] = (byte) ((clientId >> 8) & 0xff);
    pointerEventBuf[n++] = (byte) (clientId & 0xff);

    slaveSocket.send(sign(pointerEventBuf));
  }

  /* NOTE: on the slave, this must be called on the EDT. */
  public void writeWheelEvent(int wid, MouseWheelEvent event) throws IOException {
    int wheelRotation = event.getWheelRotation();
    int mask = (wheelRotation == -1) ? BUTTON4_MASK : BUTTON5_MASK;
    int n;

    // AppXrw.logger.info("send MouseWheelEvent = " + event);

    /* First send button pressevent */
    n = 0;
    pointerEventBuf[n++] = (byte) Proto.ClientMessageType.EVENT_POINTER.ordinal();
    pointerEventBuf[n++] = (byte) mask;
    pointerEventBuf[n++] = (byte) ((lastPointerX >> 8) & 0xff);
    pointerEventBuf[n++] = (byte) (lastPointerX & 0xff);
    pointerEventBuf[n++] = (byte) ((lastPointerY >> 8) & 0xff);
    pointerEventBuf[n++] = (byte) (lastPointerY & 0xff);
    pointerEventBuf[n++] = (byte) 0;
    pointerEventBuf[n++] = (byte) 0;
    pointerEventBuf[n++] = (byte) ((wid >> 24) & 0xff);
    pointerEventBuf[n++] = (byte) ((wid >> 16) & 0xff);
    pointerEventBuf[n++] = (byte) ((wid >> 8) & 0xff);
    pointerEventBuf[n++] = (byte) (wid & 0xff);
    pointerEventBuf[n++] = (byte) ((clientId >> 24) & 0xff);
    pointerEventBuf[n++] = (byte) ((clientId >> 16) & 0xff);
    pointerEventBuf[n++] = (byte) ((clientId >> 8) & 0xff);
    pointerEventBuf[n++] = (byte) (clientId & 0xff);
    slaveSocket.send(sign(pointerEventBuf));

    /* Then send button release event */
    mask = 0;
    n = 0;
    pointerEventBuf[n++] = (byte) Proto.ClientMessageType.EVENT_POINTER.ordinal();
    pointerEventBuf[n++] = (byte) mask;
    pointerEventBuf[n++] = (byte) ((lastPointerX >> 8) & 0xff);
    pointerEventBuf[n++] = (byte) (lastPointerX & 0xff);
    pointerEventBuf[n++] = (byte) ((lastPointerY >> 8) & 0xff);
    pointerEventBuf[n++] = (byte) (lastPointerY & 0xff);
    pointerEventBuf[n++] = (byte) 0;
    pointerEventBuf[n++] = (byte) 0;
    pointerEventBuf[n++] = (byte) ((wid >> 24) & 0xff);
    pointerEventBuf[n++] = (byte) ((wid >> 16) & 0xff);
    pointerEventBuf[n++] = (byte) ((wid >> 8) & 0xff);
    pointerEventBuf[n++] = (byte) (wid & 0xff);
    pointerEventBuf[n++] = (byte) ((clientId >> 24) & 0xff);
    pointerEventBuf[n++] = (byte) ((clientId >> 16) & 0xff);
    pointerEventBuf[n++] = (byte) ((clientId >> 8) & 0xff);
    pointerEventBuf[n++] = (byte) (clientId & 0xff);
    slaveSocket.send(sign(pointerEventBuf));
  }

  /* NOTE: on the slave, this must be called on the EDT. */
  public void writeEvent(KeyEvent event) throws IOException {
    char keyChar;
    int keyCode;
    int keySym;
    int n = 0;

    /* First, try to get the keysym from the keychar */
    keyChar = event.getKeyChar();
    keyCode = event.getKeyCode();
    keySym = UnicodeToKeysym.getKeysym(keyChar);

    /* Next, try to get it from the keycode */
    if (keySym == -1) {
      keySym = KeycodeToKeysym.getKeysym(keyCode);
    }

    /*
     ** If we still don't have it and the keychar is less than 0x20
     ** the control key is pressed and has already been sent. So
     ** just send the key and not its controlled version.
     */
    if (keySym == -1) {
      if (keyChar < 0x20) {
        keySym = keyChar + 0x60;
      }
    }

    if (keySym == -1) {
      AppXrw.logger.warning("Could not find keysym for key event = " + event);
      AppXrw.logger.warning("Key event not sent to remote window server");
      return;
    }

    // AppXrw.logger.info("Send keySym = " + keySym);

    keyEventBuf[n++] = (byte) Proto.ClientMessageType.EVENT_KEY.ordinal();
    keyEventBuf[n++] = (byte) ((event.getID() == KeyEvent.KEY_PRESSED) ? 1 : 0);
    keyEventBuf[n++] = (byte) 0;
    keyEventBuf[n++] = (byte) 0;
    keyEventBuf[n++] = (byte) ((keySym >> 24) & 0xff);
    keyEventBuf[n++] = (byte) ((keySym >> 16) & 0xff);
    keyEventBuf[n++] = (byte) ((keySym >> 8) & 0xff);
    keyEventBuf[n++] = (byte) (keySym & 0xff);
    keyEventBuf[n++] = (byte) ((clientId >> 24) & 0xff);
    keyEventBuf[n++] = (byte) ((clientId >> 16) & 0xff);
    keyEventBuf[n++] = (byte) ((clientId >> 8) & 0xff);
    keyEventBuf[n++] = (byte) (clientId & 0xff);

    slaveSocket.send(sign(keyEventBuf));
  }

  public void writeTakeControl(boolean steal) throws IOException {
    int n = 0;

    AppXrw.logger.info("ServerProxySlave: clientId = " + clientId);
    takeControlBuf[n++] = (byte) Proto.ClientMessageType.TAKE_CONTROL.ordinal();
    takeControlBuf[n++] = (byte) (steal ? 1 : 0);
    takeControlBuf[n++] = (byte) 0;
    takeControlBuf[n++] = (byte) 0;
    takeControlBuf[n++] = (byte) ((clientId >> 24) & 0xff);
    takeControlBuf[n++] = (byte) ((clientId >> 16) & 0xff);
    takeControlBuf[n++] = (byte) ((clientId >> 8) & 0xff);
    takeControlBuf[n++] = (byte) (clientId & 0xff);
    slaveSocket.send(sign(takeControlBuf));
  }

  public void writeReleaseControl() throws IOException {
    int n = 0;

    AppXrw.logger.info("ServerProxySlave: clientId = " + clientId);
    releaseControlBuf[n++] = (byte) Proto.ClientMessageType.RELEASE_CONTROL.ordinal();
    releaseControlBuf[n++] = (byte) 0;
    releaseControlBuf[n++] = (byte) 0;
    releaseControlBuf[n++] = (byte) 0;
    releaseControlBuf[n++] = (byte) ((clientId >> 24) & 0xff);
    releaseControlBuf[n++] = (byte) ((clientId >> 16) & 0xff);
    releaseControlBuf[n++] = (byte) ((clientId >> 8) & 0xff);
    releaseControlBuf[n++] = (byte) (clientId & 0xff);
    slaveSocket.send(sign(releaseControlBuf));
  }

  public void writeSetWindowTitle(int wid, String title) throws IOException {
    int strLen = title.length();
    int n = 0;

    // allocate dynamically, since we don't know ahead of time how big
    // the string will be
    byte[] setWindowTitleBuf =
        new byte[Proto.ClientMessageType.SET_WINDOW_TITLE.size() + strLen + Proto.SIGNATURE_SIZE];

    /* First send header */
    setWindowTitleBuf[n++] = (byte) Proto.ClientMessageType.SET_WINDOW_TITLE.ordinal();
    setWindowTitleBuf[n++] = 0; // Pad
    setWindowTitleBuf[n++] = 0; // Pad
    setWindowTitleBuf[n++] = 0; // Pad
    setWindowTitleBuf[n++] = (byte) ((wid >> 24) & 0xff);
    setWindowTitleBuf[n++] = (byte) ((wid >> 16) & 0xff);
    setWindowTitleBuf[n++] = (byte) ((wid >> 8) & 0xff);
    setWindowTitleBuf[n++] = (byte) (wid & 0xff);
    setWindowTitleBuf[n++] = (byte) ((strLen >> 24) & 0xff);
    setWindowTitleBuf[n++] = (byte) ((strLen >> 16) & 0xff);
    setWindowTitleBuf[n++] = (byte) ((strLen >> 8) & 0xff);
    setWindowTitleBuf[n++] = (byte) (strLen & 0xff);

    // copy the string's bytes into the message buffer
    System.arraycopy(title.getBytes(), 0, setWindowTitleBuf, n, strLen);

    slaveSocket.send(sign(setWindowTitleBuf));
  }

  public void windowSetUserDisplacement(int cid, int wid, Vector3f userDispl) throws IOException {
    int n = 0;
    int ix = Float.floatToRawIntBits(userDispl.x);
    int iy = Float.floatToRawIntBits(userDispl.y);
    int iz = Float.floatToRawIntBits(userDispl.z);

    setUserDisplBuf[n++] = (byte) Proto.ClientMessageType.WINDOW_SET_USER_DISPLACEMENT.ordinal();
    setUserDisplBuf[n++] = 0; // Pad
    setUserDisplBuf[n++] = 0; // Pad
    setUserDisplBuf[n++] = 0; // Pad
    setUserDisplBuf[n++] = (byte) ((cid >> 24) & 0xff);
    setUserDisplBuf[n++] = (byte) ((cid >> 16) & 0xff);
    setUserDisplBuf[n++] = (byte) ((cid >> 8) & 0xff);
    setUserDisplBuf[n++] = (byte) (cid & 0xff);
    setUserDisplBuf[n++] = (byte) ((wid >> 24) & 0xff);
    setUserDisplBuf[n++] = (byte) ((wid >> 16) & 0xff);
    setUserDisplBuf[n++] = (byte) ((wid >> 8) & 0xff);
    setUserDisplBuf[n++] = (byte) (wid & 0xff);
    setUserDisplBuf[n++] = (byte) ((ix >> 24) & 0xff);
    setUserDisplBuf[n++] = (byte) ((ix >> 16) & 0xff);
    setUserDisplBuf[n++] = (byte) ((ix >> 8) & 0xff);
    setUserDisplBuf[n++] = (byte) (ix & 0xff);
    setUserDisplBuf[n++] = (byte) ((iy >> 24) & 0xff);
    setUserDisplBuf[n++] = (byte) ((iy >> 16) & 0xff);
    setUserDisplBuf[n++] = (byte) ((iy >> 8) & 0xff);
    setUserDisplBuf[n++] = (byte) (iy & 0xff);
    setUserDisplBuf[n++] = (byte) ((iz >> 24) & 0xff);
    setUserDisplBuf[n++] = (byte) ((iz >> 16) & 0xff);
    setUserDisplBuf[n++] = (byte) ((iz >> 8) & 0xff);
    setUserDisplBuf[n++] = (byte) (iz & 0xff);

    slaveSocket.send(sign(setUserDisplBuf));
  }

  public void windowSetSize(int cid, int wid, int w, int h) throws IOException {
    int n = 0;

    setSizeBuf[n++] = (byte) Proto.ClientMessageType.WINDOW_SET_SIZE.ordinal();
    setSizeBuf[n++] = 0; // Pad
    setSizeBuf[n++] = 0; // Pad
    setSizeBuf[n++] = 0; // Pad
    setSizeBuf[n++] = (byte) ((cid >> 24) & 0xff);
    setSizeBuf[n++] = (byte) ((cid >> 16) & 0xff);
    setSizeBuf[n++] = (byte) ((cid >> 8) & 0xff);
    setSizeBuf[n++] = (byte) (cid & 0xff);
    setSizeBuf[n++] = (byte) ((wid >> 24) & 0xff);
    setSizeBuf[n++] = (byte) ((wid >> 16) & 0xff);
    setSizeBuf[n++] = (byte) ((wid >> 8) & 0xff);
    setSizeBuf[n++] = (byte) (wid & 0xff);
    setSizeBuf[n++] = (byte) ((w >> 24) & 0xff);
    setSizeBuf[n++] = (byte) ((w >> 16) & 0xff);
    setSizeBuf[n++] = (byte) ((w >> 8) & 0xff);
    setSizeBuf[n++] = (byte) (w & 0xff);
    setSizeBuf[n++] = (byte) ((h >> 24) & 0xff);
    setSizeBuf[n++] = (byte) ((h >> 16) & 0xff);
    setSizeBuf[n++] = (byte) ((h >> 8) & 0xff);
    setSizeBuf[n++] = (byte) (h & 0xff);

    slaveSocket.send(sign(setSizeBuf));
  }

  public void windowSetRotateY(int cid, int wid, float rotY) throws IOException {
    int n = 0;
    int irotY = Float.floatToRawIntBits(rotY);

    setRotateYBuf[n++] = (byte) Proto.ClientMessageType.WINDOW_SET_ROTATE_Y.ordinal();
    setRotateYBuf[n++] = 0; // Pad
    setRotateYBuf[n++] = 0; // Pad
    setRotateYBuf[n++] = 0; // Pad
    setRotateYBuf[n++] = (byte) ((cid >> 24) & 0xff);
    setRotateYBuf[n++] = (byte) ((cid >> 16) & 0xff);
    setRotateYBuf[n++] = (byte) ((cid >> 8) & 0xff);
    setRotateYBuf[n++] = (byte) (cid & 0xff);
    setRotateYBuf[n++] = (byte) ((wid >> 24) & 0xff);
    setRotateYBuf[n++] = (byte) ((wid >> 16) & 0xff);
    setRotateYBuf[n++] = (byte) ((wid >> 8) & 0xff);
    setRotateYBuf[n++] = (byte) (wid & 0xff);
    setRotateYBuf[n++] = (byte) ((irotY >> 24) & 0xff);
    setRotateYBuf[n++] = (byte) ((irotY >> 16) & 0xff);
    setRotateYBuf[n++] = (byte) ((irotY >> 8) & 0xff);
    setRotateYBuf[n++] = (byte) (irotY & 0xff);

    slaveSocket.send(sign(setRotateYBuf));
  }

  public void windowToFront(int cid, int wid) throws IOException {
    int n = 0;

    toFrontBuf[n++] = (byte) Proto.ClientMessageType.WINDOW_TO_FRONT.ordinal();
    toFrontBuf[n++] = 0; // Pad
    toFrontBuf[n++] = 0; // Pad
    toFrontBuf[n++] = 0; // Pad
    toFrontBuf[n++] = (byte) ((cid >> 24) & 0xff);
    toFrontBuf[n++] = (byte) ((cid >> 16) & 0xff);
    toFrontBuf[n++] = (byte) ((cid >> 8) & 0xff);
    toFrontBuf[n++] = (byte) (cid & 0xff);
    toFrontBuf[n++] = (byte) ((wid >> 24) & 0xff);
    toFrontBuf[n++] = (byte) ((wid >> 16) & 0xff);
    toFrontBuf[n++] = (byte) ((wid >> 8) & 0xff);
    toFrontBuf[n++] = (byte) (wid & 0xff);

    slaveSocket.send(sign(toFrontBuf));
  }

  public void destroyWindow(int wid) throws IOException {
    int n = 0;

    destroyWindowBuf[n++] = (byte) Proto.ClientMessageType.DESTROY_WINDOW.ordinal();
    destroyWindowBuf[n++] = 0; // Pad
    destroyWindowBuf[n++] = 0; // Pad
    destroyWindowBuf[n++] = 0; // Pad
    destroyWindowBuf[n++] = (byte) ((wid >> 24) & 0xff);
    destroyWindowBuf[n++] = (byte) ((wid >> 16) & 0xff);
    destroyWindowBuf[n++] = (byte) ((wid >> 8) & 0xff);
    destroyWindowBuf[n++] = (byte) (wid & 0xff);

    slaveSocket.send(sign(destroyWindowBuf));
  }

  public void slaveCloseWindow(int clientId, int wid) throws IOException {
    int n = 0;

    slaveCloseWindowBuf[n++] = (byte) Proto.ClientMessageType.SLAVE_CLOSE_WINDOW.ordinal();
    slaveCloseWindowBuf[n++] = 0; // Pad
    slaveCloseWindowBuf[n++] = 0; // Pad
    slaveCloseWindowBuf[n++] = 0; // Pad
    slaveCloseWindowBuf[n++] = (byte) ((clientId >> 24) & 0xff);
    slaveCloseWindowBuf[n++] = (byte) ((clientId >> 16) & 0xff);
    slaveCloseWindowBuf[n++] = (byte) ((clientId >> 8) & 0xff);
    slaveCloseWindowBuf[n++] = (byte) (clientId & 0xff);
    slaveCloseWindowBuf[n++] = (byte) ((wid >> 24) & 0xff);
    slaveCloseWindowBuf[n++] = (byte) ((wid >> 16) & 0xff);
    slaveCloseWindowBuf[n++] = (byte) ((wid >> 8) & 0xff);
    slaveCloseWindowBuf[n++] = (byte) (wid & 0xff);

    slaveSocket.send(sign(slaveCloseWindowBuf));
  }

  /**
   * Sign a message from this slave with the shared secret. This method will overwrite the last
   * SIGNATURE_SIZE bytes of the given buffer with the following data: [4 bytes] - a per-client
   * counter to prevent replays [20 bytes] - a SHA-1 signature on the rest of the client data
   *
   * @param message the message to sign
   * @return the signed message. This method does not create a new byte array, it just signs the
   *     message in place. The return value is a convenience for chaining.
   */
  private synchronized byte[] sign(byte[] message) {
    int idx = message.length - Proto.SIGNATURE_SIZE;

    // encode the counter
    counter++;
    message[idx++] = (byte) ((counter >> 24) & 0xff);
    message[idx++] = (byte) ((counter >> 16) & 0xff);
    message[idx++] = (byte) ((counter >> 8) & 0xff);
    message[idx++] = (byte) (counter & 0xff);

    // now sign the whole thing
    try {
      mac.update(message, 0, idx);
      mac.doFinal(message, idx);
    } catch (ShortBufferException sbe) {
      // shouldn't happen
      throw new IllegalStateException(sbe);
    }
    return message;
  }

  // For Debug
  private static void print10bytes(byte[] bytes) {
    int n = (bytes.length > 10) ? 10 : bytes.length;
    for (int i = 0; i < n; i++) {
      System.err.print(Integer.toHexString(bytes[i] & 0xff) + " ");
    }
    System.err.println();
  }

  // For Debug
  private static String printbytes(byte[] bytes) {
    StringBuffer sb = new StringBuffer();

    for (int i = 0; i < bytes.length; i++) {
      sb.append(Integer.toHexString(bytes[i] & 0xff) + " ");
    }
    return sb.toString();
  }
}