org.sikuli.script.App Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sikulixapi Show documentation
Show all versions of sikulixapi Show documentation
... for visual testing and automation
/*
* 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;
/**
* App implements features to manage (open, switch to, close) applications. on the system we are running on and to
* access their assets like windows
*
* 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 appsWindows;
private static final Map 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();
appsWindows.put(Type.EDITOR, "Notepad");
appsWindows.put(Type.BROWSER, "Google Chrome");
appsWindows.put(Type.VIEWER, "");
appsMac = new HashMap();
appsMac.put(Type.EDITOR, "TextEdit");
appsMac.put(Type.BROWSER, "Safari");
appsMac.put(Type.VIEWER, "Preview");
}
// //
// 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;
// }
// //
//
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) {
}
}
//
//
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;
}
//
//
/**
* 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);
}
}
//
//
public static void getApps(String name) {
Map 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);
}
//
//
/**
* 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
* 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
* and waits until app is running
*
* @param waitTime max waittime until running
* @return this or null on failure
*/
public App open(int waitTime) {
return openAndWait(waitTime);
}
private 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;
}
//
//
/**
* 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;
}
//
//
/**
* 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;
}
//
//
/**
* 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());
}
//
//
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.
* After completion, the following information is available:
* App.lastRunResult: a string containing the complete result according to the docs of the run() command
* App.lastRunStdout: a string containing only the output lines that went to stdout
* App.lastRunStderr: a string containing only the output lines that went to stderr
* 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;
}
//
//
/**
* 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();
}
}
}
//
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy