/* * Copyright (c) 2010-2016, Sikuli.org, sikulix.com * Released under the MIT License. * */ package org.sikuli.natives; import org.apache.commons.exec.CommandLine; import org.apache.commons.exec.DefaultExecutor; import org.apache.commons.exec.ExecuteException; import org.apache.commons.exec.PumpStreamHandler; import org.sikuli.basics.Debug; import org.sikuli.script.App; import java.awt.*; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.Arrays; import java.util.List; import java.util.Map; public class LinuxUtil implements OSUtil { private static boolean wmctrlAvail = true; private static boolean xdoToolAvail = true; @Override public void checkFeatureAvailability() { List<CommandLine> commands = Arrays.asList( CommandLine.parse("wmctrl -m"), CommandLine.parse("xdotool version") ); for (CommandLine cmd : commands) { String executable = cmd.toStrings()[0]; try { DefaultExecutor executor = new DefaultExecutor(); // other return values throw exception executor.setExitValue(0); //suppress system output executor.setStreamHandler(new PumpStreamHandler(null)); executor.execute(cmd); } catch (ExecuteException e) { // it ran, but exited with non-zero status -- accept Debug.info("App: command %s ran, but failed: `%s'. Hoping for the best", executable, e.toString()); } catch (Exception e) { if (executable.equals("wmctrl")) { wmctrlAvail = false; } if (executable.equals("xdotool")) { xdoToolAvail = false; } Debug.error("App: command %s is not executable, the App features will not work", executable); } } } private boolean isAvailable(boolean module, String cmd, String feature) { if (module) { return true; } Debug.error("%s: feature %s: not available or not working", cmd, feature); return false; } @Override public App.AppEntry getApp(int appPID, String appName) { return new App.AppEntry(appName, "" + appPID, "", "", ""); } @Override public int isRunning(App.AppEntry app) { return -1; } @Override public int open(String appName) { try { String cmd[] = {"sh", "-c", "(" + appName + ") &\necho -n $!"}; Process p = Runtime.getRuntime().exec(cmd); InputStream in = p.getInputStream(); byte pidBytes[] = new byte[64]; int len = in.read(pidBytes); String pidStr = new String(pidBytes, 0, len); int pid = Integer.parseInt(pidStr); p.waitFor(); return pid; //return p.exitValue(); } catch (Exception e) { System.out.println("[error] openApp:\n" + e.getMessage()); return 0; } } @Override public int open(App.AppEntry app) { return open(app.execName); } @Override public int switchto(String appName, int winNum) { int windowPID = findWindowPID(appName, winNum); if (windowPID > 1) { return switchto(windowPID, winNum); } System.err.println("[error] switchApp: could not identify process with search name '" + appName + "'"); return -1; } @Override public int switchto(String appName) { return switchto(appName, 0); } @Override public int switchto(App.AppEntry app, int num) { if (app.pid > 0) { return switchto(app.pid, num); } return switchto(app.execName, num); } @Override public int close(String appName) { try { //on the success exit value = 0 -> so no exception will be thrown CommandExecutorResult result1 = CommandExecutorHelper.execute("pidof " + appName, 0); String pid = result1.getStandardOutput(); if (pid == null || pid.isEmpty()) { throw new CommandExecutorException("No app could be found with Name '" + appName + "'"); } //use kill incase that killall could maybe not work in all environments return CommandExecutorHelper.execute("kill " + pid, 0).getExitValue(); } catch (Exception e) { //try to search for the appName Integer windowPID = findWindowPID(appName, 1); if (windowPID > 1) { try { return CommandExecutorHelper.execute("kill " + windowPID.toString(), 0).getExitValue(); } catch (Exception e1) { e.addSuppressed(e1); } } System.out.println("[error] closeApp:\n" + e.getMessage()); return -1; } } @Override public int close(App.AppEntry app) { if (app.pid > 0) { return close(app.pid); } return close(app.execName); } @Override public Map<Integer, String[]> getApps(String name) { return null; } @Override public Rectangle getFocusedWindow() { if (!isAvailable(xdoToolAvail, "getFocusedWindow", "xdoTool")) { return null; } String cmd[] = {"xdotool", "getactivewindow"}; try { Process p = Runtime.getRuntime().exec(cmd); InputStream in = p.getInputStream(); BufferedReader bufin = new BufferedReader(new InputStreamReader(in)); String str = bufin.readLine(); long id = Integer.parseInt(str); String hexid = String.format("0x%08x", id); return findRegion(hexid, 0, SearchType.WINDOW_ID); } catch (IOException e) { System.out.println("[error] getFocusedWindow:\n" + e.getMessage()); return null; } } @Override public Rectangle getWindow(String appName) { return getWindow(appName, 0); } private Rectangle findRegion(String appName, int winNum, SearchType type) { String[] winLine = findWindow(appName, winNum, type); if (winLine != null && winLine.length >= 7) { int x = new Integer(winLine[3]); int y = Integer.parseInt(winLine[4]); int w = Integer.parseInt(winLine[5]); int h = Integer.parseInt(winLine[6]); return new Rectangle(x, y, w, h); } return null; } private String[] findWindow(String appName, int winNum, SearchType type) { String[] found = {}; int numFound = 0; try { CommandExecutorResult result = CommandExecutorHelper.execute("wmctrl -lpGx", 0); int slash = appName.lastIndexOf("/"); if (slash >= 0) { // remove path: /usr/bin/.... appName = appName.substring(slash + 1); } if (type == SearchType.APP_NAME) { appName = appName.toLowerCase(); } String[] lines = result.getStandardOutput().split("\\n"); for (String str : lines) { //Debug.log("read: " + str); String winLine[] = str.split("\\s+"); boolean ok = false; if (type == SearchType.WINDOW_ID) { if (appName.equals(winLine[0])) { ok = true; } } else if (type == SearchType.PID) { if (appName.equals(winLine[2])) { ok = true; } } else if (type == SearchType.APP_NAME) { String winLineName = winLine[9].toLowerCase(); if (appName.equals(winLineName)) { ok = true; } if (!ok && winLine[7].toLowerCase().contains(appName)) { ok = true; } } if (ok) { if (numFound >= winNum) { //Debug.log("Found window" + winLine); found = winLine; break; } numFound++; } } } catch (Exception e) { System.out.println("[error] findWindow:\n" + e.getMessage()); return null; } return found; } /** * Returns a PID of the givenAppname and the winNumber * * @param appName * @param winNum * @return the PID or -1 on errors */ protected int findWindowPID(String appName, int winNum) { String[] window = findWindow(appName, winNum, SearchType.APP_NAME); if (window != null && window.length > 1) { return Integer.parseInt(window[2]); } return -1; } @Override public Rectangle getWindow(String appName, int winNum) { return findRegion(appName, winNum, SearchType.APP_NAME); } @Override public Rectangle getWindow(int pid) { return getWindow(pid, 0); } @Override public Rectangle getWindow(int pid, int winNum) { return findRegion("" + pid, winNum, SearchType.PID); } @Override public int close(int pid) { if (!isAvailable(wmctrlAvail, "closeApp", "wmctrl")) { return -1; } String winLine[] = findWindow("" + pid, 0, SearchType.PID); if (winLine == null) { return -1; } String cmd[] = {"wmctrl", "-ic", winLine[0]}; try { Process p = Runtime.getRuntime().exec(cmd); p.waitFor(); return p.exitValue(); } catch (Exception e) { System.out.println("[error] closeApp:\n" + e.getMessage()); return -1; } } @Override public int switchto(int pid, int num) { if (!isAvailable(wmctrlAvail, "switchApp", "wmctrl")) { return -1; } String winLine[] = findWindow("" + pid, num, SearchType.PID); if (winLine == null || winLine.length < 1) { System.err.println("[error] switchApp: window of PID '" + pid + "' couldn't be found!"); return -1; } try { // execute wmctrl with hex, e.g. 'wmctrl -ia 0x00000' CommandExecutorHelper.execute("wmctrl -ia " + winLine[0], 0); //on the success exit value = 0 -> so no exception will be thrown return pid; } catch (Exception e) { e.printStackTrace(); System.err.println("[error] switchApp:\n" + e.getMessage()); return -1; } } @Override public void bringWindowToFront(Window win, boolean ignoreMouse) { } private enum SearchType { APP_NAME, WINDOW_ID, PID } }