diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..93c8474
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,18 @@
+# IntelliJ
+*.iml
+.idea/
+
+# Eclipse
+.settings/
+.project
+.classpath
+.metadata
+
+# Common
+target/
+.DS_Store
+__MACOSX
+
+/webview*
+/libwebview*
+/WebView*
diff --git a/Example.java b/Example.java
new file mode 100644
index 0000000..8607400
--- /dev/null
+++ b/Example.java
@@ -0,0 +1,19 @@
+package dev.webview;
+
+public class Example {
+
+ public static void main(String[] args) {
+ Webview wv = new Webview(); // Can optionally be created with an AWT component to be painted on.
+
+ // Calling `await echo(1,2,3)` will return `[1,2,3]`
+ wv.bind("echo", (arguments) -> {
+ return arguments;
+ });
+
+ wv.loadURL("https://google.com");
+
+ // Run the webview event loop, the webview is fully disposed when this returns.
+ wv.run();
+ }
+
+}
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..abdb64d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,13 @@
+# Webview
+
+A (new!) Java port of the [webview project](https://github.com/webview/webview). It uses JNA under the hood to interface with the webview library.
+
+## How to use
+
+1) Include the libary in your project (see the [JitPack page](https://jitpack.io/#Casterlabs/webview)).
+2) Copy and run the example in `Example.java`.
+3) Profit!
+
+## TODO
+
+Build our own DLLs and whatnot, the current ones are copied from the C# port.
\ No newline at end of file
diff --git a/SwingExample.java b/SwingExample.java
new file mode 100644
index 0000000..8c084b5
--- /dev/null
+++ b/SwingExample.java
@@ -0,0 +1,47 @@
+package dev.webview;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import javax.swing.JFrame;
+
+public class SwingExample {
+
+ public static void main(String[] args) {
+ JFrame frame = new JFrame();
+
+ frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+ frame.setLayout(new BorderLayout());
+
+ // Using createAWT allows you to defer the creation of the webview until the
+ // canvas is fully renderable.
+ Component component = Webview.createAWT((wv) -> {
+ // Calling `await echo(1,2,3)` will return `[1,2,3]`
+ wv.bind("echo", (arguments) -> {
+ return arguments;
+ });
+
+ wv.loadURL("https://google.com");
+
+ frame.addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent e) {
+ wv.close();
+ frame.dispose();
+ System.exit(0);
+ }
+ });
+
+ // Run the webview event loop, the webview is fully disposed when this returns.
+ wv.run();
+ });
+
+ frame.getContentPane().add(component, BorderLayout.CENTER);
+
+ frame.setSize(800, 600);
+ frame.setVisible(true);
+ }
+
+}
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..faab5fe
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,120 @@
+
+ 4.0.0
+ dev.webview
+ Webview
+ 1.0.0
+
+
+ UTF-8
+ UTF-8
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.1
+
+
+ 11
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 2.3.2
+
+ ${project.name}-original
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.2.1
+
+
+ shade
+ package
+
+ shade
+
+
+
+
+ true
+ ${project.name}
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ 3.1.0
+
+ ${project.name}
+
+
+
+ attach-sources
+
+ jar
+
+
+
+
+
+
+
+
+
+ jitpack.io
+ https://jitpack.io
+
+
+
+
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.22
+ provided
+
+
+
+ org.jetbrains
+ annotations
+ 19.0.0
+ provided
+
+
+
+ com.github.casterlabs.rakurai
+ Json
+ 1.13.0
+ compile
+
+
+ com.github.casterlabs.rakurai
+ Util
+ 1.13.0
+ compile
+
+
+
+ net.java.dev.jna
+ jna
+ 5.10.0
+ compile
+
+
+ net.java.dev.jna
+ jna-platform
+ 5.10.0
+ compile
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/dev/webview/ConsumingProducer.java b/src/main/java/dev/webview/ConsumingProducer.java
new file mode 100644
index 0000000..cd88376
--- /dev/null
+++ b/src/main/java/dev/webview/ConsumingProducer.java
@@ -0,0 +1,17 @@
+package dev.webview;
+
+import org.jetbrains.annotations.Nullable;
+
+import lombok.NonNull;
+
+public interface ConsumingProducer {
+
+ public @Nullable P produce(@Nullable C consume) throws InterruptedException;
+
+ public static ConsumingProducer of(@NonNull Class consumingClazz, @Nullable P result) {
+ return (aVoid) -> {
+ return result;
+ };
+ }
+
+}
diff --git a/src/main/java/dev/webview/Pair.java b/src/main/java/dev/webview/Pair.java
new file mode 100644
index 0000000..27de6b7
--- /dev/null
+++ b/src/main/java/dev/webview/Pair.java
@@ -0,0 +1,10 @@
+package dev.webview;
+
+import lombok.AllArgsConstructor;
+
+@AllArgsConstructor
+public class Pair {
+ public final A a;
+ public final B b;
+
+}
diff --git a/src/main/java/dev/webview/Platform.java b/src/main/java/dev/webview/Platform.java
new file mode 100644
index 0000000..fb81a58
--- /dev/null
+++ b/src/main/java/dev/webview/Platform.java
@@ -0,0 +1,35 @@
+package dev.webview;
+
+import java.util.regex.Pattern;
+
+import lombok.AllArgsConstructor;
+
+@AllArgsConstructor
+public enum Platform {
+ // @formatter:off
+ MACOSX ("macOS", "mac|darwin"),
+ LINUX ("Linux", "nux"),
+ WINDOWS ("Windows", "win");
+ // @formatter:on
+
+ private String str;
+ private String regex;
+
+ static Platform get() {
+ String osName = System.getProperty("os.name").toLowerCase();
+
+ for (Platform os : values()) {
+ if (Pattern.compile(os.regex).matcher(osName).find()) {
+ return os;
+ }
+ }
+
+ throw new UnsupportedOperationException("Unknown operating system: " + osName);
+ }
+
+ @Override
+ public String toString() {
+ return this.str;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/dev/webview/Webview.java b/src/main/java/dev/webview/Webview.java
new file mode 100644
index 0000000..652cdf8
--- /dev/null
+++ b/src/main/java/dev/webview/Webview.java
@@ -0,0 +1,193 @@
+package dev.webview;
+
+import static dev.webview.WebviewNative.NULL_PTR;
+import static dev.webview.WebviewNative.WV_HINT_FIXED;
+import static dev.webview.WebviewNative.WV_HINT_MAX;
+import static dev.webview.WebviewNative.WV_HINT_MIN;
+import static dev.webview.WebviewNative.WV_HINT_NONE;
+
+import java.awt.Canvas;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.io.Closeable;
+import java.util.function.Consumer;
+
+import org.jetbrains.annotations.Nullable;
+
+import com.sun.jna.Native;
+import com.sun.jna.ptr.PointerByReference;
+
+import co.casterlabs.rakurai.json.Rson;
+import co.casterlabs.rakurai.json.element.JsonArray;
+import co.casterlabs.rakurai.json.element.JsonElement;
+import co.casterlabs.rakurai.json.serialization.JsonParseException;
+import dev.webview.WebviewNative.BindCallback;
+import lombok.NonNull;
+
+public class Webview implements Closeable, Runnable {
+ private static final WebviewNative N;
+
+ public static final Platform PLATFORM = Platform.get();
+
+ static {
+ String toLoad = WebviewNative.runSetup();
+ N = Native.load(toLoad, WebviewNative.class);
+ }
+
+ @Deprecated
+ public long $pointer;
+
+ public static Component createAWT(@NonNull Consumer onCreate) {
+ return new Canvas() {
+ private static final long serialVersionUID = 5199512256429931156L;
+
+ private boolean initialized = false;
+
+ private Webview webview;
+ private Dimension lastSize = null;
+
+ @Override
+ public void paint(Graphics g) {
+ Dimension size = this.getSize();
+
+ if (!size.equals(this.lastSize)) {
+ this.lastSize = size;
+
+ if (this.webview != null) {
+ this.updateSize();
+ }
+ }
+
+ if (!this.initialized) {
+ this.initialized = true;
+
+ new Thread(() -> {
+ this.webview = new Webview(this);
+
+ this.updateSize();
+
+ onCreate.accept(this.webview);
+ }).start();
+ }
+ }
+
+ private void updateSize() {
+ int width = this.lastSize.width;
+ int height = this.lastSize.height;
+
+ // There is a random margin on Windows that isn't visible, so we must
+ // compensate.
+ // TODO figure out why this is caused.
+ if (PLATFORM == Platform.WINDOWS) {
+ width -= 16;
+ height -= 39;
+ }
+
+ this.webview.setFixedSize(width, height);
+ }
+
+ };
+ }
+
+ /**
+ * Creates a new Webview.
+ */
+ public Webview() {
+ this(NULL_PTR);
+ }
+
+ /**
+ * Creates a new Webview.
+ *
+ * @param target The target awt component, such as a {@link java.awt.JFrame} or
+ * {@link java.awt.Canvas}
+ */
+ public Webview(@NonNull Component target) {
+ this(new PointerByReference(Native.getComponentPointer(target)));
+ }
+
+ /**
+ * @deprecated Use this if you absolutely do know what you're doing.
+ */
+ @Deprecated
+ public Webview(@Nullable PointerByReference windowPointer) {
+ $pointer = N.webview_create(false, windowPointer);
+
+ this.loadURL(null);
+ }
+
+ public void loadURL(@Nullable String url) {
+ if (url == null) {
+ url = "about:blank";
+ }
+
+ N.webview_navigate($pointer, url);
+ }
+
+ public void setTitle(@NonNull String title) {
+ N.webview_set_title($pointer, title);
+ }
+
+ public void setMinSize(int width, int height) {
+ N.webview_set_size($pointer, width, height, WV_HINT_MIN);
+ }
+
+ public void setMaxSize(int width, int height) {
+ N.webview_set_size($pointer, width, height, WV_HINT_MAX);
+ }
+
+ public void setSize(int width, int height) {
+ N.webview_set_size($pointer, width, height, WV_HINT_NONE);
+ }
+
+ public void setFixedSize(int width, int height) {
+ N.webview_set_size($pointer, width, height, WV_HINT_FIXED);
+ }
+
+ public void setInitScript(@NonNull String script) {
+ N.webview_init($pointer, script);
+ }
+
+ public void eval(@NonNull String script) {
+ N.webview_eval($pointer, script);
+ }
+
+ public void bind(@NonNull String name, @NonNull ConsumingProducer handler) {
+ N.webview_bind($pointer, name, new BindCallback() {
+ @Override
+ public void callback(long seq, String req, long arg) {
+ try {
+ JsonArray arguments = Rson.DEFAULT.fromJson(req, JsonArray.class);
+
+ try {
+ @Nullable
+ JsonElement result = handler.produce(arguments);
+
+ N.webview_return($pointer, seq, false, Rson.DEFAULT.toJsonString(result));
+ } catch (Exception e) {
+ N.webview_return($pointer, seq, true, e.getMessage());
+ }
+ } catch (JsonParseException e) {
+ e.printStackTrace();
+ }
+ }
+ }, 0);
+ }
+
+ public void unbind(@NonNull String name) {
+ N.webview_unbind($pointer, name);
+ }
+
+ @Override
+ public void run() {
+ N.webview_run($pointer);
+ N.webview_destroy($pointer);
+ }
+
+ @Override
+ public void close() {
+ N.webview_terminate($pointer);
+ }
+
+}
diff --git a/src/main/java/dev/webview/WebviewNative.java b/src/main/java/dev/webview/WebviewNative.java
new file mode 100644
index 0000000..84725a8
--- /dev/null
+++ b/src/main/java/dev/webview/WebviewNative.java
@@ -0,0 +1,202 @@
+package dev.webview;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+
+import com.sun.jna.Callback;
+import com.sun.jna.Library;
+import com.sun.jna.ptr.PointerByReference;
+
+import co.casterlabs.rakurai.io.IOUtil;
+import lombok.NonNull;
+import lombok.SneakyThrows;
+
+public interface WebviewNative extends Library {
+ static final PointerByReference NULL_PTR = null;
+
+ @SneakyThrows
+ static String runSetup() {
+ String toLoad = "libwebview";
+
+ String[] libraries = null;
+
+ switch (Webview.PLATFORM) {
+ case LINUX: {
+ libraries = new String[] {
+ "libwebview.so"
+ };
+ break;
+ }
+
+ case MACOSX: {
+ libraries = new String[] {
+ "libwebview.dylib"
+ };
+ break;
+ }
+
+ case WINDOWS: {
+ libraries = new String[] {
+ "webview.dll",
+ "WebView2Loader.dll"
+ };
+ toLoad = "webview";
+ break;
+ }
+ }
+
+ // Extract all of the libs.
+ for (String lib : libraries) {
+ File file = new File(lib);
+
+ if (!file.exists()) {
+ InputStream in = WebviewNative.class.getResourceAsStream("/" + lib);
+ byte[] bytes = IOUtil.readInputStreamBytes(in);
+ Files.write(file.toPath(), bytes);
+ }
+ }
+
+ return toLoad;
+ }
+
+ static final int WV_HINT_NONE = 0;
+ static final int WV_HINT_MIN = 1;
+ static final int WV_HINT_MAX = 2;
+ static final int WV_HINT_FIXED = 3;
+
+ /**
+ * Used in {@link webview_bind}
+ */
+ static interface BindCallback extends Callback {
+
+ /**
+ * @param seq The request id, used in {@link webview_return}
+ * @param req The javascript arguments converted to a json array (string)
+ * @param arg Unused
+ */
+ void callback(long seq, String req, long arg);
+
+ }
+
+ /**
+ * Creates a new webview instance. If debug is true - developer tools will be
+ * enabled (if the platform supports them). Window parameter can be a pointer to
+ * the native window handle. If it's non-null - then child WebView is embedded
+ * into the given parent window. Otherwise a new window is created. Depending on
+ * the platform, a GtkWindow, NSWindow or HWND pointer can be passed here.
+ *
+ * @param debug Enables developer tools if true (if supported)
+ * @param $window A pointer to a native window handle, for embedding the webview
+ * in a window. (Either a GtkWindow, NSWindow, or HWND pointer)
+ */
+ long webview_create(boolean debug, PointerByReference window);
+
+ /**
+ * @return a native window handle pointer.
+ *
+ * @param $pointer The instance pointer of the webview
+ *
+ * @implNote This is either a pointer to a GtkWindow, NSWindow, or
+ * HWND.
+ */
+ long webview_get_window(long $pointer);
+
+ /**
+ * Navigates to the given URL.
+ *
+ * @param $pointer The instance pointer of the webview
+ * @param url The target url, can be a data uri.
+ */
+ void webview_navigate(long $pointer, String url);
+
+ /**
+ * Sets the title of the webview window.
+ *
+ * @param $pointer The instance pointer of the webview
+ * @param title
+ */
+ void webview_set_title(long $pointer, String title);
+
+ /**
+ * Updates the webview's window size, see {@link WV_HINT_NONE},
+ * {@link WV_HINT_MIN}, {@link WV_HINT_MAX}, and {@link WV_HINT_FIXED}
+ *
+ * @param $pointer The instance pointer of the webview
+ * @param width
+ * @param height
+ * @param hint
+ */
+ void webview_set_size(long $pointer, int width, int height, int hint);
+
+ /**
+ * Runs the main loop until it's terminated. You must destroy the webview after
+ * this method returns.
+ *
+ * @param $pointer The instance pointer of the webview
+ */
+ void webview_run(long $pointer);
+
+ /**
+ * Destroys a webview and closes the native window.
+ *
+ * @param $pointer The instance pointer of the webview
+ */
+ void webview_destroy(long $pointer);
+
+ /**
+ * Stops the webview loop, which causes {@link #webview_run(long)} to return.
+ *
+ * @param $pointer The instance pointer of the webview
+ */
+ void webview_terminate(long $pointer);
+
+ /**
+ * Evaluates arbitrary JavaScript code asynchronously.
+ *
+ * @param $pointer The instance pointer of the webview
+ * @param js The script to execute
+ */
+ void webview_eval(long $pointer, @NonNull String js);
+
+ /**
+ * Injects JavaScript code at the initialization of the new page.
+ *
+ * @implSpec It is guaranteed to be called before window.onload.
+ *
+ * @param $pointer The instance pointer of the webview
+ * @param js The script to execute
+ */
+ void webview_init(long $pointer, @NonNull String js);
+
+ /**
+ * Binds a native callback so that it will appear under the given name as a
+ * global JavaScript function. Internally it uses webview_init().
+ *
+ * @param $pointer The instance pointer of the webview
+ * @param name The name of the function to be exposed in Javascript
+ * @param callback The callback to be called
+ * @param arg Unused
+ */
+ void webview_bind(long $pointer, @NonNull String name, BindCallback callback, long arg);
+
+ /**
+ * Remove the native callback specified.
+ *
+ * @param $pointer The instance pointer of the webview
+ * @param name The name of the callback
+ */
+ void webview_unbind(long $pointer, @NonNull String name);
+
+ /**
+ * Allows to return a value from the native binding. Original request pointer
+ * must be provided to help internal RPC engine match requests with responses.
+ *
+ * @param $pointer The instance pointer of the webview
+ * @param name The name of the callback
+ * @param isError Whether or not `result` should be thrown as an exception
+ * @param result The result (in json)
+ */
+ void webview_return(long $pointer, long seq, boolean isError, String result);
+
+}
diff --git a/src/main/resources/WebView2Loader.dll b/src/main/resources/WebView2Loader.dll
new file mode 100644
index 0000000..2805c27
Binary files /dev/null and b/src/main/resources/WebView2Loader.dll differ
diff --git a/src/main/resources/libwebview.dylib b/src/main/resources/libwebview.dylib
new file mode 100644
index 0000000..3b7a87e
Binary files /dev/null and b/src/main/resources/libwebview.dylib differ
diff --git a/src/main/resources/libwebview.so b/src/main/resources/libwebview.so
new file mode 100644
index 0000000..3cb95a8
Binary files /dev/null and b/src/main/resources/libwebview.so differ
diff --git a/src/main/resources/webview.dll b/src/main/resources/webview.dll
new file mode 100644
index 0000000..6408593
Binary files /dev/null and b/src/main/resources/webview.dll differ