/* * Copyright (c) 2010-2016, Sikuli.org, sikulix.com * Released under the MIT License. * */ package org.sikuli.script; import java.awt.Point; import java.awt.Rectangle; import java.lang.reflect.Method; import org.sikuli.basics.Debug; /** * EXPERIMENTAL --- INTERNAL USE ONLY<br> * is not official API --- will not be in version 2 */ public class Commands { private static int lvl = 2; private static void log(int level, String message, Object... args) { Debug.logx(level, "Commands: " + message, args); } private static void logCmd(String cmd, Object... args) { String msg = cmd + ": "; if (args.length == 0) { log(lvl, msg + "no-args"); } else { for (int i = 0; i < args.length; i++) { msg += "%s "; } log(lvl, msg, args); } } int i = 0; private static Region scr = new Screen(); private static Region scrSaved = null; private static RunTime runTime = RunTime.get(); /** * @return true if we are on Java 8+ */ public static boolean isNashorn() { return runTime.isJava8(); } /** * INTERNAL USE: call interface for JavaScript to be used with predefined functions * * @param function the function's name * @param args the parameters * @return the object returned by the function or null */ public static Object call(String function, Object... args) { Method m = null; Object retVal = null; int count = 0; for (Object aObj : args) { if (aObj == null || aObj.getClass().getName().endsWith("Undefined")) { break; } if (aObj instanceof String && ((String) aObj).contains("undefined")) { break; } count++; } Object[] newArgs = new Object[count]; for (int n = 0; n < count; n++) { newArgs[n] = args[n]; } try { m = Commands.class.getMethod(function, Object[].class); retVal = m.invoke(null, (Object) newArgs); } catch (Exception ex) { m = null; } return retVal; } public static Object run(Object... args) { String script = args[0].toString(); String scriptArgs[] = new String[args.length - 1]; if (scriptArgs.length > 0) { for (int i = 1; i < args.length; i++) { scriptArgs[i - 1] = args[i].toString(); } } return Runner.run(script, scriptArgs); } public static Object circle(Object args) { return 0; } //<editor-fold defaultstate="collapsed" desc="conversions"> private static boolean isNumber(Object aObj) { if (aObj instanceof Integer || aObj instanceof Long || aObj instanceof Float || aObj instanceof Double) { return true; } return false; } private static int getInteger(Object aObj, int deflt) { Integer val = deflt; if (aObj instanceof Integer || aObj instanceof Long) { val = (Integer) aObj; } if (aObj instanceof Float) { val = Math.round((Float) aObj); } if (aObj instanceof Double) { val = (int) Math.round((Double) aObj); } return val; } private static int getInteger(Object aObj) { return getInteger(aObj, 0); } private static double getNumber(Object aObj, Double deflt) { Double val = deflt; if (aObj instanceof Integer) { val = 0.0 + (Integer) aObj; } else if (aObj instanceof Long) { val = 0.0 + (Long) aObj; } else if (aObj instanceof Float) { val = 0.0 + (Float) aObj; } else if (aObj instanceof Double) { val = (Double) aObj; } return val; } private static double getNumber(Object aObj) { return getNumber(aObj, 0.0); } //</editor-fold> //<editor-fold defaultstate="collapsed" desc="use/use1"> /** * all following undotted function calls will use the given screen or region * until this is changed by a later use()<br> * -- no args: use Screen(0) (this is the default after start)<br> * -- a number: use Screen(number), Screen(0) if not valid<br> * -- a region: use the given region<br> * * @param args * @return the used region */ public static Region use(Object... args) { logCmd("use", args); scrSaved = null; return usex(args); } /** * same as use(), but only affects the next processed undotted function * after that the use() active before is restored * * @param args see use() * @return the used region */ public static Region use1(Object... args) { logCmd("use1", args); scrSaved = scr; return usex(args); } /** * INTERNAL USE: restore a saved use() after a use1() */ public static void restoreUsed() { if (scrSaved != null) { scr = scrSaved; scrSaved = null; log(lvl, "restored: %s", scr); } } private static Region usex(Object... args) { int len = args.length; int nScreen = -1; if (len == 0 || len > 1) { scr = new Screen(); return scr; } nScreen = getInteger(args[0], -1); if (nScreen > -1) { scr = new Screen(nScreen); } else { Object oReg = args[0]; if (oReg instanceof Region) { scr = (Region) oReg; } } return scr; } //</editor-fold> //<editor-fold defaultstate="collapsed" desc="wait/waitVanish/exists"> /** * wait for the given visual to appear within the given wait time<br> * args [String|Pattern|Double, [Double, [Float]]] (max 3 args)<br> * arg1: String/Pattern to search or double time to wait (rest ignored)<br> * arg2: time to wait in seconds<br> * arg3: minimum similarity to use for search (overwrites Pattern setting)<br> * * @param args * @return the match or throws FindFailed * @throws FindFailed */ public static Match wait(Object... args) throws FindFailed { logCmd("wait", args); Object[] realArgs = waitArgs(args); return waitx((String) realArgs[0], (Pattern) realArgs[1], (Double) realArgs[2], (Float) realArgs[3]); } private static Match waitx(String image, Pattern pimage, double timeout, float score) throws FindFailed { Object aPattern = null; if (image != null) { if (score > 0) { aPattern = new Pattern(image).similar(score); } else { aPattern = image; } } else if (pimage != null) { aPattern = pimage; } if (aPattern != null) { if (timeout > -1.0) { return scr.wait(aPattern, timeout); } return scr.wait(aPattern); } return null; } private static Object[] waitArgs(Object... args) { int len = args.length; String image = ""; float score = 0.0f; double timeout = -1.0f; boolean argsOK = true; Object[] realArgs = new Object[]{null, null, (Double) (-1.0), (Float) 0f}; if (len == 0 || len > 3) { argsOK = false; } else { Object aObj = args[0]; if (aObj == null) { return realArgs; } if (isJSON(aObj)) { aObj = fromJSON(aObj); } if (aObj instanceof String) { realArgs[0] = aObj; } else if (aObj instanceof Pattern) { realArgs[1] = aObj; if (len > 1 && isNumber(args[1])) { realArgs[2] = (Double) getNumber(args[1]); } } else if (isNumber(aObj)) { scr.wait(getNumber(aObj)); return null; } else { argsOK = false; } } if (argsOK && len > 1 && realArgs[1] == null) { if (len > 2 && isNumber(args[2])) { score = (float) getNumber(args[2]) / 100.0f; if (score < 0.7) { score = 0.7f; } else if (score > 0.99) { score = 0.99f; } } if (score > 0.0f) { realArgs[3] = (Float) score; } if (len > 1 && isNumber(args[1])) { realArgs[2] = (Double) getNumber(args[1]); } } if (!argsOK) { throw new UnsupportedOperationException( "Commands.wait: parameters: String/Pattern:image, float:timeout, int:score"); } return realArgs; } /** * wait for the given visual to vanish within the given wait time * * @param args see wait() * @return true if not there from beginning or vanished within wait time, false otherwise */ public static boolean waitVanish(Object... args) { logCmd("waitVanish", args); Object aPattern; Object[] realArgs = waitArgs(args); String image = (String) realArgs[0]; Pattern pimage = (Pattern) realArgs[1]; double timeout = (Double) realArgs[2]; float score = (Float) realArgs[3]; if (image != null) { if (score > 0) { aPattern = new Pattern(image).similar(score); } else { aPattern = image; } } else { aPattern = pimage; } if (timeout > -1.0) { return scr.waitVanish(aPattern, timeout); } return scr.waitVanish(aPattern); } /** * wait for the given visual to appear within the given wait time * * @param args see wait() * @return the match or null if not found within wait time (no FindFailed exception) */ public static Match exists(Object... args) { logCmd("exists", args); Match match = null; Object[] realArgs = waitArgs(args); if ((Double) realArgs[2] < 0.0) { realArgs[2] = 0.0; } try { match = waitx((String) realArgs[0], (Pattern) realArgs[1], (Double) realArgs[2], (Float) realArgs[3]); } catch (Exception ex) { return null; } return match; } //</editor-fold> //<editor-fold defaultstate="collapsed" desc="hover/click/doubleClick/rightClick"> /** * move the mouse to the given location with a given offset<br> * 3 parameter configurations:<br> * --1: wait for a visual and move mouse to it (args see wait())<br> * --2: move to the given region/location/match with a given offset<br> * --3: move to the given offset relative to the last match of the region in use * * @param args * @return the evaluated location to where the mouse should have moved */ public static Location hover(Object... args) { logCmd("hover", args); return hoverx(args); } private static Location hoverx(Object... args) { int len = args.length; Match aMatch; if (len == 0 || args[0] == null) { Mouse.move(scr.checkMatch()); return Mouse.at(); } if (len < 4) { Object aObj = args[0]; Location loc = null; if (isJSON(aObj)) { aObj = fromJSON(aObj); } if (aObj instanceof String || aObj instanceof Pattern) { try { aMatch = wait(args); Mouse.move(aMatch.getTarget()); } catch (Exception ex) { Mouse.move(scr.checkMatch()); } return Mouse.at(); } else if (aObj instanceof Region) { loc = ((Region) aObj).getTarget(); } else if (aObj instanceof Location) { loc = (Location) aObj; } if (len > 1) { if (isNumber(aObj) && isNumber(args[1])) { Mouse.move(scr.checkMatch().offset(getInteger(aObj), getInteger(args[1]))); return Mouse.at(); } else if (len == 3 && loc != null && isNumber(args[1]) && isNumber(args[2])) { Mouse.move(loc.offset(getInteger(args[1], 0), getInteger(args[2], 0))); return Mouse.at(); } } if (loc != null) { Mouse.move(loc); return Mouse.at(); } } Mouse.move(scr.checkMatch()); return Mouse.at(); } /** * move the mouse with hover() and click using the left button * * @param args see hover() * @return the location, where the click was done */ public static Location click(Object... args) { logCmd("click", args); Location loc = hoverx(args); Mouse.click(null, Button.LEFT, 0, false, null); return Mouse.at(); } /** * move the mouse with hover() and double click using the left button * * @param args see hover() * @return the location, where the double click was done */ public static Location doubleClick(Object... args) { logCmd("doubleClick", args); Location loc = hoverx(args); Mouse.click(null, Button.LEFT, 0, true, null); return Mouse.at(); } /** * move the mouse with hover() and do a right click * * @param args see hover() * @return the location, where the right click was done */ public static Location rightClick(Object... args) { logCmd("rightClick", args); Location loc = hoverx(args); Mouse.click(null, Button.RIGHT, 0, false, null); return Mouse.at(); } //</editor-fold> //<editor-fold defaultstate="collapsed" desc="type/write/paste"> /** * just doing a currentRegion.paste(text) (see paste()) * * @param args only one parameter being a String * @return true if paste() returned 1, false otherwise */ public static boolean paste(Object... args) { logCmd("paste", args); Object[] realArgs = typeArgs(args); return 0 < scr.paste((String) realArgs[0]); } /** * just doing a currentRegion.write(text) (see write()) * * @param args only one parameter being a String * @return true if write() returned 1, false otherwise */ public static boolean write(Object... args) { logCmd("write", args); Object[] realArgs = typeArgs(args); return 0 < scr.write((String) realArgs[0]); } private static Object[] typeArgs(Object... args) { Object[] realArgs = new Object[]{null}; if (!(args[0] instanceof String)) { throw new UnsupportedOperationException("Commands.type/paste/write: parameters: String:text"); } realArgs[0] = args[0]; return realArgs; } //</editor-fold> //<editor-fold defaultstate="collapsed" desc="JSON support"> /** * check wether the given object is in JSON format as ["ID", ...] * * @param aObj * @return true if object is in JSON format, false otherwise */ public static boolean isJSON(Object aObj) { if (aObj instanceof String) { return ((String) aObj).startsWith("[\""); } return false; } /** * experimental: create the real object from the given JSON<br> * take care: content length not checked if valid (risk for index out of bounds)<br> * planned to be used with a socket/RPC based interface to any framework (e.g. C#) * Region ["R", x, y, w, h]<br> * Location ["L", x, y]<br> * Match ["M", x, y, w, h, score, offx, offy]<br> * Screen ["S", x, y, w, h, id]<br> * Pattern ["P", "imagename", score, offx, offy]<br> * These real objects have a toJSON(), that returns these JSONs<br> * * @param aObj * @return the real object or the given object if it is not one of these JSONs */ public static Object fromJSON(Object aObj) { if (!isJSON(aObj)) { return aObj; } Object newObj = null; String[] json = ((String) aObj).split(","); String last = json[json.length - 1]; if (!last.endsWith("]")) { return aObj; } else { json[json.length - 1] = last.substring(0, last.length() - 1); } String oType = json[0].substring(2, 3); if (!"SRML".contains(oType)) { return aObj; } if ("S".equals(oType)) { aObj = new Screen(intFromJSON(json, 5)); ((Screen) aObj).setRect(rectFromJSON(json)); } else if ("R".equals(oType)) { newObj = new Region(rectFromJSON(json)); } else if ("M".equals(oType)) { double score = dblFromJSON(json, 5) / 100; newObj = new Match(new Region(rectFromJSON(json)), score); ((Match) newObj).setTarget(intFromJSON(json, 6), intFromJSON(json, 7)); } else if ("L".equals(oType)) { newObj = new Location(locFromJSON(json)); } else if ("P".equals(oType)) { newObj = new Pattern(json[1]); ((Pattern) newObj).similar(fltFromJSON(json, 2)); ((Pattern) newObj).targetOffset(intFromJSON(json, 3), intFromJSON(json, 4)); } return newObj; } private static Rectangle rectFromJSON(String[] json) { int[] vals = new int[4]; for (int n = 1; n < 5; n++) { try { vals[n - 1] = Integer.parseInt(json[n].trim()); } catch (Exception ex) { vals[n - 1] = 0; } } return new Rectangle(vals[0], vals[1], vals[2], vals[3]); } private static Point locFromJSON(String[] json) { int[] vals = new int[2]; for (int n = 1; n < 3; n++) { try { vals[n - 1] = Integer.parseInt(json[n].trim()); } catch (Exception ex) { vals[n - 1] = 0; } } return new Point(vals[0], vals[1]); } private static int intFromJSON(String[] json, int pos) { try { return Integer.parseInt(json[pos].trim()); } catch (Exception ex) { return 0; } } private static float fltFromJSON(String[] json, int pos) { try { return Float.parseFloat(json[pos].trim()); } catch (Exception ex) { return 0; } } private static double dblFromJSON(String[] json, int pos) { try { return Double.parseDouble(json[pos].trim()); } catch (Exception ex) { return 0; } } //</editor-fold> }