package it.paspiz85.nanobot.platform.mac; import it.paspiz85.nanobot.exception.BotConfigurationException; import it.paspiz85.nanobot.platform.AbstractPlatform; import it.paspiz85.nanobot.platform.Platform; import it.paspiz85.nanobot.util.OS; import it.paspiz85.nanobot.util.Point; import it.paspiz85.nanobot.util.Size; import it.paspiz85.nanobot.util.Utils; import java.awt.AWTException; import java.awt.Color; import java.awt.MouseInfo; import java.awt.PointerInfo; import java.awt.Rectangle; import java.awt.Robot; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.image.BufferedImage; import java.io.File; import java.util.List; import java.util.function.Consumer; import java.util.logging.Level; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathFactory; import org.w3c.dom.Document; import org.w3c.dom.DocumentType; import org.w3c.dom.Element; /** * Platform implementation of BlueStacks for Mac. * * @author paspiz85 * */ public final class BlueStacksMacPlatform extends AbstractPlatform { public static final Size BS_SIZE = new Size(Platform.GAME_SIZE.x(), Platform.GAME_SIZE.y() + 47); private static final String BS_WINDOW_NAME = "BlueStacks App Player"; private static final int TITLE_BAR_HEIGHT = 22; public static Platform instance() { // return Utils.singleton(UnsupportedPlatform.class, () -> // UnsupportedPlatform.instance()); return Utils.singleton(BlueStacksMacPlatform.class, () -> new BlueStacksMacPlatform()); } private static BlueStacksMacPlatform instanceNew() { return Utils.singleton(BlueStacksMacPlatform.class, () -> new BlueStacksMacPlatform()); } public static boolean isSupported() { return OS.getCurrent().getFamily() == OS.Family.MAC; } private Point position; private final Robot robot; private final ScriptEngineManager scriptEngineManager; private BlueStacksMacPlatform() { try { robot = new Robot(); } catch (final AWTException e) { throw new IllegalStateException("Unable to init robot", e); } scriptEngineManager = new ScriptEngineManager(); } private Point clientToScreen(final Point point) { final int x = point.x() + position.x(); final int y = point.y() + position.y() + TITLE_BAR_HEIGHT + 3; return new Point(x, y); } @Override protected void doActivate() { try { final ScriptEngine engine = scriptEngineManager.getEngineByName("AppleScript"); final String script = "tell application \"BlueStacks\" to activate\n"; engine.eval(script); } catch (final ScriptException e) { logger.log(Level.SEVERE, e.getMessage(), e); } } @Override protected void doApplySize(final Size size) throws BotConfigurationException { try { final File plistFile = new File(System.getProperty("user.home") + "/Library/Preferences/com.BlueStacks.AppPlayer.plist"); final DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); final DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder(); final XPathFactory xpathFactory = XPathFactory.newInstance(); final XPath xpath = xpathFactory.newXPath(); final TransformerFactory transformerFactor = TransformerFactory.newInstance(); final Transformer transformer = transformerFactor.newTransformer(); final Document document = docBuilder.parse(plistFile); final Element frameBufferNode = (Element) xpath.evaluate("/plist/dict" + "/key[text()='Guests']/following-sibling::dict[1]" + "/key[text()='Android']/following-sibling::dict[1]" + "/key[text()='FrameBuffer']/following-sibling::dict[1]", document, XPathConstants.NODE); final Element widthNode = (Element) xpath.evaluate("key[text()='Width']/following-sibling::integer[1]", frameBufferNode, XPathConstants.NODE); final Element heightNode = (Element) xpath.evaluate("key[text()='Height']/following-sibling::integer[1]", frameBufferNode, XPathConstants.NODE); final Size current = new Size(Integer.parseInt(widthNode.getTextContent()), Integer.parseInt(heightNode .getTextContent())); if (!current.equals(size)) { widthNode.setTextContent(Integer.toString(size.x())); heightNode.setTextContent(Integer.toString(size.y())); final DocumentType documentType = document.getDoctype(); if (documentType != null) { transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, documentType.getSystemId()); transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, documentType.getPublicId()); } transformer.transform(new DOMSource(document), new StreamResult(plistFile)); } throw new BotConfigurationException(String.format("Please restart %s to fix resolution", BS_WINDOW_NAME)); } catch (BotConfigurationException | RuntimeException e) { throw e; } catch (final Exception e) { throw new RuntimeException(e); } } @Override protected void doKeyPress(final int keyCode, final boolean shifted) throws InterruptedException { // TODO Auto-generated method stub throw new UnsupportedOperationException("not implemented"); } @Override protected void doLeftClick(final Point point) throws InterruptedException { // TODO non funziona final PointerInfo a = MouseInfo.getPointerInfo(); final java.awt.Point b = a.getLocation(); final int xOrig = (int) b.getX(); final int yOrig = (int) b.getY(); try { final Point p = clientToScreen(point); robot.mouseMove(p.x(), p.y()); robot.mousePress(InputEvent.BUTTON1_MASK); robot.mouseRelease(InputEvent.BUTTON1_MASK); } finally { robot.mouseMove(xOrig, yOrig); } } @Override protected BufferedImage doScreenshot(final Point p1, final Point p2) { final Point anchor = clientToScreen(p1); final int width = p2.x() - p1.x(); final int height = p2.y() - p1.y(); return robot.createScreenCapture(new Rectangle(anchor.x(), anchor.y(), width, height)); } @Override protected void doSingleZoomUp() throws InterruptedException { robot.keyPress(KeyEvent.VK_DOWN); Thread.sleep(100); robot.keyRelease(KeyEvent.VK_DOWN); } @Override protected void doWrite(final String s) throws InterruptedException { // TODO Auto-generated method stub throw new UnsupportedOperationException("not implemented"); } @Override protected Size getActualSize() { Size size = null; try { final ScriptEngine engine = scriptEngineManager.getEngineByName("AppleScript"); final String script = "tell application \"System Events\" to tell application process \"BlueStacks\"\n" + "get size of front window\n" + "end tell\n"; @SuppressWarnings("unchecked") final List<? extends Number> result = (List<? extends Number>) engine.eval(script); size = new Size(result.get(0).intValue(), result.get(1).intValue() - TITLE_BAR_HEIGHT); } catch (final ScriptException e) { logger.log(Level.SEVERE, e.getMessage(), e); size = null; } return size; } @Override protected Color getColor(final Point point) { final Point screenPoint = clientToScreen(point); return robot.getPixelColor(screenPoint.x(), screenPoint.y()); } @Override public Size getExpectedSize() { return BS_SIZE; } private Point getPosition() { Point position = null; try { final ScriptEngine engine = scriptEngineManager.getEngineByName("AppleScript"); final String script = "tell application \"System Events\" to tell application process \"BlueStacks\"\n" + "get position of front window\n" + "end tell\n"; @SuppressWarnings("unchecked") final List<? extends Number> result = (List<? extends Number>) engine.eval(script); position = new Point(result.get(0).intValue(), result.get(1).intValue()); } catch (final ScriptException e) { logger.log(Level.SEVERE, e.getMessage(), e); position = null; } return position; } @Override protected boolean registerForClick(final Consumer<Point> clickConsumer) { // TODO implement but not now (it's not used from main logic) throw new UnsupportedOperationException("Not implemented"); } @Override protected void setup() throws BotConfigurationException { position = getPosition(); } }