Java tutorial
/* * Copyright (c) 2010-2016, Sikuli.org, sikulix.com * Released under the MIT License. * */ package org.sikuli.script; import org.sikuli.basics.Debug; import org.sikuli.natives.OSUtil; import org.sikuli.natives.SysUtil; import java.awt.*; import java.awt.datatransfer.*; import java.io.*; import java.net.URI; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.util.Date; import java.util.HashMap; import java.util.Map; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpResponseException; import org.apache.http.client.ResponseHandler; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.sikuli.basics.FileManager; /** * App implements features to manage (open, switch to, close) applications. on the system we are running on and to * access their assets like windows * <br> * TAKE CARE: function behavior differs depending on the running system (cosult the docs for more info) */ public class App { static RunTime runTime = RunTime.get(); private static final OSUtil _osUtil = SysUtil.getOSUtil(); private String appNameGiven; private String appOptions; private String appName; private String appWindow; private int appPID; private boolean isImmediate = false; private boolean notFound = false; private static final Map<Type, String> appsWindows; private static final Map<Type, String> appsMac; private static final Region aRegion = new Region(); static { //TODO Sikuli hangs if App is used before Screen new Screen(); _osUtil.checkFeatureAvailability(); appsWindows = new HashMap<Type, String>(); appsWindows.put(Type.EDITOR, "Notepad"); appsWindows.put(Type.BROWSER, "Google Chrome"); appsWindows.put(Type.VIEWER, ""); appsMac = new HashMap<Type, String>(); appsMac.put(Type.EDITOR, "TextEdit"); appsMac.put(Type.BROWSER, "Safari"); appsMac.put(Type.VIEWER, "Preview"); } //<editor-fold defaultstate="collapsed" desc="features based on org.apache.httpcomponents.httpclient"> private static CloseableHttpClient httpclient = null; /** * create a HTTP Client (for use of wwGet, ... multiple times as session) * @return true on success, false otherwise */ public static boolean wwwStart() { if (httpclient != null) { return true; } httpclient = HttpClients.createDefault(); if (httpclient != null) { return true; } return false; } /** * stop a started HTTP Client */ public static void wwwStop() { if (httpclient != null) { try { httpclient.close(); } catch (IOException ex) { } httpclient = null; } } /** * issue a http(s) request * @param url a valid url as used in a browser * @return textual content of the response or empty (UTF-8) * @throws IOException */ public static String wwwGet(String url) throws IOException { HttpGet httpget = new HttpGet(url); CloseableHttpResponse response = null; ResponseHandler rh = new ResponseHandler() { @Override public String handleResponse(final HttpResponse response) throws IOException { StatusLine statusLine = response.getStatusLine(); HttpEntity entity = response.getEntity(); if (statusLine.getStatusCode() >= 300) { throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase()); } if (entity == null) { throw new ClientProtocolException("Response has no content"); } InputStream is = entity.getContent(); ByteArrayOutputStream result = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int length; while ((length = is.read(buffer)) != -1) { result.write(buffer, 0, length); } return result.toString("UTF-8"); } }; boolean oneTime = false; if (httpclient == null) { wwwStart(); oneTime = true; } Object content = httpclient.execute(httpget, rh); if (oneTime) { wwwStop(); } return (String) content; } /** * same as wwwGet(), but the content is also saved to a file * @param url a valid url as used in a browser * @param pOut absolute path to output file (overwritten) (if null: bundlePath/wwwSave.txt is taken) * @return textual content of the response or empty (UTF-8) * @throws IOException */ public static String wwwSave(String url, String pOut) throws IOException { String content = wwwGet(url); File out = null; if (pOut == null) { out = new File(ImagePath.getBundleFolder(), "wwwSave.txt"); } else { out = new File(pOut); } FileManager.writeStringToFile(content, out); return content; } //</editor-fold> //<editor-fold defaultstate="collapsed" desc="special app features"> public static enum Type { EDITOR, BROWSER, VIEWER } public static Region start(Type appType) { App app = null; Region win; try { if (Type.EDITOR.equals(appType)) { if (runTime.runningMac) { app = new App(appsMac.get(appType)); if (app.window() != null) { app.focus(); aRegion.wait(0.5); win = app.window(); aRegion.click(win); aRegion.write("#M.a#B."); return win; } else { app.open(); win = app.waitForWindow(); app.focus(); aRegion.wait(0.5); aRegion.click(win); return win; } } if (runTime.runningWindows) { app = new App(appsWindows.get(appType)); if (app.window() != null) { app.focus(); aRegion.wait(0.5); win = app.window(); aRegion.click(win); aRegion.write("#C.a#B."); return win; } else { app.open(); win = app.waitForWindow(); app.focus(); aRegion.wait(0.5); aRegion.click(win); return win; } } } else if (Type.BROWSER.equals(appType)) { if (runTime.runningWindows) { app = new App(appsWindows.get(appType)); if (app.window() != null) { app.focus(); aRegion.wait(0.5); win = app.window(); aRegion.click(win); // aRegion.write("#C.a#B."); return win; } else { app.open(); win = app.waitForWindow(); app.focus(); aRegion.wait(0.5); aRegion.click(win); return win; } } return null; } else if (Type.VIEWER.equals(appType)) { return null; } } catch (Exception ex) { } return null; } public Region waitForWindow() { return waitForWindow(5); } public Region waitForWindow(int seconds) { Region win = null; while ((win = window()) == null && seconds > 0) { aRegion.wait(0.5); seconds -= 0.5; } return win; } public static boolean openLink(String url) { if (!Desktop.isDesktopSupported()) { return false; } try { Desktop.getDesktop().browse(new URI(url)); } catch (Exception ex) { return false; } return true; } private static Region asRegion(Rectangle r) { if (r != null) { return Region.create(r); } else { return null; } } public static void pause(int time) { try { Thread.sleep(time * 1000); } catch (InterruptedException ex) { } } public static void pause(float time) { try { Thread.sleep((int) (time * 1000)); } catch (InterruptedException ex) { } } //</editor-fold> //<editor-fold defaultstate="collapsed" desc="AppEntry"> public static class AppEntry { public String name; public String execName; public String options; public String window; public int pid; public AppEntry(String theName, String thePID, String theWindow, String theExec, String theOptions) { name = theName; window = theWindow; options = theOptions; pid = -1; execName = theExec; try { pid = Integer.parseInt(thePID); } catch (Exception ex) { } } } public AppEntry makeAppEntry() { String name = appName; String window = appWindow; if (name.isEmpty() && appOptions.isEmpty()) { name = appNameGiven; } if (isImmediate && !window.startsWith("!")) { window = "!" + window; } if (notFound) { name = "!" + name; } String pid = getPID().toString(); AppEntry appEntry = new AppEntry(name, pid, window, appNameGiven, appOptions); return appEntry; } //</editor-fold> //<editor-fold defaultstate="collapsed" desc="constructors"> /** * creates an instance for an app with this name (nothing done yet) * * @param name name */ public App(String name) { appNameGiven = name; appName = name; appPID = -1; appWindow = ""; appOptions = ""; String execName = ""; if (appNameGiven.startsWith("+")) { isImmediate = true; appNameGiven = appNameGiven.substring(1); Debug.log(3, "App.immediate: %s", appNameGiven); appName = appNameGiven; String[] parts; if (appName.startsWith("\"")) { parts = appName.substring(1).split("\""); if (parts.length > 1) { appOptions = appName.substring(parts[0].length() + 3); appName = "\"" + parts[0] + "\""; } } else { parts = appName.split(" "); if (parts.length > 1) { appOptions = appName.substring(parts[0].length() + 1); appName = parts[0]; } } if (appName.startsWith("\"")) { execName = appName.substring(1, appName.length() - 1); } else { execName = appName; } appName = new File(execName).getName(); File checkName = new File(execName); if (checkName.isAbsolute()) { if (!checkName.exists()) { appName = ""; appOptions = ""; appWindow = "!"; notFound = true; } } } else { init(appNameGiven); } Debug.log(3, "App.create: %s", toStringShort()); } private void init(String name) { AppEntry app = null; if (!(isImmediate && notFound)) { app = _osUtil.getApp(-1, name); } if (app != null) { appName = app.name; if (app.options.isEmpty()) { appPID = app.pid; if (!app.window.contains("N/A")) { appWindow = app.window; if (notFound) { notFound = false; } } } else { appOptions = app.options; appNameGiven = appName; } } } public App(int pid) { appNameGiven = "FromPID"; appName = ""; appPID = pid; appWindow = ""; init(pid); } private void init(int pid) { AppEntry app = _osUtil.getApp(pid, appName); if (app != null) { appName = app.name; appPID = app.pid; if (!app.window.contains("N/A")) { appWindow = app.window; } } else { appPID = -1; } } private void init() { if (appPID > -1) { init(appPID); } else { String name = appName; if (name.isEmpty() && appOptions.isEmpty()) { name = appNameGiven; } init(name); } } //</editor-fold> //<editor-fold defaultstate="collapsed" desc="getter/setter"> public static void getApps(String name) { Map<Integer, String[]> theApps = _osUtil.getApps(name); int count = 0; String[] item; for (Integer pid : theApps.keySet()) { item = theApps.get(pid); if (pid < 0) { pid = -pid; Debug.logp("%d:%s (N/A)", pid, item[0]); } else { Debug.logp("%d:%s (%s)", pid, item[0], item[1]); count++; } } Debug.logp("App.getApps: %d apps (%d having window)", theApps.size(), count); } public static void getApps() { getApps(null); } public App setUsing(String options) { if (options != null) { appOptions = options; } else { appOptions = ""; } return this; } public Integer getPID() { return appPID; } public String getName() { return appName; } public String getWindow() { return appWindow; } public boolean isValid() { return !notFound; } public boolean isRunning() { return isRunning(1); } public boolean isRunning(int maxTime) { if (!isValid()) { return false; } long wait = -1; for (int n = 0; n < maxTime; n++) { wait = new Date().getTime(); int retVal = _osUtil.isRunning(makeAppEntry()); if (retVal > 0) { init(); break; } if (n == 0) { continue; } wait = 1000 - new Date().getTime() + wait; if (wait > 0) { RunTime.pause(wait / 1000f); } } return appPID > -1; } public boolean hasWindow() { if (!isValid()) { return false; } init(appName); return !getWindow().isEmpty(); } @Override public String toString() { if (!appWindow.startsWith("!")) { init(); } return String.format("[%d:%s (%s)] %s", appPID, appName, appWindow, appNameGiven); } public String toStringShort() { return String.format("[%d:%s]", appPID, appName); } //</editor-fold> //<editor-fold defaultstate="collapsed" desc="open"> /** * creates an instance for an app with this name and tries to open it * * @param appName name * @return the App instance or null on failure */ public static App open(String appName) { return new App("+" + appName).open(); } /** * tries to open the app defined by this App instance<br> * do not wait for the app to get running * * @return this or null on failure */ public App open() { return openAndWait(0); } /** * tries to open the app defined by this App instance * * @return this or null on failure */ public App open(int waitTime) { return openAndWait(waitTime); } public App openAndWait(int waitTime) { if (isImmediate) { appPID = _osUtil.open(appNameGiven); } else { AppEntry appEntry = makeAppEntry(); init(_osUtil.open(appEntry)); } if (appPID < 0) { Debug.error("App.open failed: " + appNameGiven + " not found"); notFound = true; } else { Debug.action("App.open " + this.toStringShort()); } if (isImmediate && notFound) { return null; } if (waitTime > 0) { if (!isRunning(waitTime)) { return null; } } return this; } //</editor-fold> //<editor-fold defaultstate="collapsed" desc="close"> /** * tries to identify a running app with the given name and then tries to close it * * @param appName name * @return 0 for success -1 otherwise */ public static int close(String appName) { return new App("+" + appName).close(); } /** * tries to close the app defined by this App instance * * @return this or null on failure */ public int close() { if (!isValid()) { return 0; } if (appPID > -1) { init(appPID); } else if (isImmediate) { init(); } int ret = _osUtil.close(makeAppEntry()); if (ret > -1) { Debug.action("App.close: %s", this.toStringShort()); appPID = -1; appWindow = ""; } else { Debug.error("App.close %s did not work", this); } return ret; } //</editor-fold> //<editor-fold defaultstate="collapsed" desc="focus"> /** * tries to identify a running app with name and if not running tries to open it and tries to make it the foreground * application bringing its topmost window to front * * @param appName name * @return the App instance or null on failure */ public static App focus(String appName) { return focus(appName, 0); } /** * tries to identify a running app with name and if not running tries to open it and tries to make it the foreground * application bringing its window with the given number to front * * @param appName name * @param num window * @return the App instance or null on failure */ public static App focus(String appName, int num) { return (new App("+" + appName)).focus(num); } /** * tries to make it the foreground application bringing its topmost window to front * * @return the App instance or null on failure */ public App focus() { if (appPID > -1) { init(appPID); } return focus(0); } /** * tries to make it the foreground application bringing its window with the given number to front * * @param num window * @return the App instance or null on failure */ public App focus(int num) { if (!isValid()) { if (!appWindow.startsWith("!")) { return this; } } if (isImmediate) { appPID = _osUtil.switchto(appNameGiven, num); } else { init(_osUtil.switchto(makeAppEntry(), num)); } if (appPID < 0) { Debug.error("App.focus failed: " + (num > 0 ? " #" + num : "") + " " + this.toString()); return null; } else { Debug.action("App.focus: " + (num > 0 ? " #" + num : "") + " " + this.toStringShort()); if (appPID < 1) { init(); } } return this; } //</editor-fold> //<editor-fold defaultstate="collapsed" desc="window"> /** * evaluates the region currently occupied by the topmost window of this App instance. The region might not be fully * visible, not visible at all or invalid with respect to the current monitor configuration (outside any screen) * * @return the region */ public Region window() { if (appPID != 0) { return asRegion(_osUtil.getWindow(appPID)); } return asRegion(_osUtil.getWindow(appNameGiven)); } /** * evaluates the region currently occupied by the window with the given number of this App instance. The region might * not be fully visible, not visible at all or invalid with respect to the current monitor configuration (outside any * screen) * * @param winNum window * @return the region */ public Region window(int winNum) { if (appPID != 0) { return asRegion(_osUtil.getWindow(appPID, winNum)); } return asRegion(_osUtil.getWindow(appNameGiven, winNum)); } /** * evaluates the region currently occupied by the systemwide frontmost window (usually the one that has focus for * mouse and keyboard actions) * * @return the region */ public static Region focusedWindow() { return asRegion(_osUtil.getFocusedWindow()); } //</editor-fold> //<editor-fold defaultstate="collapsed" desc="run"> public static int lastRunReturnCode = -1; public static String lastRunStdout = ""; public static String lastRunStderr = ""; public static String lastRunResult = ""; /** * the given text is parsed into a String[] suitable for issuing a Runtime.getRuntime().exec(args). quoting is * preserved/obeyed. the first item must be an executable valid for the running system.<br> * After completion, the following information is available: <br> * App.lastRunResult: a string containing the complete result according to the docs of the run() command<br> * App.lastRunStdout: a string containing only the output lines that went to stdout<br> * App.lastRunStderr: a string containing only the output lines that went to stderr<br> * App.lastRunReturnCode: the value, that is returnd as returncode * * @param cmd the command to run starting with an executable item * @return the final returncode of the command execution */ public static int run(String cmd) { lastRunResult = runTime.runcmd(cmd); String NL = runTime.runningWindows ? "\r\n" : "\n"; String[] res = lastRunResult.split(NL); try { lastRunReturnCode = Integer.parseInt(res[0].trim()); } catch (Exception ex) { } lastRunStdout = ""; lastRunStderr = ""; boolean isError = false; for (int n = 1; n < res.length; n++) { if (isError) { lastRunStderr += res[n] + NL; continue; } if (RunTime.runCmdError.equals(res[n])) { isError = true; continue; } lastRunStdout += res[n] + NL; } return lastRunReturnCode; } //</editor-fold> //<editor-fold defaultstate="collapsed" desc="clipboard"> /** * evaluates the current textual content of the system clipboard * * @return the textual content or empty string if not possible */ public static String getClipboard() { Transferable content = null; try { content = Clipboard.getSystemClipboard().getContents(null); } catch (Exception ex) { Debug.error("Env.getClipboard: clipboard not available:\n%s", ex.getMessage()); } if (content != null) { try { if (content.isDataFlavorSupported(DataFlavor.stringFlavor)) { return (String) content.getTransferData(DataFlavor.stringFlavor); } } catch (UnsupportedFlavorException ex) { Debug.error("Env.getClipboard: UnsupportedFlavorException: " + content); } catch (IOException ex) { Debug.error("Env.getClipboard: IOException:\n%s", ex.getMessage()); } } return ""; } /** * sets the current textual content of the system clipboard to the given text * * @param text text */ public static void setClipboard(String text) { Clipboard.putText(Clipboard.PLAIN, Clipboard.UTF8, Clipboard.CHAR_BUFFER, text); } private static class Clipboard { public static final TextType HTML = new TextType("text/html"); public static final TextType PLAIN = new TextType("text/plain"); public static final Charset UTF8 = new Charset("UTF-8"); public static final Charset UTF16 = new Charset("UTF-16"); public static final Charset UNICODE = new Charset("unicode"); public static final Charset US_ASCII = new Charset("US-ASCII"); public static final TransferType READER = new TransferType(Reader.class); public static final TransferType INPUT_STREAM = new TransferType(InputStream.class); public static final TransferType CHAR_BUFFER = new TransferType(CharBuffer.class); public static final TransferType BYTE_BUFFER = new TransferType(ByteBuffer.class); private Clipboard() { } /** * Dumps a given text (either String or StringBuffer) into the Clipboard, with a default MIME type */ public static void putText(CharSequence data) { StringSelection copy = new StringSelection(data.toString()); getSystemClipboard().setContents(copy, copy); } /** * Dumps a given text (either String or StringBuffer) into the Clipboard with a specified MIME type */ public static void putText(TextType type, Charset charset, TransferType transferType, CharSequence data) { String mimeType = type + "; charset=" + charset + "; class=" + transferType; TextTransferable transferable = new TextTransferable(mimeType, data.toString()); getSystemClipboard().setContents(transferable, transferable); } public static java.awt.datatransfer.Clipboard getSystemClipboard() { return Toolkit.getDefaultToolkit().getSystemClipboard(); } private static class TextTransferable implements Transferable, ClipboardOwner { private String data; private DataFlavor flavor; public TextTransferable(String mimeType, String data) { flavor = new DataFlavor(mimeType, "Text"); this.data = data; } @Override public DataFlavor[] getTransferDataFlavors() { return new DataFlavor[] { flavor, DataFlavor.stringFlavor }; } @Override public boolean isDataFlavorSupported(DataFlavor flavor) { boolean b = this.flavor.getPrimaryType().equals(flavor.getPrimaryType()); return b || flavor.equals(DataFlavor.stringFlavor); } @Override public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException { if (flavor.isRepresentationClassInputStream()) { return new StringReader(data); } else if (flavor.isRepresentationClassReader()) { return new StringReader(data); } else if (flavor.isRepresentationClassCharBuffer()) { return CharBuffer.wrap(data); } else if (flavor.isRepresentationClassByteBuffer()) { return ByteBuffer.wrap(data.getBytes()); } else if (flavor.equals(DataFlavor.stringFlavor)) { return data; } throw new UnsupportedFlavorException(flavor); } @Override public void lostOwnership(java.awt.datatransfer.Clipboard clipboard, Transferable contents) { } } /** * Enumeration for the text type property in MIME types */ public static class TextType { private String type; private TextType(String type) { this.type = type; } @Override public String toString() { return type; } } /** * Enumeration for the charset property in MIME types (UTF-8, UTF-16, etc.) */ public static class Charset { private String name; private Charset(String name) { this.name = name; } @Override public String toString() { return name; } } /** * Enumeration for the transferScriptt type property in MIME types (InputStream, CharBuffer, etc.) */ public static class TransferType { private Class dataClass; private TransferType(Class streamClass) { this.dataClass = streamClass; } public Class getDataClass() { return dataClass; } @Override public String toString() { return dataClass.getName(); } } } //</editor-fold> }