How can I set the focus (e.g. cmd+tab) of an arbitrary application (Java or not) from a Java program, on OSX?
Looking for an answer to this question, I came across this question, but it doesn't really help for OSX.
EDIT: one possibiltiy seems to be to use something like Quicksilver, and a Robot to send it keypresses with modifiers. I'd prefer something more portable, though, that requires less setup to make changes after it's compiled....
You should be able to reactivate an already running app using the open command that comes with OS X:
Runtime.exec("open /path/to/Whichever.app");
(Or some equivalent overload of that function.) This will also open an app if it's not running yet.
Chuck's answer tipped me off to osascript, so I decided to give it a shot straight from the command line. Managed to get it working with Runtime.exec(), osascript, and AppleScript.
Java launches an AppleScript and passes it the application name, using osascript from the command line, via Runtime.exec():
try {
List<String> shellCommandList = new ArrayList<String>();
shellCommandList.add("osascript");
shellCommandList.add("activateApplication.scpt");
shellCommandList.add(appName);
String[] shellCommand = (String[])shellCommandList.toArray(new String[0]);
Process p = Runtime.getRuntime().exec(shellCommand);
// if desired, pipe out the script's output
BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
String cmdOutStr = "";
while ((cmdOutStr = in.readLine()) != null) {
System.out.println(cmdOutStr);
}
// if desired, check the script's exit value
int exitValue = p.waitFor();
if (exitValue != 0) {
// TODO: error dialog
System.err.println("Invalid application name: "+ appName);
}
} catch (Exception e) {
e.printStackTrace();
}
And the AppleScript uses a run handler to capture the incoming argument:
on run (arguments)
set appName to (item 1 of arguments)
tell application appName to activate
return 0
end run
You can use the javax.script API to run AppleScripts. So you can write a script along the lines of "tell application "WhateverApp" to activate", filling in your arbitrary application for WhateverApp, and it should do what you want.
Related
So I have been trying to find a solution to this problem for quite a while now but haven't been able to even get an error message. I am trying to toggle the dark mode on MacOS with this command: /usr/local/bin/dark-mode on
It works if I execute it with the standard terminal, but executing the code below doesn't do anything. The log file is empty. To make sure my code is correct I used a different command (commented out) and this one gave me the correct output to whoami and returned my current user.
I thought this may have to do with application security on MacOS? I have no idea where to start though.
private void switchDark() {
try {
//Activate the dark mode on MacOS
String command = new String[] { "/usr/local/bin/dark-mode", "on" };
//String command = new String[] { "whoami" }; //This works and gives me the current user
ProcessBuilder pb =
new ProcessBuilder(command[0], command[1]);
File log = new File("LOG.txt");
pb.redirectErrorStream(true);
pb.redirectOutput(Redirect.appendTo(log));
Process proc = pb.start();
proc.waitFor();
if(proc.exitValue() != 0) {
throw new IllegalThreadStateException();
}
} catch (IOException | InterruptedException | IllegalThreadStateException ex) {
System.out.println("Setting dark mode failed!");
if(debug)System.out.println(ex.getMessage());
}
}
I looked at the code to see what that "dark-mode" command does. (https://github.com/sindresorhus/dark-mode - thanks #MadProgrammer)
I'm no Swift or MacOS expert, but that command's implementation doesn't look like it tests if the AppleScript command succeeded. Furthermore, there doesn't seem to be anything to set non-zero exit status in the event of a failure.
So my conclusion is that the underlying AppleScript command is not working ... for reasons that are not reported ... and that information is not passed back to Java via the exit status. There is nothing you can do about it at the Java level.
My guess is that the "dark mode" request is actually failing for permissions-related reasons.
I have been working around a Java test framework and ended up with testing an android application which does a lot of toast messages.
The problem is the framework doesn't use anything other than ADB and I can't use any extra libs.
so Is there any way to get the toasted message from an application via adb?
I wouldn't mind writing hundreds of lines for this alone..
You can check toast messages through events with adb shell uiautomator events from the command line. Within your java test framework, however, you can create a method for code reuse that runs this command and parses it for whatever string you want to compare it to with a try block as such:
try {
Process process = Runtime.getRuntime().exec("uiautomator events");
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
StringBuilder log=new StringBuilder();
String toastMessage;
while ((toastMessage = bufferedReader.readLine()) != null) {
log.append(toastMessage).append("\n");
if (toastMessage.contains(myToastStringVar)) {
Log.i("ToastMessageMatcher", "" // <-- your info message);
} else {
Log.e("ToastMessageMatcher", "" // <-- your error message);
}
}
bufferedReader.close();
process.waitFor();
System.out.println(log.toString());
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
This can likewise be directly in your derivatives too.
Using AndroidViewClient/culebra you can wait for and retrieve the text in a Toast using something like this
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
from com.dtmilano.android.viewclient import ViewClient
vc = ViewClient(*ViewClient.connectToDeviceOrExit(), useuiautomatorhelper=True)
print(vc.uiAutomatorHelper.device.wait_for_new_toast(timeout=10000))
however in this case is not using adb as the backend (which is the default for AndroidViewClient/culebra) but CulebraTester2-public, an alternate backend with more capabilities (precisely this is one of those). You get this backend using useuiautomatorhelper=True while creating the ViewClient object.
You can take a look at the complete API here, so if you want to use java you would be able.
This is my first question on stackoverflow so I'll try to keep it concise and relevant.
I'm currently creating a Java program that is attempting to call an external program located on the system, in order to do this however I am required to call a shell script that sets up links to the relevant libraries to ensure that the system is linked to these before the external program can be executed.
The issue at hand is that I cannot invoke the shell script through Java, I've researched high and low and am aware that of alternative ways such as the use of the ProcessBuilder class. Unfortunately I'm quite new to the world of trying to invoke command line statements through Java and so I'm stuck for answers.
An example of the code I am using can be found below:
private void analyse_JButtonActionPerformed(java.awt.event.ActionEvent evt) {
// Get project path for copying of Fortran program to folder and execution
String projectPath = Newproject_GUI.getProjectPath();
String sourcePath [] = {"/bin/sh ", "-c ","source ~/set_env_WRF_gnu.sh"} ;
Runtime fortranAnalyser = Runtime.getRuntime();
try {
Process p = fortranAnalyser.exec("cp main.exe " + projectPath);
Process k = fortranAnalyser.exec(sourcePath);
BufferedReader is = new BufferedReader(new InputStreamReader(k.getInputStream()));
String line;
while ((line = is.readLine()) != null) {
System.out.println(line);
}
} catch (IOException ex) {
Logger.getLogger(Analyser_GUI.class.getName()).log(Level.SEVERE, null, ex);
}
}
Process p works fine and does indeed copy main.exe to the intended directory when the method is called. Process k however does not and this is where the issue is.
Thanks in advance.
The issue is "source" is internal command of BASH (you are using "sh" but that is just BASH in the simplified mode). So what you do is:
you spawn new process "sh" and source something there (setting some VARIABLES I guess)
the process ends and all VARIABLES are lost
you spawn another process, but VARIABLES are already gone
I am not sure if you use those variables later on, but according to the script name it is probably setting some. Don't do that like this.
By the way if you only want to execute script in bash, you don't need to source it. To get it's side effects, just execute it with:
String sourcePath [] = {"/bin/sh ", "/home/XYZ/set_env_WRF_gnu.sh"} ;
Please note you cannot use ~ in this case, use Java to get your home dir.
My aim with this project was to have a remote command prompt feel with Java. Using TCP/IP sockets, I was aiming to run a command prompt process on one computer, and virtually transmit all control to the other side. I immediately stumbled over Runtime.getRuntime().exec() and Process objects, etc. I've solved my problem about halfway. With my remote command prompt, I can run a single command, gather the output, and send it back to the other side. The problem is, I can only seem to run one command per command prompt instance. This won't do (with situations where I need to change directory and THEN run a command, etc). I've stripped all socket/networking programming from this situation to show you (and to create an easier testing environment for me).
import java.io.*;
public class testingProgram {
public static void main(String[] args) {
Runtime rt = Runtime.getRuntime();
StringBuilder inputMessage = new StringBuilder();
String resultData;
try {
Process pr = rt.exec("cmd.exe /c net user");
BufferedReader processInput = new BufferedReader(new InputStreamReader(pr.getInputStream()));
BufferedReader errorProcessInput = new BufferedReader(new InputStreamReader(pr.getErrorStream()));
PrintWriter processOut = new PrintWriter(pr.getOutputStream());
while( (resultData = processInput.readLine()) != null ) {
inputMessage.append(resultData + "\n");
}
resultData = inputMessage.toString();
System.out.print(resultData);
} catch(IOException e) {
} //catch(InterruptedException e) {
//}
}
}
I have a lot more, but this is where my problem is. I can customize the command "net user" with a simple variable and message from the socketstream, so that's not my problem. My problem is that I need to create an ongoing command prompt instance, retaining all redirections of the input/output. Basically, I would like to be able to send another command AFTER "net user".
I have gathered and redirected the output stream. I want to be able to do something like:
processOut.write("net user");
I want to be able to use this, have the command prompt run the command, and retain the output (whether it be from the errorStream OR the inputStream).
I just need some more direction on how to go about doing this.
You should look into multi threading. What you basically want is a thread which keeps running and maintaining the rt.
Like this:
String commandLine;
while ((commandLine = System.in.readline()) != 'q') {
Process pc = rt.exec(commandLine);
}
For further reference on multithreading:
http://download.oracle.com/javase/tutorial/essential/concurrency/procthread.html
You problem is that your program terminates after one call.
cheers
You're telling the command interpreter to terminate. Remove the /C after cmd.exe.
cmd /?
Starts a new instance of the Windows command interpreter
CMD [/A | /U] [/Q] [/D] [/E:ON | /E:OFF] [/F:ON | /F:OFF] [/V:ON | /V:OFF]
[[/S] [/C | /K] string]
/C Carries out the command specified by string and then terminates
/K Carries out the command specified by string but remains
...
I tried to run a shell script from java code, but I am facing problem. The script is in batchstart.sh file -
#!/bin/ksh
export DISPLAY=:0.0
Now the script is run with a dot on the command line -- . batchstart.sh
How do I run it from java? My java code is below. It throws the following exception -
java.io.IOException: .: not found
at java.lang.UNIXProcess.forkAndExec(Native Method)
at java.lang.UNIXProcess.<init>(UNIXProcess.java:102)
at java.lang.ProcessImpl.start(ProcessImpl.java:65)
at java.lang.ProcessBuilder.start(ProcessBuilder.java:451)
at java.lang.Runtime.exec(Runtime.java:591)
at java.lang.Runtime.exec(Runtime.java:429)
at SetDisplay.main(SetDisplay.java:12)
import java.io.*;
public class SetDisplay {
public static void main(String[] args) {
File wd = new File("/myhomedir/");
System.out.println("Working Directory: " +wd);
Process proc = null;
try {
proc = Runtime.getRuntime().exec(". batchstart.sh", null, wd);
} catch (Exception e) {
e.printStackTrace();
}
}
}
How do I make the shell script run ?
I tried the following code as well, but that too doesn't work.
File wd = new File("/bin");
System.out.println(wd);
Process proc = null;
try {
proc = Runtime.getRuntime().exec("/bin/bash", null, wd);
}
catch (IOException e) {
e.printStackTrace();
}
if (proc != null) {
BufferedReader in = new BufferedReader(new InputStreamReader(proc.getInputStream()));
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(proc.getOutputStream())), true);
out.println("cd /home/");
out.println(". batchstart.sh");
out.println("exit");
try {
String line;
while ((line = in.readLine()) != null) {
System.out.println(line);
}
proc.waitFor();
in.close();
out.close();
proc.destroy();
}
catch (Exception e) {
e.printStackTrace();
}
}
When run from the command line, using a dot at the start of a script indicates that the script should be run in the current environment, instead of spawning a new subshell and using a new copy of the current environment. This allows you to export a new value of an environment variable to be used by commands run later from the same interactive shell.
Obviously, this technique only works if you are running your batchstart.sh script from an actual shell. Java does not know how this mechanism works and so the dot means nothing to it. A script cannot modify the environment of the Java process it was called from.
If your goal is to change the value of the DISPLAY environment variable for other commands run by your Java process, consider using the ProcessBuilder class to specify a new environment for the child process. Java does not contain a built-in way to modify variables in its own environment.
The source command (".") is a shell built-in. You have to explicitly run /bin/ksh, passing your script name as the argument (followed by any script arguments).
You have a larger problem if you need to source the script. That usually means that environment changes happen in the context of the current shell, not a subshell.
This won't work with Java since Java's not a shell. You'll need to figure out how to change the environment with Java.
Of course, if I'm wrong and there's more to that script that just setting DISPLAY, it may work as suggested.
The method you're going to have to use depends on what you're trying to achieve(as in "Are you running other programs using exec() that rely on DISPLAY being set?" or "Does your Java program need DISPLAY to be set?").
If, as you state in your comment, it's only your Java program that needs DISPLAY set, just set it outside before your program runs. Create a cmd (or bash) file which sets the DISPLAY variable then calls the JRE to run your program.
#/bin/ksh
export DISPLAY-:0.0
/usr/bin/jre/java your_program blah.blah.blah
I would also modify your main() to check that it's set to something and exit gracefully if not:
if (System.getenv ("DISPLAY") == null)
// doesn't exist, exit gracefully.
The period "." is a shell built-in, and executes the script "in-place", analogous to #include in C/C++. Using "." outside of a shell-script has no meaning.
If you want to run the script from Java, you have to execute the script interpreter (/bin/ksh):
Runtime.getRuntime().exec("/bin/ksh batchstart.sh", ...)
but note that this is not semantically equivalent, since you're executing batchstart.sh as a sub-process instead of sourcing it.