/** * Copyright 2009 Marc Stogaitis and Mimi Sun * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.gmote.server.visualtouchpad; import java.awt.Rectangle; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Semaphore; import java.util.logging.Level; import java.util.logging.Logger; import org.gmote.common.TcpConnection; import org.gmote.common.packet.TileClickReq; import org.gmote.common.packet.TileInfoReply; import org.gmote.common.packet.TileSetReq; import org.gmote.common.packet.TileUpdatePacket; public class VisualTouchpad { private static final Logger LOGGER = Logger.getLogger(VisualTouchpad.class.getName()); private static VisualTouchpad instance = null; private TileHandler tileHandler = new TileHandler(); private List<ScreenTile> tilesToUpdate = new ArrayList<ScreenTile>(); private Semaphore tilesToUpdateSemaphore = new Semaphore(0); private TcpConnection con; private TileUpdaterTask tileUpdater = null; private TileSetReq latestTileSet; private Semaphore latestTileSemaphore = new Semaphore(0); private VisualTouchpad() { // TODO(mstogaitis): we'll need to make sure that the thread gets to its // 'wait' before allowing queries. new Thread(new TileRequestHandler()).start(); } /** * Gets an instance of this class. */ public static VisualTouchpad instance() { if (instance == null) { instance = new VisualTouchpad(); } return instance; } public void clearTileImages() { tileHandler.clearTileImages(); } public void tileUpdateRequest(TileSetReq tileSet) { LOGGER.info("Received tile update request for: " + tileSet); if (tileSet.getTile1X() < 0 || tileSet.getTile1Y() < 0 || tileSet.getTile2X() < 0 || tileSet.getTile2Y() < 0) { LOGGER.warning("Tile set request contains negative numbers. Ignoring it."); return; } synchronized (latestTileSemaphore) { latestTileSet = tileSet; latestTileSemaphore.release(); } } public void tileClickRequest(TileClickReq packet) { LOGGER.info("Click request: " + packet.getTileIdX() + " " + packet.getTileIdY() + " " + packet.getPixelOffsetInTileX() + " " + packet.getPixelOffsetInTileY()); ScreenTile tile = tileHandler.getTile(packet.getTileIdX(), packet.getTileIdY()); tile.clickMouse(packet.getPixelOffsetInTileX(), packet.getPixelOffsetInTileY(), packet .getMouseEvent()); } private class TileRequestHandler implements Runnable { @Override public void run() { while (true) { try { LOGGER.info("Acquire semaphore"); latestTileSemaphore.acquire(); LOGGER.info("Semaphore acquired"); } catch (InterruptedException e) { LOGGER.log(Level.SEVERE, e.getMessage(), e); } if (tileUpdater != null) { LOGGER.info("Pausing tile updater"); tileUpdater.setThreadPaused(true); LOGGER.info("Done pausing tile updater"); } // Synchronize on tileToUpdate so as not to interfere with the // tileUpdater thread. synchronized (tilesToUpdate) { tilesToUpdate.clear(); synchronized (latestTileSemaphore) { for (int tileIdX = latestTileSet.getTile1X(); tileIdX <= latestTileSet.getTile2X(); tileIdX++) { for (int tileIdY = latestTileSet.getTile1Y(); tileIdY <= latestTileSet.getTile2Y(); tileIdY++) { ScreenTile tile = tileHandler.getTile(tileIdX, tileIdY); if (tile != null) { tilesToUpdate.add(tile); } } } latestTileSemaphore.drainPermits(); } } if (tileUpdater == null) { tileUpdater = new TileUpdaterTask(); tileUpdater.start(); } LOGGER.info("Unpausing tile updater"); tileUpdater.setThreadPaused(false); LOGGER.info("Done unpausing tile updater"); } } } private class TileUpdaterTask extends Thread { private Boolean threadPaused = false; @Override public void run() { TcpConnection conToSend; while (true) { try { if (isThreadPaused()) { LOGGER.info("TileUpdater about to acquire semaphore"); tilesToUpdateSemaphore.acquire(); LOGGER.info("TileUpdater acquired semaphore"); } } catch (InterruptedException e1) { LOGGER.log(Level.SEVERE, e1.getMessage(), e1); } synchronized (tilesToUpdate) { for (ScreenTile tile : tilesToUpdate) { if (isThreadPaused()) { // Leave the for loop if the thread should be paused. LOGGER.info("Leaving for loop since thread going to be paused"); break; } try { byte[] imageData = tile.takeImage(); if (imageData != null) { LOGGER.info("Sending tile " + tile.getIdX() + " " + tile.getIdY()); conToSend = getConnection(); conToSend.sendPacket(new TileUpdatePacket(tile.getIdX(), tile.getIdY(), imageData)); } } catch (IOException e) { LOGGER.log(Level.SEVERE, e.getMessage(), e); setThreadPaused(true); break; } } try { Thread.sleep(500); } catch (InterruptedException e) { LOGGER.log(Level.SEVERE, e.getMessage(), e); } } } } public synchronized void setThreadPaused(boolean paused) { threadPaused = paused; if (paused) { tilesToUpdateSemaphore.drainPermits(); } else { tilesToUpdateSemaphore.release(); } } public synchronized boolean isThreadPaused() { return threadPaused; } } public TileInfoReply createScreenInfoReply() { Rectangle rect = tileHandler.getAllScreenRect(); TileInfoReply reply = new TileInfoReply(rect.width, rect.height, TileHandler.TILE_SIZE); return reply; } public synchronized void setConnection(TcpConnection newConnection) { con = newConnection; } public synchronized TcpConnection getConnection() { return con; } }