There exist ways to set environment variables for the currently running VM, for example like this:
private static void setEnv(Map<String, String> newEnv) throws Exception {
Map<String, String> env = System.getenv();
Class<?> cl = env.getClass();
Field field = cl.getDeclaredField("m");
field.setAccessible(true);
@SuppressWarnings("unchecked")
Map<String, String> envMap = (Map<String, String>) field.get(env);
envMap.putAll(newEnv);
}
(the idea is taken from the answers at How do I set environment variables from Java?)
Yet in my case I need the env vars to affect the libraries that are executed outside the VM, so this method does not solve my problem.
After thinking a little I have realized that I want to set environment for the parent process of the JVM, so I need first to set the required variables and then recursively run another JVM instance that would execute my app -- then the variables would affect the libraries even though their code is executed outside the VM.
So the logic should be as the following:
if (required vars are absent) {
start a process that {
set required vars;
run another instance of the JVM with the application inside;
}
exit;
}
// here the vars already set
do whatever we need in the proper environment
As it comes to Java, the code may look like this:
public class SecondVM {
public static void main(String[] args) {
if ( System.getenv("SWT_GTK3") == null
|| System.getenv("LIBOVERLAY_SCROLLBAR") == null )
{
URL classResource = SecondVM.class.getResource("SecondVM.class");
boolean fromJar = classResource.getProtocol().equals("rsrc");
String exePath = ClassLoader.getSystemClassLoader().getResource(".").getPath();
exePath = new File(exePath).getAbsolutePath().replaceFirst("\\.$", "").replaceFirst("bin$", "");
if (!exePath.endsWith(System.getProperty("file.separator")))
exePath += System.getProperty("file.separator");
String[] script = {
"/bin/bash", "-c",
"export SWT_GTK3=0; "
+ "export LIBOVERLAY_SCROLLBAR=0; "
+ (fromJar? // TODO: Put the proper paths, packages and class names here
"java -jar " + exePath + "SecondVM.jar" : // if runs from jar
"java -cp ./bin/:../ExtLibs/swt_linux64/swt.jar " // if runs from under Eclipse or somewhat alike
+ "com.m_v.test.SecondVM")
};
try {
Process p = new ProcessBuilder(script).start();
// When jar is run from a bash script, it kills the second VM when exits.
// Let it has some time to take a breath
p.waitFor(12, TimeUnit.HOURS);
} catch (Exception e) { e.printStackTrace(); }
System.exit(0);
}
// Now the env vars are OK. We can use SWT with normal scrollbars
Display display = Display.getDefault();
// .... do watever we need
}
}
In case of running the jar from a shell script, we have to wait for the child process to finish, before exiting the original process, so this solution leads to an overhead of running two instances of JVM simultaneously. If there is no need to provide a possibility to run it from a script, the p.waitFor(12, TimeUnit.HOURS);
may be replaced with p.waitFor(12, TimeUnit.MILLISECONDS);
or, perhaps, removed at all (I have not tested without it), so we can have a single instance of JVM, like with a usual Java program.
A working snippet with a text
widget and a scrollbar
is at http://ideone.com/eRjePQ