diff --git a/.gitignore b/.gitignore
index 3034361..7d5b006 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,3 +17,12 @@ target/
# OS X
.DS_Store
+
+# jenv
+
+
+# jenv
+.java-version
+
+# log files
+*.log
diff --git a/README.md b/README.md
index 3e7072c..d9347ea 100644
--- a/README.md
+++ b/README.md
@@ -132,6 +132,8 @@ This is because Java only allows to set the toolkit once, and it cannot be unloa
The `add-exports` and `add-opens` jvm args are required with Java 17, since these are internal packages that aren't exported, these can't be added to a `module-info.java` file.
+With Java 18+, you may also want to add a argument to suppress warnings about an agent (ByteBuddyAgent) being loaded: `-XX:+EnableDynamicAgentLoading` .
+
You can change the resolution of the virtual screen by setting the `cacio.managed.screensize` system property.
For example:
diff --git a/cacio-shared/pom.xml b/cacio-shared/pom.xml
index 1db89e5..bd69d1f 100644
--- a/cacio-shared/pom.xml
+++ b/cacio-shared/pom.xml
@@ -21,8 +21,8 @@
maven-compiler-plugin
3.8.1
- 17
- 17
+ ${cacio.java.version}
+ ${cacio.java.version}
-XDignore.symbol.file=true
--add-exports=java.desktop/java.awt.peer=ALL-UNNAMED
diff --git a/cacio-tta/pom.xml b/cacio-tta/pom.xml
index d1067c2..3ecf9d7 100644
--- a/cacio-tta/pom.xml
+++ b/cacio-tta/pom.xml
@@ -45,6 +45,17 @@
jide-oss
3.6.18
+
+
+ net.bytebuddy
+ byte-buddy
+ 1.14.11
+
+
+ net.bytebuddy
+ byte-buddy-agent
+ 1.14.11
+
@@ -54,8 +65,8 @@
maven-compiler-plugin
3.8.1
- 17
- 17
+ ${cacio.java.version}
+ ${cacio.java.version}
-XDignore.symbol.file=true
--add-exports=java.desktop/java.awt.peer=ALL-UNNAMED
diff --git a/cacio-tta/src/main/java/com/github/caciocavallosilano/cacio/ctc/CTCGraphicsEnvironment.java b/cacio-tta/src/main/java/com/github/caciocavallosilano/cacio/ctc/CTCGraphicsEnvironment.java
index 24e8c8c..5cdfbf7 100644
--- a/cacio-tta/src/main/java/com/github/caciocavallosilano/cacio/ctc/CTCGraphicsEnvironment.java
+++ b/cacio-tta/src/main/java/com/github/caciocavallosilano/cacio/ctc/CTCGraphicsEnvironment.java
@@ -31,10 +31,14 @@
public class CTCGraphicsEnvironment extends SunGraphicsEnvironment {
+ private static final CTCGraphicsEnvironment INSTANCE = new CTCGraphicsEnvironment();
public CTCGraphicsEnvironment() {
SurfaceManagerFactory.setInstance(new CTCSurfaceManagerFactory());
}
+ public static CTCGraphicsEnvironment getInstance() {
+ return INSTANCE;
+ }
@Override
protected int getNumScreens() {
return 1;
diff --git a/cacio-tta/src/main/java/com/github/caciocavallosilano/cacio/ctc/CTCSurfaceManagerFactory.java b/cacio-tta/src/main/java/com/github/caciocavallosilano/cacio/ctc/CTCSurfaceManagerFactory.java
index b06387c..7bc20a4 100644
--- a/cacio-tta/src/main/java/com/github/caciocavallosilano/cacio/ctc/CTCSurfaceManagerFactory.java
+++ b/cacio-tta/src/main/java/com/github/caciocavallosilano/cacio/ctc/CTCSurfaceManagerFactory.java
@@ -28,7 +28,7 @@
import sun.awt.image.VolatileSurfaceManager;
import sun.java2d.SurfaceManagerFactory;
-class CTCSurfaceManagerFactory extends SurfaceManagerFactory {
+public class CTCSurfaceManagerFactory extends SurfaceManagerFactory {
@Override
public VolatileSurfaceManager createVolatileManager(SunVolatileImage image,
diff --git a/cacio-tta/src/main/java/com/github/caciocavallosilano/cacio/ctc/CTCVolatileSurfaceManager.java b/cacio-tta/src/main/java/com/github/caciocavallosilano/cacio/ctc/CTCVolatileSurfaceManager.java
index 236dc57..81e36bf 100644
--- a/cacio-tta/src/main/java/com/github/caciocavallosilano/cacio/ctc/CTCVolatileSurfaceManager.java
+++ b/cacio-tta/src/main/java/com/github/caciocavallosilano/cacio/ctc/CTCVolatileSurfaceManager.java
@@ -28,7 +28,7 @@
import sun.awt.image.VolatileSurfaceManager;
import sun.java2d.SurfaceData;
-class CTCVolatileSurfaceManager extends VolatileSurfaceManager {
+public class CTCVolatileSurfaceManager extends VolatileSurfaceManager {
protected CTCVolatileSurfaceManager(SunVolatileImage vImg, Object context) {
super(vImg, context);
diff --git a/cacio-tta/src/main/java/com/github/caciocavallosilano/cacio/ctc/junit/CTCInterceptor.java b/cacio-tta/src/main/java/com/github/caciocavallosilano/cacio/ctc/junit/CTCInterceptor.java
new file mode 100644
index 0000000..c2464a9
--- /dev/null
+++ b/cacio-tta/src/main/java/com/github/caciocavallosilano/cacio/ctc/junit/CTCInterceptor.java
@@ -0,0 +1,14 @@
+package com.github.caciocavallosilano.cacio.ctc.junit;
+
+import com.github.caciocavallosilano.cacio.ctc.CTCGraphicsEnvironment;
+import net.bytebuddy.implementation.bind.annotation.*;
+
+import java.awt.*;
+
+
+public class CTCInterceptor {
+ @RuntimeType
+ public static GraphicsEnvironment intercept() {
+ return CTCGraphicsEnvironment.getInstance();
+ }
+}
diff --git a/cacio-tta/src/main/java/com/github/caciocavallosilano/cacio/ctc/junit/CacioExtension.java b/cacio-tta/src/main/java/com/github/caciocavallosilano/cacio/ctc/junit/CacioExtension.java
index 8974a41..ad5c44c 100644
--- a/cacio-tta/src/main/java/com/github/caciocavallosilano/cacio/ctc/junit/CacioExtension.java
+++ b/cacio-tta/src/main/java/com/github/caciocavallosilano/cacio/ctc/junit/CacioExtension.java
@@ -24,8 +24,19 @@
*/
package com.github.caciocavallosilano.cacio.ctc.junit;
-import com.github.caciocavallosilano.cacio.ctc.CTCGraphicsEnvironment;
-import com.github.caciocavallosilano.cacio.ctc.CTCToolkit;
+import com.github.caciocavallosilano.cacio.ctc.*;
+import com.github.caciocavallosilano.cacio.peer.PlatformWindowFactory;
+import com.github.caciocavallosilano.cacio.peer.managed.FullScreenWindowFactory;
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.dynamic.loading.ClassInjector;
+import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
+import net.bytebuddy.implementation.MethodDelegation;
+import net.bytebuddy.implementation.bind.annotation.*;
+import net.bytebuddy.matcher.ElementMatchers;
+import net.bytebuddy.pool.TypePool;
+import net.bytebuddy.agent.ByteBuddyAgent;
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
import org.junit.jupiter.api.extension.ExecutionCondition;
import org.junit.jupiter.api.extension.ExtensionContext;
@@ -33,11 +44,17 @@
import javax.swing.plaf.metal.MetalLookAndFeel;
import java.awt.*;
+import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
+import java.lang.reflect.Method;
+import java.util.Map;
+
+
+import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
+
public class CacioExtension implements ExecutionCondition {
// https://stackoverflow.com/a/56043252/1050369
@@ -45,6 +62,8 @@ public class CacioExtension implements ExecutionCondition {
static {
try {
+ ByteBuddyAgent.install();
+
var lookup = MethodHandles.privateLookupIn(Field.class, MethodHandles.lookup());
MODIFIERS = lookup.findVarHandle(Field.class, "modifiers", int.class);
} catch (IllegalAccessException | NoSuchFieldException ex) {
@@ -54,31 +73,23 @@ public class CacioExtension implements ExecutionCondition {
static {
try {
+ injectCTCGraphicsEnvironment();
+
Field toolkit = Toolkit.class.getDeclaredField("toolkit");
toolkit.setAccessible(true);
toolkit.set(null, new CTCToolkit());
Field defaultHeadlessField = java.awt.GraphicsEnvironment.class.getDeclaredField("defaultHeadless");
defaultHeadlessField.setAccessible(true);
- defaultHeadlessField.set(null, Boolean.TRUE);
+ defaultHeadlessField.set(null, Boolean.FALSE);
Field headlessField = java.awt.GraphicsEnvironment.class.getDeclaredField("headless");
headlessField.setAccessible(true);
- headlessField.set(null, Boolean.TRUE);
-
- Class> geCls = Class.forName("java.awt.GraphicsEnvironment$LocalGE");
- Field ge = geCls.getDeclaredField("INSTANCE");
- ge.setAccessible(true);
- defaultHeadlessField.set(null, Boolean.FALSE);
headlessField.set(null, Boolean.FALSE);
- makeNonFinal(ge);
-
Class> smfCls = Class.forName("sun.java2d.SurfaceManagerFactory");
Field smf = smfCls.getDeclaredField("instance");
smf.setAccessible(true);
smf.set(null, null);
-
- ge.set(null, new CTCGraphicsEnvironment());
} catch (Exception e) {
e.printStackTrace();
}
@@ -86,10 +97,63 @@ public class CacioExtension implements ExecutionCondition {
System.setProperty("swing.defaultlaf", MetalLookAndFeel.class.getName());
}
- public static void makeNonFinal(Field field) {
- int mods = field.getModifiers();
- if (Modifier.isFinal(mods)) {
- MODIFIERS.set(field, mods & ~Modifier.FINAL);
+ public static void injectCTCGraphicsEnvironment() throws ClassNotFoundException, IOException {
+ /*
+ * ByteBuddy is used to intercept the methods that return the graphics environment in use
+ * (java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment() and
+ * sun.awt.PlatformGraphicsInfo.createGE())
+ *
+ * Since java.awt.GraphicsEnvironment is loaded by the bootstrap class loader,
+ * all classes used by CTCGraphicsEnvironment also need to be available to the bootstrap class loader,
+ * as that class loader also loads the CTCInterceptor class, which will instantiate CTCGraphicsEnvironment.
+ */
+ injectClassIntoBootstrapClassLoader(
+ CTCInterceptor.class,
+ CTCGraphicsEnvironment.class,
+ CTCSurfaceManagerFactory.class,
+ CTCGraphicsConfiguration.class,
+ PlatformWindowFactory.class,
+ FullScreenWindowFactory.class,
+ CTCGraphicsDevice.class,
+ CTCVolatileSurfaceManager.class);
+
+ ByteBuddy byteBuddy = new ByteBuddy();
+
+ byteBuddy
+ .redefine(
+ TypePool.Default.ofSystemLoader().describe("java.awt.GraphicsEnvironment").resolve(),
+ ClassFileLocator.ForClassLoader.ofSystemLoader())
+ .method(ElementMatchers.named("getLocalGraphicsEnvironment"))
+ .intercept(
+ MethodDelegation.to(CTCInterceptor.class))
+ .make()
+ .load(
+ Object.class.getClassLoader(),
+ ClassReloadingStrategy.fromInstalledAgent());
+
+ TypeDescription platformGraphicInfosType;
+ platformGraphicInfosType = TypePool.Default.ofSystemLoader().describe("sun.awt.PlatformGraphicsInfo").resolve();
+ ClassFileLocator locator = ClassFileLocator.ForClassLoader.ofSystemLoader();
+
+ byteBuddy
+ .redefine(
+ platformGraphicInfosType,
+ locator)
+ .method(
+ nameStartsWith("createGE"))
+ .intercept(
+ MethodDelegation.to(GraphicsEnvironmentInterceptor.class))
+ .make()
+ .load(
+ Thread.currentThread().getContextClassLoader(),
+ ClassReloadingStrategy.fromInstalledAgent());
+
+ }
+
+ public static class GraphicsEnvironmentInterceptor {
+ @RuntimeType
+ public static Object intercept(@Origin Method method, @AllArguments final Object[] args) throws Exception {
+ return CTCGraphicsEnvironment.getInstance();
}
}
@@ -100,4 +164,12 @@ public final ConditionEvaluationResult evaluateExecutionCondition(ExtensionConte
.map(annotation -> ConditionEvaluationResult.enabled("@GUITest is present"))
.orElse(ConditionEvaluationResult.enabled("@GUITest is not present"));
}
+
+ private static void injectClassIntoBootstrapClassLoader(Class... classes) throws IOException {
+ for (Class> clazz: classes) {
+ final byte[] buffer = clazz.getClassLoader().getResourceAsStream(clazz.getName().replace('.', '/').concat(".class")).readAllBytes();
+ ClassInjector.UsingUnsafe injector = new ClassInjector.UsingUnsafe(null);
+ injector.injectRaw(Map.of(clazz.getName(), buffer));
+ }
+ }
}
diff --git a/pom.xml b/pom.xml
index 6319fa8..5c65a0a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -40,6 +40,10 @@
cacio-tta
+
+ 17
+
+
ossrh
@@ -112,8 +116,8 @@
maven-compiler-plugin
3.8.1
- 17
- 17
+ ${cacio.java.version}
+ ${cacio.java.version}
-XDignore.symbol.file=true
@@ -130,6 +134,7 @@
false
+ -XX:+EnableDynamicAgentLoading
--add-exports=java.desktop/java.awt=ALL-UNNAMED
--add-exports=java.desktop/java.awt.peer=ALL-UNNAMED
--add-exports=java.desktop/sun.awt.image=ALL-UNNAMED