Compare commits

...

8 Commits

Author SHA1 Message Date
UnlegitDqrk
83006ae037 Removed unused library 2026-03-03 12:48:20 +01:00
UnlegitDqrk
bca4841bef Import fix 2026-03-03 12:30:16 +01:00
UnlegitDqrk
466ca64c57 Added license 2026-03-03 12:28:38 +01:00
UnlegitDqrk
bb157a8eb2 Added license 2026-03-03 12:28:22 +01:00
UnlegitDqrk
e795aa3041 Small fixes 2026-02-28 17:52:10 +01:00
UnlegitDqrk
9338865185 Small fixes 2026-02-28 17:42:18 +01:00
UnlegitDqrk
a9b0ccb8a7 Many new things 2026-02-28 17:39:42 +01:00
UnlegitDqrk
a84c626416 Added image/video control and property manipulation 2026-02-28 16:56:30 +01:00
59 changed files with 4808 additions and 206 deletions

51
.idea/misc.xml generated
View File

@@ -8,7 +8,56 @@
</list> </list>
</option> </option>
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_23" default="true" project-jdk-name="23" project-jdk-type="JavaSDK"> <component name="NullableNotNullManager">
<option name="myDefaultNullable" value="org.jetbrains.annotations.Nullable" />
<option name="myDefaultNotNull" value="org.jetbrains.annotations.NotNull" />
<option name="myOrdered" value="false" />
<option name="myNullables">
<value>
<list size="16">
<item index="0" class="java.lang.String" itemvalue="org.jspecify.annotations.Nullable" />
<item index="1" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
<item index="2" class="java.lang.String" itemvalue="android.annotation.Nullable" />
<item index="3" class="java.lang.String" itemvalue="androidx.annotation.Nullable" />
<item index="4" class="java.lang.String" itemvalue="com.android.annotations.Nullable" />
<item index="5" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
<item index="6" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNullable" />
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
<item index="10" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.Nullable" />
<item index="11" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
<item index="12" class="java.lang.String" itemvalue="jakarta.annotation.Nullable" />
<item index="13" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
<item index="14" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
<item index="15" class="java.lang.String" itemvalue="org.springframework.lang.Nullable" />
</list>
</value>
</option>
<option name="myNotNulls">
<value>
<list size="16">
<item index="0" class="java.lang.String" itemvalue="org.jspecify.annotations.NonNull" />
<item index="1" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="2" class="java.lang.String" itemvalue="android.annotation.NonNull" />
<item index="3" class="java.lang.String" itemvalue="androidx.annotation.NonNull" />
<item index="4" class="java.lang.String" itemvalue="com.android.annotations.NonNull" />
<item index="5" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
<item index="6" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNonNull" />
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
<item index="10" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.NonNull" />
<item index="11" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
<item index="12" class="java.lang.String" itemvalue="jakarta.annotation.Nonnull" />
<item index="13" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
<item index="14" class="java.lang.String" itemvalue="lombok.NonNull" />
<item index="15" class="java.lang.String" itemvalue="org.springframework.lang.NonNull" />
</list>
</value>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_25" default="true" project-jdk-name="25" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" /> <output url="file://$PROJECT_DIR$/out" />
</component> </component>
</project> </project>

15
LICENSE
View File

@@ -1 +1,14 @@
Please read the license here: https://open-autonomous-connection.org/license.html Open Autonomous Public License (OAPL)
Copyright (C) Open Autonomous Connection (OAC)
This project is licensed under the Open Autonomous Public License (OAPL),
version 1.1 or any later version published by the Open Autonomous Connection.
The full and authoritative license text is available at:
https://open-autonomous-connection.org/license.html
By using, modifying, or distributing this software, you agree to the terms
of the latest applicable OAPL version.
NOTICE: Third party licenses can be found in resources/licenses

147
LICENSE-oapl1.1 Normal file
View File

@@ -0,0 +1,147 @@
Open Autonomous Public License (OAPL) v1.1
Sovereign Network Copyleft License
Copyright (C) Open Autonomous Connection (OAC)
Project URL: https://open-autonomous-connection.org/
0. Definitions
“OAC Software”
Means any software distributed under this License, including but not limited to the Open Autonomous Connection protocol, reference implementations, clients, servers, INS systems, APIs, libraries, and the WebPage framework.
“Covered Work”
Means the OAC Software and any Derivative Work thereof.
“Derivative Work”
A work shall be considered a Derivative Work if it:
1. Modifies the OAC Software;
2. Incorporates it;
3. Links to it, whether statically or dynamically;
4. Extends its public APIs in a manner requiring OAC at runtime;
5. Implements required OAC interfaces;
6. Is designed to operate exclusively within the OAC runtime environment.
For clarity: A class extending “WebPage” is considered a Derivative Work.
“Independent Content”
Data or media files that do not contain executable OAC-dependent logic and can exist independently of OAC. Examples include HTML files, CSS files, images, JSON data, and static documents.
“Network Deployment”
Making a Covered Work available to third parties over any network, including but not limited to SaaS, cloud hosting, managed services, container environments, remote execution systems, and reverse-proxied deployments.
“Software Monetisation”
Any direct or indirect financial compensation in exchange for licensing, distributing, sublicensing, selling, restricting access to, or otherwise commercially exploiting the Covered Work itself as software.
“Content Monetisation”
Commercial activity based on content transmitted through OAC, provided that no Software Monetisation occurs.
1. Grant of License
Subject to compliance with this License, you are granted a worldwide, royalty-free, non-exclusive, non-transferable right to use, copy, study, modify, and distribute the Covered Work.
All rights not expressly granted remain reserved.
2. Strong Copyleft
Any Derivative Work:
1. Must be licensed exclusively under this OAPL v1.1;
2. Must include the full, unmodified text of this License;
3. Must clearly document all modifications;
4. Must provide full corresponding source code;
5. Must not impose additional restrictions beyond this License.
Re-licensing under any other license is prohibited.
3. Network Copyleft
If a Covered Work is subject to Network Deployment:
1. The complete corresponding source code must be publicly accessible;
2. Access must not require payment;
3. Access must not require authentication beyond reasonable technical protections such as rate-limiting;
4. Build scripts and sufficient instructions for reproducible builds must be provided.
This obligation applies regardless of whether distribution occurs.
4. Prohibition of Software Monetisation
The Covered Work may not be:
- Sold;
- Licensed for a fee;
- Distributed against financial compensation;
- Placed behind a paywall;
- Restricted by license keys or technical access controls intended to require payment;
- Otherwise monetised as software.
Charging for infrastructure, hosting, support, consulting, or independent services is permitted, provided no Software Monetisation occurs.
5. Content Monetisation
Independent Content transmitted through OAC may be monetised without restriction, provided the Covered Work itself is not monetised.
6. Patent License
Each contributor grants a perpetual, worldwide, royalty-free patent license covering any patent claims necessarily infringed by their contributions to the Covered Work.
If any party initiates patent litigation alleging that the Covered Work infringes their patent rights, that partys rights under this License terminate immediately.
7. Contributor Terms
By submitting contributions to the Covered Work, you grant the original copyright holder the right to:
- Enforce this License;
- Publish future versions of the OAPL;
- Re-license your contribution under later versions of the OAPL.
Contributors retain their copyright.
8. No Proprietary Integration
The Covered Work may not be combined with software that:
- Prevents full source disclosure;
- Imposes additional restrictions;
- Conflicts with this License.
If full compliance is not possible, distribution is prohibited.
9. Trademark
This License does not grant any rights to use the trademarks, trade names, service marks, or product names of the original copyright holder.
The terms:
- “Open Autonomous Connection”
- “OAC”
- “OAC-Compatible”
may only be used:
1. For unmodified versions of the OAC Software; or
2. For fully compliant implementations under the OAC Compliance Specification; or
3. With explicit written permission from the trademark holder.
Modified or forked versions must remove all references to OAC trademarks unless permission is granted.
10. Cure Period
Any violation of this License results in automatic termination of rights.
If the violation is cured within 30 days of written notice, the License is automatically reinstated.
11. Warranty Disclaimer
THE COVERED WORK IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.
12. Limitation of Liability
TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THE AUTHORS SHALL NOT BE LIABLE FOR ANY DAMAGES ARISING FROM THE USE OF THE COVERED WORK.
13. Severability
If any provision of this License is held to be unenforceable, the remaining provisions shall remain in full force and effect.
Such provision shall be interpreted to most closely reflect its original intent within the limits of applicable law.
14. Governing Law
This License shall be governed by the laws of the Federal Republic of Germany.
Exclusive place of jurisdiction shall be the registered seat of the copyright holder, unless mandatory law provides otherwise.
End of License.

34
pom.xml
View File

@@ -6,18 +6,18 @@
<groupId>org.openautonomousconnection</groupId> <groupId>org.openautonomousconnection</groupId>
<artifactId>LuaScript</artifactId> <artifactId>LuaScript</artifactId>
<version>0.0.0-STABLE.1.3</version> <version>0.0.0-STABLE.1.6</version>
<organization> <organization>
<name>Open Autonomous Connection</name> <name>Open Autonomous Connection</name>
<url>https://open-autonomous-connection.org/</url> <url>https://open-autonomous-connection.org/</url>
</organization> </organization>
<url>https://open-autonomous-connection.org/</url> <url>https://open-autonomous-connection.org/</url>
<description>The default DNS-Server</description> <description>A Scripting alternative to JavaScript</description>
<properties> <properties>
<maven.compiler.source>23</maven.compiler.source> <maven.compiler.source>25</maven.compiler.source>
<maven.compiler.target>23</maven.compiler.target> <maven.compiler.target>25</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
@@ -66,28 +66,16 @@
<repositories> <repositories>
<repository> <repository>
<id>unlegitdqrk</id> <id>repounlegitdqrk</id>
<url>https://repo.unlegitdqrk.dev/api/packages/UnlegitDqrk/maven</url> <url>https://repo.unlegitdqrk.dev/api/packages/UnlegitDqrk/maven</url>
</repository> </repository>
<repository>
<id>oac</id>
<url>https://repo.open-autonomous-connection.org/api/packages/open-autonomous-connection/maven</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories> </repositories>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>dev.unlegitdqrk</groupId> <groupId>org.openautonomousconnection.luaj</groupId>
<artifactId>unlegitlibrary</artifactId> <artifactId>jse</artifactId>
<version>1.8.3</version> <version>0.0.0-STABLE-1.0</version>
</dependency>
<dependency>
<groupId>org.luaj</groupId>
<artifactId>luaj-jse</artifactId>
<version>3.0.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
@@ -130,13 +118,17 @@
<artifactId>maven-javadoc-plugin</artifactId> <artifactId>maven-javadoc-plugin</artifactId>
<version>3.6.3</version> <version>3.6.3</version>
<configuration> <configuration>
<failOnError>false</failOnError> <failOnError>true</failOnError>
<failOnWarnings>false</failOnWarnings> <failOnWarnings>false</failOnWarnings>
<doclint>none</doclint> <doclint>none</doclint>
<locale>en_US</locale> <locale>en_US</locale>
<encoding>UTF-8</encoding> <encoding>UTF-8</encoding>
<docencoding>UTF-8</docencoding> <docencoding>UTF-8</docencoding>
<charset>UTF-8</charset> <charset>UTF-8</charset>
<additionalOptions>
<additionalOption>--allow-script-in-comments</additionalOption>
</additionalOptions>
</configuration> </configuration>
<executions> <executions>
<execution> <execution>

View File

@@ -3,6 +3,8 @@ package org.openautonomousconnection.luascript.events;
import org.luaj.vm2.LuaTable; import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue; import org.luaj.vm2.LuaValue;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -17,12 +19,18 @@ public final class JavaToLua {
public static LuaValue coerce(Object v) { public static LuaValue coerce(Object v) {
if (v == null) return LuaValue.NIL; if (v == null) return LuaValue.NIL;
if (v instanceof LuaValue lv) return lv; if (v instanceof LuaValue lv) return lv;
if (v instanceof String s) return LuaValue.valueOf(s); if (v instanceof String s) return LuaValue.valueOf(s);
if (v instanceof Boolean b) return LuaValue.valueOf(b); if (v instanceof Boolean b) return LuaValue.valueOf(b);
if (v instanceof Integer i) return LuaValue.valueOf(i);
if (v instanceof Long l) return LuaValue.valueOf(l); if (v instanceof Byte n) return LuaValue.valueOf(n.intValue());
if (v instanceof Float f) return LuaValue.valueOf(f); if (v instanceof Short n) return LuaValue.valueOf(n.intValue());
if (v instanceof Double d) return LuaValue.valueOf(d); if (v instanceof Integer n) return LuaValue.valueOf(n);
if (v instanceof Long n) return LuaValue.valueOf(n);
if (v instanceof Float n) return LuaValue.valueOf(n.doubleValue());
if (v instanceof Double n) return LuaValue.valueOf(n);
if (v instanceof Number n) return LuaValue.valueOf(n.doubleValue());
if (v instanceof Map<?, ?> m) { if (v instanceof Map<?, ?> m) {
LuaTable t = new LuaTable(); LuaTable t = new LuaTable();
@@ -43,6 +51,24 @@ public final class JavaToLua {
return t; return t;
} }
if (v instanceof Collection<?> col) {
LuaTable t = new LuaTable();
int i = 1;
for (Object o : col) {
t.set(i++, coerce(o));
}
return t;
}
if (v.getClass().isArray()) {
LuaTable t = new LuaTable();
int len = Array.getLength(v);
for (int i = 0; i < len; i++) {
t.set(i + 1, coerce(Array.get(v, i)));
}
return t;
}
return LuaValue.valueOf(String.valueOf(v)); return LuaValue.valueOf(String.valueOf(v));
} }
} }

View File

@@ -0,0 +1,212 @@
package org.openautonomousconnection.luascript.fx;
import javafx.application.Platform;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import org.openautonomousconnection.luascript.hosts.AudioHost;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
/**
* JavaFX MediaPlayer based AudioHost.
*
* <p>Note: JavaFX Media does not reliably use {@link java.net.URLStreamHandler} for custom schemes.
* Therefore, for {@code web://} this host resolves via {@link URLConnection} (your installed handler),
* spools to a temporary file and plays that file via {@code file://}.</p>
*/
public final class FxAudioHost implements AudioHost {
private static final Set<String> AUDIO_EXTENSIONS = Set.of(
"mp3", "wav", "ogg", "m4a", "opus", "web"
);
private volatile MediaPlayer player;
private volatile boolean loop;
private volatile double volume = 1.0;
private volatile Path lastTempFile;
@Override
public void playFile(File file) {
Objects.requireNonNull(file, "file");
if (!file.isFile()) {
throw new IllegalArgumentException("Audio file not found: " + file.getAbsolutePath());
}
String ext = MediaExtensions.extensionOf(file.getName());
ensureAllowedAudioExtension(ext, file.getName());
playUri(file.toURI());
}
@Override
public void playUrl(String url) {
Objects.requireNonNull(url, "url");
String u = url.trim();
if (u.isEmpty()) throw new IllegalArgumentException("URL is empty");
URI uri = URI.create(u);
String scheme = (uri.getScheme() == null) ? "" : uri.getScheme().toLowerCase(Locale.ROOT);
String ext = MediaExtensions.extensionOf(uri.getPath());
ensureAllowedAudioExtension(ext, uri.getPath());
if ("http".equals(scheme) || "https".equals(scheme) || "file".equals(scheme)) {
playUri(uri);
return;
}
if ("web".equals(scheme)) {
Path tmp = null;
try {
tmp = downloadToTempFile(u, ext);
rememberTemp(tmp);
playUri(tmp.toUri());
return;
} catch (IOException e) {
safeDelete(tmp);
throw new RuntimeException("Failed to load web audio: " + e.getMessage(), e);
}
}
throw new IllegalArgumentException("Unsupported scheme: " + scheme);
}
@Override
public void pause() {
Platform.runLater(() -> {
MediaPlayer p = player;
if (p != null) p.pause();
});
}
@Override
public void stop() {
Platform.runLater(this::stopInternal);
}
@Override
public void setVolume(double volume) {
double v = clamp01(volume);
this.volume = v;
Platform.runLater(() -> {
MediaPlayer p = player;
if (p != null) p.setVolume(v);
});
}
@Override
public void setLoop(boolean loop) {
this.loop = loop;
Platform.runLater(() -> {
MediaPlayer p = player;
if (p != null) p.setCycleCount(loop ? MediaPlayer.INDEFINITE : 1);
});
}
private void playUri(URI uri) {
Platform.runLater(() -> {
stopInternal();
Media media = new Media(uri.toString());
MediaPlayer p = new MediaPlayer(media);
p.setCycleCount(loop ? MediaPlayer.INDEFINITE : 1);
p.setVolume(volume);
p.setOnEndOfMedia(() -> {
if (!loop) {
try {
p.dispose();
} catch (Exception ignored) {
// ignore
}
}
});
this.player = p;
p.play();
});
}
private void stopInternal() {
MediaPlayer p = this.player;
this.player = null;
if (p != null) {
try {
p.stop();
} catch (Exception ignored) {
// ignore
}
try {
p.dispose();
} catch (Exception ignored) {
// ignore
}
}
Path tmp = this.lastTempFile;
this.lastTempFile = null;
safeDelete(tmp);
}
private static Path downloadToTempFile(String url, String ext) throws IOException {
URL u = new URL(url);
URLConnection con = u.openConnection();
con.setUseCaches(false);
String safeExt = (ext == null || ext.isBlank()) ? "bin" : ext.toLowerCase(Locale.ROOT);
Path tmp = Files.createTempFile("oac-audio-", "." + safeExt);
try (InputStream in = con.getInputStream()) {
Files.copy(in, tmp, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
safeDelete(tmp);
throw e;
}
Files.write(tmp, new byte[0], StandardOpenOption.APPEND);
return tmp;
}
private void rememberTemp(Path tmp) {
Path prev = this.lastTempFile;
this.lastTempFile = tmp;
safeDelete(prev);
}
private static void safeDelete(Path p) {
if (p == null) return;
try {
Files.deleteIfExists(p);
} catch (IOException ignored) {
// best-effort
}
}
private static double clamp01(double v) {
if (v < 0.0) return 0.0;
if (v > 1.0) return 1.0;
return v;
}
private static void ensureAllowedAudioExtension(String ext, String source) {
String e = (ext == null) ? "" : ext.toLowerCase(Locale.ROOT);
if (e.isEmpty() || !AUDIO_EXTENSIONS.contains(e)) {
throw new IllegalArgumentException("Unsupported audio format '" + e + "' for: " + source);
}
}
}

View File

@@ -0,0 +1,35 @@
package org.openautonomousconnection.luascript.fx;
import javafx.application.Platform;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import org.openautonomousconnection.luascript.hosts.ClipboardHost;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
/**
* JavaFX clipboard host (text only).
*/
public final class FxClipboardHost implements ClipboardHost {
@Override
public void setText(String text) {
String s = text == null ? "" : text;
Platform.runLater(() -> {
ClipboardContent c = new ClipboardContent();
c.putString(s);
Clipboard.getSystemClipboard().setContent(c);
});
}
@Override
public String getText() {
AtomicReference<String> out = new AtomicReference<>("");
FxThreadBridge.runAndWait(() -> {
String s = Clipboard.getSystemClipboard().getString();
out.set(s == null ? "" : s);
});
return out.get();
}
}

View File

@@ -0,0 +1,122 @@
package org.openautonomousconnection.luascript.fx;
import javafx.scene.web.WebEngine;
import org.openautonomousconnection.luascript.hosts.CssHost;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
/**
* CSS host via internal JS bridge.
*/
public final class FxCssHost implements CssHost {
private final WebEngine engine;
private final FxDomHost dom;
public FxCssHost(WebEngine engine, FxDomHost dom) {
this.engine = Objects.requireNonNull(engine, "engine");
this.dom = Objects.requireNonNull(dom, "dom");
}
@Override
public String getComputedStyle(String elementId, String property) {
Objects.requireNonNull(elementId, "elementId");
Objects.requireNonNull(property, "property");
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
dom.requireDocument();
String script = ""
+ "(function(){"
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
+ " var prop=" + FxWebBridge.toJsLiteral(property) + ";"
+ " var el=document.getElementById(id);"
+ " if(!el) throw new Error('Unknown element id: '+id);"
+ " var cs=getComputedStyle(el);"
+ " var v=cs.getPropertyValue(prop) || cs[prop] || '';"
+ " return String(v);"
+ "})();";
Object ret = engine.executeScript(script);
return ret == null ? "" : String.valueOf(ret);
}));
}
@Override
public Map<String, String> getComputedStyles(String elementId, String[] properties) {
Objects.requireNonNull(elementId, "elementId");
Objects.requireNonNull(properties, "properties");
Map<String, String> out = new LinkedHashMap<>();
for (String p : properties) {
if (p == null || p.isBlank()) continue;
out.put(p, getComputedStyle(elementId, p));
}
return out;
}
@Override
public void setInlineStyle(String elementId, String property, String value) {
Objects.requireNonNull(elementId, "elementId");
Objects.requireNonNull(property, "property");
FxThreadBridge.runAndWait(() -> FxWebBridge.runWithJs(engine, () -> {
dom.requireDocument();
String v = value == null ? "" : value;
String script = ""
+ "(function(){"
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
+ " var prop=" + FxWebBridge.toJsLiteral(property) + ";"
+ " var val=" + FxWebBridge.toJsLiteral(v) + ";"
+ " var el=document.getElementById(id);"
+ " if(!el) throw new Error('Unknown element id: '+id);"
+ " if(!val){ el.style.removeProperty(prop); return null; }"
+ " el.style.setProperty(prop, val);"
+ " return null;"
+ "})();";
engine.executeScript(script);
}));
}
@Override
public String getInlineStyle(String elementId, String property) {
Objects.requireNonNull(elementId, "elementId");
Objects.requireNonNull(property, "property");
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
dom.requireDocument();
String script = ""
+ "(function(){"
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
+ " var prop=" + FxWebBridge.toJsLiteral(property) + ";"
+ " var el=document.getElementById(id);"
+ " if(!el) throw new Error('Unknown element id: '+id);"
+ " var v=el.style.getPropertyValue(prop) || '';"
+ " return String(v);"
+ "})();";
Object ret = engine.executeScript(script);
return ret == null ? "" : String.valueOf(ret);
}));
}
@Override
public void setCssVariable(String elementId, String name, String value) {
Objects.requireNonNull(name, "name");
if (!name.trim().startsWith("--")) throw new IllegalArgumentException("CSS variable must start with '--': " + name);
setInlineStyle(elementId, name.trim(), value);
}
@Override
public String getCssVariable(String elementId, String name) {
Objects.requireNonNull(name, "name");
if (!name.trim().startsWith("--")) throw new IllegalArgumentException("CSS variable must start with '--': " + name);
return getComputedStyle(elementId, name.trim());
}
}

View File

@@ -10,7 +10,9 @@ import java.util.concurrent.atomic.AtomicLong;
/** /**
* DomHost implementation backed by JavaFX WebView's W3C DOM (WebEngine#getDocument()). * DomHost implementation backed by JavaFX WebView's W3C DOM (WebEngine#getDocument()).
* *
* <p>No jsoup and no JavaScript. All operations are performed via W3C DOM APIs.</p> * <p>Uses W3C DOM for structure/attributes and a small JavaScript bridge for DOM properties and method calls
* (required for HTMLMediaElement like video/audio). Lua remains the scripting language; JavaScript is only
* used internally to access DOM properties/methods that are not available via W3C DOM APIs.</p>
* *
* <p>Element identity is the {@code id} attribute. This host auto-assigns stable ids to elements that * <p>Element identity is the {@code id} attribute. This host auto-assigns stable ids to elements that
* do not have one, ensuring addressability for Lua bindings and event routing.</p> * do not have one, ensuring addressability for Lua bindings and event routing.</p>
@@ -77,6 +79,24 @@ public final class FxDomHost implements DomHost {
return v; return v;
} }
private static String requireId(String id) {
if (id == null) throw new IllegalArgumentException("elementId is null");
String v = id.trim();
if (v.isEmpty()) throw new IllegalArgumentException("elementId is blank");
return v;
}
private static String requireJsIdentifier(String s, String label) {
if (s == null) throw new IllegalArgumentException(label + " is null");
String v = s.trim();
if (v.isEmpty()) throw new IllegalArgumentException(label + " is blank");
// Prevent JS injection: only simple identifier.
if (!v.matches("^[A-Za-z_$][A-Za-z0-9_$]*$")) {
throw new IllegalArgumentException(label + " must be a JS identifier: " + v);
}
return v;
}
/** /**
* Ensures every element has a stable id. * Ensures every element has a stable id.
*/ */
@@ -314,6 +334,77 @@ public final class FxDomHost implements DomHost {
}); });
} }
@Override
public void setProperty(String elementId, String property, Object value) {
final String id = requireId(elementId);
final String prop = requireJsIdentifier(property, "property");
final String jsValue = toJsLiteral(value);
FxThreadBridge.runAndWait(() -> {
requireDocument();
ensureJsEnabled();
String script = ""
+ "(function(){"
+ " var el = document.getElementById(" + toJsLiteral(id) + ");"
+ " if(!el) throw new Error('Unknown element id: ' + " + toJsLiteral(id) + ");"
+ " el[" + toJsLiteral(prop) + "] = " + jsValue + ";"
+ " return null;"
+ "})();";
engine.executeScript(script);
engine.setJavaScriptEnabled(false);
});
}
@Override
public Object getProperty(String elementId, String property) {
final String id = requireId(elementId);
final String prop = requireJsIdentifier(property, "property");
return FxThreadBridge.callAndWait(() -> {
requireDocument();
ensureJsEnabled();
String script = ""
+ "(function(){"
+ " var el = document.getElementById(" + toJsLiteral(id) + ");"
+ " if(!el) throw new Error('Unknown element id: ' + " + toJsLiteral(id) + ");"
+ " return el[" + toJsLiteral(prop) + "];"
+ "})();";
Object ret = engine.executeScript(script);
engine.setJavaScriptEnabled(false);
return ret;
});
}
@Override
public Object call(String elementId, String method, Object... args) {
final String id = requireId(elementId);
final String m = requireJsIdentifier(method, "method");
final Object[] safeArgs = (args == null) ? new Object[0] : args.clone();
final String argvLiteral = toJsArrayLiteral(safeArgs);
return FxThreadBridge.callAndWait(() -> {
requireDocument();
ensureJsEnabled();
String script = ""
+ "(function(){"
+ " var el = document.getElementById(" + toJsLiteral(id) + ");"
+ " if(!el) throw new Error('Unknown element id: ' + " + toJsLiteral(id) + ");"
+ " var fn = el[" + toJsLiteral(m) + "];"
+ " if(typeof fn !== 'function') throw new Error('Not a function: ' + " + toJsLiteral(m) + ");"
+ " return fn.apply(el, " + argvLiteral + ");"
+ "})();";
Object ret = engine.executeScript(script);
engine.setJavaScriptEnabled(false);
return ret;
});
}
/** /**
* Exposes the current document (FX thread access required by callers). * Exposes the current document (FX thread access required by callers).
* *
@@ -339,6 +430,13 @@ public final class FxDomHost implements DomHost {
return el; return el;
} }
private void ensureJsEnabled() {
// Required for HTMLMediaElement and DOM property access beyond W3C DOM.
if (!engine.isJavaScriptEnabled()) {
engine.setJavaScriptEnabled(true);
}
}
private String generateUniqueId(Document doc) { private String generateUniqueId(Document doc) {
while (true) { while (true) {
String id = "__auto_" + autoIdSeq.getAndIncrement(); String id = "__auto_" + autoIdSeq.getAndIncrement();
@@ -353,4 +451,104 @@ public final class FxDomHost implements DomHost {
} }
return generateUniqueId(doc); return generateUniqueId(doc);
} }
}
/**
* Converts a Java value into a safe JavaScript literal.
*
* <p>Supported:</p>
* <ul>
* <li>null</li>
* <li>String</li>
* <li>Boolean</li>
* <li>Number (finite)</li>
* </ul>
*
* @param v value
* @return JS literal
*/
private static String toJsLiteral(Object v) {
if (v == null) return "null";
if (v instanceof String s) {
return "'" + escapeJsSingleQuotedString(s) + "'";
}
if (v instanceof Boolean b) {
return b ? "true" : "false";
}
if (v instanceof Byte || v instanceof Short || v instanceof Integer || v instanceof Long) {
return String.valueOf(((Number) v).longValue());
}
if (v instanceof Float || v instanceof Double) {
double d = ((Number) v).doubleValue();
if (!Double.isFinite(d)) {
throw new IllegalArgumentException("Non-finite number is not supported for JS literal: " + d);
}
// Use plain Java formatting; JS accepts it.
return Double.toString(d);
}
if (v instanceof Number n) {
double d = n.doubleValue();
if (!Double.isFinite(d)) {
throw new IllegalArgumentException("Non-finite number is not supported for JS literal: " + d);
}
return Double.toString(d);
}
throw new IllegalArgumentException("Unsupported value type for JS literal: " + v.getClass().getName());
}
/**
* Builds a JavaScript array literal from Java arguments.
*
* @param args args (nullable)
* @return JS array literal
*/
private static String toJsArrayLiteral(Object[] args) {
if (args == null || args.length == 0) return "[]";
StringBuilder sb = new StringBuilder();
sb.append('[');
for (int i = 0; i < args.length; i++) {
if (i > 0) sb.append(',');
sb.append(toJsLiteral(args[i]));
}
sb.append(']');
return sb.toString();
}
/**
* Escapes a string for inclusion inside a single-quoted JavaScript string literal.
*
* @param s raw string
* @return escaped string
*/
private static String escapeJsSingleQuotedString(String s) {
if (s == null || s.isEmpty()) return "";
StringBuilder out = new StringBuilder(s.length() + 16);
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
switch (c) {
case '\'' -> out.append("\\'");
case '\\' -> out.append("\\\\");
case '\n' -> out.append("\\n");
case '\r' -> out.append("\\r");
case '\t' -> out.append("\\t");
case '\u0000' -> out.append("\\0");
case '\u2028' -> out.append("\\u2028");
case '\u2029' -> out.append("\\u2029");
default -> {
// Keep printable chars; escape other control chars.
if (c < 0x20) {
out.append(String.format("\\u%04x", (int) c));
} else {
out.append(c);
}
}
}
}
return out.toString();
}
}

View File

@@ -0,0 +1,99 @@
package org.openautonomousconnection.luascript.fx;
import javafx.scene.web.WebEngine;
import org.openautonomousconnection.luascript.hosts.GeometryHost;
import java.util.Map;
import java.util.Objects;
/**
* Geometry/scroll host via internal JS bridge.
*/
public final class FxGeometryHost implements GeometryHost {
private final WebEngine engine;
private final FxDomHost dom;
public FxGeometryHost(WebEngine engine, FxDomHost dom) {
this.engine = Objects.requireNonNull(engine, "engine");
this.dom = Objects.requireNonNull(dom, "dom");
}
@Override
public Map<String, Object> getBoundingClientRect(String elementId) {
Objects.requireNonNull(elementId, "elementId");
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
dom.requireDocument();
String script = ""
+ "(function(){"
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
+ " var el=document.getElementById(id);"
+ " if(!el) throw new Error('Unknown element id: '+id);"
+ " var r=el.getBoundingClientRect();"
+ " return {"
+ " x:r.x, y:r.y, width:r.width, height:r.height,"
+ " top:r.top, left:r.left, right:r.right, bottom:r.bottom"
+ " };"
+ "})();";
Object ret = engine.executeScript(script);
return FxWebBridge.toStringObjectMap(ret);
}));
}
@Override
public Map<String, Object> getViewport() {
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
dom.requireDocument();
String script = ""
+ "(function(){"
+ " return {"
+ " width: window.innerWidth || 0,"
+ " height: window.innerHeight || 0,"
+ " devicePixelRatio: window.devicePixelRatio || 1,"
+ " scrollX: window.scrollX || 0,"
+ " scrollY: window.scrollY || 0"
+ " };"
+ "})();";
Object ret = engine.executeScript(script);
return FxWebBridge.toStringObjectMap(ret);
}));
}
@Override
public void scrollTo(double x, double y) {
FxThreadBridge.runAndWait(() -> FxWebBridge.runWithJs(engine, () -> {
dom.requireDocument();
String script = ""
+ "(function(){"
+ " window.scrollTo(" + FxWebBridge.toJsLiteral(x) + "," + FxWebBridge.toJsLiteral(y) + ");"
+ " return null;"
+ "})();";
engine.executeScript(script);
}));
}
@Override
public void scrollIntoView(String elementId, String align) {
Objects.requireNonNull(elementId, "elementId");
String a = FxWebBridge.normalizeAlign(align);
FxThreadBridge.runAndWait(() -> FxWebBridge.runWithJs(engine, () -> {
dom.requireDocument();
String script = ""
+ "(function(){"
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
+ " var el=document.getElementById(id);"
+ " if(!el) throw new Error('Unknown element id: '+id);"
+ " el.scrollIntoView({block:" + FxWebBridge.toJsLiteral(a) + ", inline:" + FxWebBridge.toJsLiteral(a) + "});"
+ " return null;"
+ "})();";
engine.executeScript(script);
}));
}
}

View File

@@ -0,0 +1,270 @@
package org.openautonomousconnection.luascript.fx;
import javafx.scene.web.WebEngine;
import org.openautonomousconnection.luascript.hosts.ImageHost;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
/**
* ImageHost implementation that renders images inside the WebView DOM using an {@code <img>} element.
*
* <p>No JavaFX overlay panes are used. Positioning is done by setting CSS via {@code style} attribute.</p>
*
* <p>For {@code web://} URLs this host downloads the resource via {@link URLConnection} (your installed handler),
* stores it as a temporary file, then sets {@code src=file://...} because WebView won't reliably load custom schemes.</p>
*/
public final class FxImageHost implements ImageHost {
private static final Set<String> IMAGE_EXTENSIONS = Set.of(
"png", "jpg", "jpeg", "ico", "bmp", "avif", "heif", "heic", "webp"
);
private static final String IMG_ID = "__oac_image";
private final WebEngine engine;
private final FxDomHost dom;
private volatile double x;
private volatile double y;
private volatile double w = 320;
private volatile double h = 240;
private volatile boolean preserveRatio = true;
private volatile boolean smooth = true;
private volatile double opacity = 1.0;
private volatile Path lastTempFile;
/**
* Creates a new image host.
*
* @param engine web engine
* @param dom fx dom host
*/
public FxImageHost(WebEngine engine, FxDomHost dom) {
this.engine = Objects.requireNonNull(engine, "engine");
this.dom = Objects.requireNonNull(dom, "dom");
// Ensure element exists once document is present; if not yet loaded, calls will create on demand.
}
@Override
public void showFile(File file) {
Objects.requireNonNull(file, "file");
if (!file.isFile()) {
throw new IllegalArgumentException("Image file not found: " + file.getAbsolutePath());
}
String ext = MediaExtensions.extensionOf(file.getName());
ensureAllowed(ext, "image", file.getName());
URI uri = file.toURI();
setSource(uri.toString(), null);
}
@Override
public void showUrl(String url) {
Objects.requireNonNull(url, "url");
String u = url.trim();
if (u.isEmpty()) throw new IllegalArgumentException("URL is empty");
URI uri = URI.create(u);
String ext = MediaExtensions.extensionOf(uri.getPath());
ensureAllowed(ext, "image", uri.getPath());
String scheme = (uri.getScheme() == null) ? "" : uri.getScheme().toLowerCase(Locale.ROOT);
if ("http".equals(scheme) || "https".equals(scheme) || "file".equals(scheme)) {
setSource(uri.toString(), null);
return;
}
if ("web".equals(scheme)) {
Path tmp = null;
try {
tmp = downloadToTempFile(u, ext, "oac-img-");
rememberTemp(tmp);
setSource(tmp.toUri().toString(), tmp);
return;
} catch (IOException e) {
safeDelete(tmp);
throw new RuntimeException("Failed to load web image: " + e.getMessage(), e);
}
}
throw new IllegalArgumentException("Unsupported scheme: " + scheme);
}
@Override
public void hide() {
FxThreadBridge.runAndWait(() -> {
Element img = ensureImgElement();
img.setAttribute("style", mergeStyle(baseStyle(false), rectStyle(), visualStyle()));
img.removeAttribute("src");
});
Path tmp = this.lastTempFile;
this.lastTempFile = null;
safeDelete(tmp);
}
@Override
public void setRect(double x, double y, double w, double h) {
this.x = x;
this.y = y;
this.w = Math.max(0.0, w);
this.h = Math.max(0.0, h);
applyStyle(true);
}
@Override
public void setPreserveRatio(boolean preserve) {
this.preserveRatio = preserve;
applyStyle(true);
}
@Override
public void setSmooth(boolean smooth) {
this.smooth = smooth;
applyStyle(true);
}
@Override
public void setOpacity(double opacity) {
this.opacity = clamp01(opacity);
applyStyle(true);
}
private void setSource(String src, Path tempFileKept) {
FxThreadBridge.runAndWait(() -> {
Element img = ensureImgElement();
img.setAttribute("src", src);
img.setAttribute("style", mergeStyle(baseStyle(true), rectStyle(), visualStyle()));
});
// Keep temp file reference (so previous gets cleaned up)
if (tempFileKept != null) {
rememberTemp(tempFileKept);
}
}
private void applyStyle(boolean visible) {
FxThreadBridge.runAndWait(() -> {
Element img = ensureImgElement();
String src = img.getAttribute("src");
boolean show = visible && src != null && !src.isBlank();
img.setAttribute("style", mergeStyle(baseStyle(show), rectStyle(), visualStyle()));
});
}
private Element ensureImgElement() {
Document doc = dom.requireDocument();
Element img = doc.getElementById(IMG_ID);
if (img != null) return img;
Element body = (Element) doc.getElementsByTagName("body").item(0);
if (body == null) throw new IllegalStateException("No <body> element available");
img = doc.createElement("img");
img.setAttribute("id", IMG_ID);
img.setAttribute("draggable", "false");
img.setAttribute("alt", "");
body.appendChild(img);
return img;
}
private String baseStyle(boolean visible) {
return "position:fixed;"
+ "left:0;top:0;"
+ "z-index:2147483647;"
+ "display:" + (visible ? "block" : "none") + ";";
}
private String rectStyle() {
return "left:" + x + "px;"
+ "top:" + y + "px;"
+ "width:" + w + "px;"
+ "height:" + h + "px;";
}
private String visualStyle() {
String fit;
if (preserveRatio) {
fit = "object-fit:contain;";
} else {
fit = "object-fit:fill;";
}
String smoothing = smooth ? "image-rendering:auto;" : "image-rendering:pixelated;";
return fit + smoothing + "opacity:" + opacity + ";";
}
private static String mergeStyle(String... parts) {
StringBuilder sb = new StringBuilder(256);
for (String p : parts) {
if (p == null || p.isBlank()) continue;
String s = p.trim();
if (!s.endsWith(";")) s += ";";
sb.append(s);
}
return sb.toString();
}
private static Path downloadToTempFile(String url, String ext, String prefix) throws IOException {
URL u = new URL(url);
URLConnection con = u.openConnection();
con.setUseCaches(false);
String safeExt = (ext == null || ext.isBlank()) ? "bin" : ext.toLowerCase(Locale.ROOT);
Path tmp = Files.createTempFile(prefix, "." + safeExt);
try (InputStream in = con.getInputStream()) {
Files.copy(in, tmp, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
safeDelete(tmp);
throw e;
}
return tmp;
}
private void rememberTemp(Path tmp) {
Path prev = this.lastTempFile;
this.lastTempFile = tmp;
safeDelete(prev);
}
private static void safeDelete(Path p) {
if (p == null) return;
try {
Files.deleteIfExists(p);
} catch (IOException ignored) {
// best-effort
}
}
private static void ensureAllowed(String ext, String kind, String source) {
String e = (ext == null) ? "" : ext.toLowerCase(Locale.ROOT);
if (e.isEmpty() || !IMAGE_EXTENSIONS.contains(e)) {
throw new IllegalArgumentException("Unsupported " + kind + " format '" + e + "' for: " + source);
}
}
private static double clamp01(double v) {
if (v < 0.0) return 0.0;
if (v > 1.0) return 1.0;
return v;
}
}

View File

@@ -0,0 +1,246 @@
package org.openautonomousconnection.luascript.fx;
import javafx.scene.web.WebEngine;
import netscape.javascript.JSObject;
import org.openautonomousconnection.luascript.hosts.ObserverHost;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
/**
* Observer host implemented via JS observers calling back into Java.
*
* <p>Requires JavaScript enabled (bridge only).</p>
*/
public final class FxObserverHost implements ObserverHost {
private final WebEngine engine;
private final FxDomHost dom;
private volatile ObserverCallback callback;
private final ConcurrentHashMap<String, Boolean> mutationObserved = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, Boolean> resizeObserved = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, Boolean> intersectionObserved = new ConcurrentHashMap<>();
public FxObserverHost(WebEngine engine, FxDomHost dom) {
this.engine = Objects.requireNonNull(engine, "engine");
this.dom = Objects.requireNonNull(dom, "dom");
}
@Override
public void setCallback(ObserverCallback callback) {
this.callback = callback;
FxThreadBridge.runAndWait(this::installBridge);
}
@Override
public void observeMutations(String elementId, boolean subtree, boolean attributes, boolean childList, boolean characterData) {
Objects.requireNonNull(elementId, "elementId");
FxThreadBridge.runAndWait(() -> {
dom.requireDocument();
FxWebBridge.ensureJsEnabled(engine);
installBridge();
String script = ""
+ "(function(){"
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
+ " var el=document.getElementById(id);"
+ " if(!el) throw new Error('Unknown element id: '+id);"
+ " window.__oac_obs = window.__oac_obs || {};"
+ " window.__oac_obs.muts = window.__oac_obs.muts || new Map();"
+ " if(window.__oac_obs.muts.has(id)) return null;"
+ " var cfg={subtree:" + (subtree ? "true" : "false")
+ " ,attributes:" + (attributes ? "true" : "false")
+ " ,childList:" + (childList ? "true" : "false")
+ " ,characterData:" + (characterData ? "true" : "false")
+ " };"
+ " var mo=new MutationObserver(function(muts){"
+ " try{"
+ " var payload={count:muts.length};"
+ " window.__oac_bridge.emit('mutation', id, JSON.stringify(payload));"
+ " }catch(e){}"
+ " });"
+ " mo.observe(el,cfg);"
+ " window.__oac_obs.muts.set(id, mo);"
+ " return null;"
+ "})();";
engine.executeScript(script);
engine.setJavaScriptEnabled(false);
mutationObserved.put(elementId, true);
});
}
@Override
public void unobserveMutations(String elementId) {
Objects.requireNonNull(elementId, "elementId");
FxThreadBridge.runAndWait(() -> {
dom.requireDocument();
FxWebBridge.ensureJsEnabled(engine);
String script = ""
+ "(function(){"
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
+ " if(!window.__oac_obs || !window.__oac_obs.muts) return null;"
+ " var mo=window.__oac_obs.muts.get(id);"
+ " if(mo){ mo.disconnect(); window.__oac_obs.muts.delete(id); }"
+ " return null;"
+ "})();";
engine.executeScript(script);
engine.setJavaScriptEnabled(false);
mutationObserved.remove(elementId);
});
}
@Override
public void observeResize(String elementId) {
Objects.requireNonNull(elementId, "elementId");
FxThreadBridge.runAndWait(() -> {
dom.requireDocument();
FxWebBridge.ensureJsEnabled(engine);
installBridge();
String script = ""
+ "(function(){"
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
+ " var el=document.getElementById(id);"
+ " if(!el) throw new Error('Unknown element id: '+id);"
+ " if(!('ResizeObserver' in window)) return null;"
+ " window.__oac_obs = window.__oac_obs || {};"
+ " window.__oac_obs.res = window.__oac_obs.res || new Map();"
+ " if(window.__oac_obs.res.has(id)) return null;"
+ " var ro=new ResizeObserver(function(entries){"
+ " try{"
+ " var r=entries && entries[0] && entries[0].contentRect;"
+ " var payload=r?{x:r.x,y:r.y,width:r.width,height:r.height}:{count:(entries?entries.length:0)};"
+ " window.__oac_bridge.emit('resize', id, JSON.stringify(payload));"
+ " }catch(e){}"
+ " });"
+ " ro.observe(el);"
+ " window.__oac_obs.res.set(id, ro);"
+ " return null;"
+ "})();";
engine.executeScript(script);
engine.setJavaScriptEnabled(false);
resizeObserved.put(elementId, true);
});
}
@Override
public void unobserveResize(String elementId) {
Objects.requireNonNull(elementId, "elementId");
FxThreadBridge.runAndWait(() -> {
dom.requireDocument();
FxWebBridge.ensureJsEnabled(engine);
String script = ""
+ "(function(){"
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
+ " if(!window.__oac_obs || !window.__oac_obs.res) return null;"
+ " var ro=window.__oac_obs.res.get(id);"
+ " if(ro){ ro.disconnect(); window.__oac_obs.res.delete(id); }"
+ " return null;"
+ "})();";
engine.executeScript(script);
engine.setJavaScriptEnabled(false);
resizeObserved.remove(elementId);
});
}
@Override
public void observeIntersection(String elementId, double threshold) {
Objects.requireNonNull(elementId, "elementId");
double t = threshold;
if (t < 0.0) t = 0.0;
if (t > 1.0) t = 1.0;
double finalT = t;
FxThreadBridge.runAndWait(() -> {
dom.requireDocument();
FxWebBridge.ensureJsEnabled(engine);
installBridge();
String script = ""
+ "(function(){"
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
+ " var el=document.getElementById(id);"
+ " if(!el) throw new Error('Unknown element id: '+id);"
+ " if(!('IntersectionObserver' in window)) return null;"
+ " window.__oac_obs = window.__oac_obs || {};"
+ " window.__oac_obs.int = window.__oac_obs.int || new Map();"
+ " if(window.__oac_obs.int.has(id)) return null;"
+ " var io=new IntersectionObserver(function(entries){"
+ " try{"
+ " var e=entries && entries[0];"
+ " var payload=e?{"
+ " isIntersecting:!!e.isIntersecting,"
+ " ratio:(e.intersectionRatio||0)"
+ " }:{count:(entries?entries.length:0)};"
+ " window.__oac_bridge.emit('intersection', id, JSON.stringify(payload));"
+ " }catch(ex){}"
+ " }, {threshold:" + FxWebBridge.toJsLiteral(finalT) + "});"
+ " io.observe(el);"
+ " window.__oac_obs.int.set(id, io);"
+ " return null;"
+ "})();";
engine.executeScript(script);
engine.setJavaScriptEnabled(false);
intersectionObserved.put(elementId, true);
});
}
@Override
public void unobserveIntersection(String elementId) {
Objects.requireNonNull(elementId, "elementId");
FxThreadBridge.runAndWait(() -> {
dom.requireDocument();
FxWebBridge.ensureJsEnabled(engine);
String script = ""
+ "(function(){"
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
+ " if(!window.__oac_obs || !window.__oac_obs.int) return null;"
+ " var io=window.__oac_obs.int.get(id);"
+ " if(io){ io.disconnect(); window.__oac_obs.int.delete(id); }"
+ " return null;"
+ "})();";
engine.executeScript(script);
engine.setJavaScriptEnabled(false);
intersectionObserved.remove(elementId);
});
}
private void installBridge() {
dom.requireDocument();
FxWebBridge.ensureJsEnabled(engine);
JSObject win = (JSObject) engine.executeScript("window");
win.setMember("__oac_bridge", new Bridge());
}
/**
* Object exposed to JS.
*/
public final class Bridge {
/**
* Emits observer events from JS to Java.
*
* @param type observer type
* @param targetId element id
* @param json payload JSON string
*/
public void emit(String type, String targetId, String json) {
ObserverCallback cb = callback;
if (cb == null) return;
Map<String, Object> payload = new LinkedHashMap<>();
payload.put("json", json == null ? "" : json);
try {
cb.onEvent(type == null ? "" : type, targetId == null ? "" : targetId, payload);
} catch (RuntimeException ex) {
System.err.println("[observer] callback failed: " + ex.getMessage());
ex.printStackTrace(System.err);
}
}
}
}

View File

@@ -0,0 +1,127 @@
package org.openautonomousconnection.luascript.fx;
import javafx.animation.AnimationTimer;
import javafx.application.Platform;
import org.openautonomousconnection.luascript.hosts.SchedulerHost;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
/**
* JavaFX-based scheduler providing setTimeout/setInterval/requestAnimationFrame.
*/
public final class FxSchedulerHost implements SchedulerHost, AutoCloseable {
private final ScheduledExecutorService scheduler;
private final AtomicLong seq = new AtomicLong(1);
private final ConcurrentHashMap<Long, ScheduledFuture<?>> scheduled = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Long, Runnable> rafCallbacks = new ConcurrentHashMap<>();
private final Set<Long> canceledRaf = ConcurrentHashMap.newKeySet();
private final AnimationTimer rafTimer;
public FxSchedulerHost() {
this.scheduler = Executors.newSingleThreadScheduledExecutor(r -> {
Thread t = new Thread(r, "oac-fx-scheduler");
t.setDaemon(true);
return t;
});
this.rafTimer = new AnimationTimer() {
@Override
public void handle(long now) {
// Drain RAF callbacks once per frame.
if (rafCallbacks.isEmpty()) return;
Map<Long, Runnable> snap = new ConcurrentHashMap<>(rafCallbacks);
rafCallbacks.clear();
for (Map.Entry<Long, Runnable> e : snap.entrySet()) {
long id = e.getKey();
Runnable cb = e.getValue();
if (canceledRaf.remove(id)) continue;
try {
cb.run();
} catch (RuntimeException ex) {
System.err.println("[scheduler.raf] callback failed: " + ex.getMessage());
ex.printStackTrace(System.err);
}
}
}
};
Platform.runLater(rafTimer::start);
}
@Override
public long setTimeout(long delayMillis, Runnable callback) {
if (delayMillis < 0) delayMillis = 0;
Runnable cb = Objects.requireNonNull(callback, "callback");
long id = seq.getAndIncrement();
ScheduledFuture<?> f = scheduler.schedule(() -> safeRun(cb, "timeout"), delayMillis, TimeUnit.MILLISECONDS);
scheduled.put(id, f);
return id;
}
@Override
public long setInterval(long intervalMillis, Runnable callback) {
if (intervalMillis <= 0) throw new IllegalArgumentException("intervalMillis must be > 0");
Runnable cb = Objects.requireNonNull(callback, "callback");
long id = seq.getAndIncrement();
ScheduledFuture<?> f = scheduler.scheduleAtFixedRate(() -> safeRun(cb, "interval"), intervalMillis, intervalMillis, TimeUnit.MILLISECONDS);
scheduled.put(id, f);
return id;
}
@Override
public boolean clear(long handle) {
ScheduledFuture<?> f = scheduled.remove(handle);
if (f == null) return false;
return f.cancel(false);
}
@Override
public long requestAnimationFrame(Runnable callback) {
Runnable cb = Objects.requireNonNull(callback, "callback");
long id = seq.getAndIncrement();
rafCallbacks.put(id, cb);
return id;
}
@Override
public boolean cancelAnimationFrame(long handle) {
// If already queued, mark as canceled.
boolean existed = rafCallbacks.remove(handle) != null;
canceledRaf.add(handle);
return existed;
}
private static void safeRun(Runnable cb, String kind) {
try {
cb.run();
} catch (RuntimeException ex) {
System.err.println("[scheduler." + kind + "] callback failed: " + ex.getMessage());
ex.printStackTrace(System.err);
}
}
@Override
public void close() {
try {
Platform.runLater(rafTimer::stop);
} catch (Exception ignored) {
// ignore
}
scheduler.shutdownNow();
scheduled.clear();
rafCallbacks.clear();
canceledRaf.clear();
}
}

View File

@@ -0,0 +1,114 @@
package org.openautonomousconnection.luascript.fx;
import javafx.scene.web.WebEngine;
import org.openautonomousconnection.luascript.hosts.SelectorHost;
import java.util.List;
import java.util.Objects;
/**
* CSS selector host implemented via internal JS bridge.
*/
public final class FxSelectorHost implements SelectorHost {
private final WebEngine engine;
private final FxDomHost dom;
public FxSelectorHost(WebEngine engine, FxDomHost dom) {
this.engine = Objects.requireNonNull(engine, "engine");
this.dom = Objects.requireNonNull(dom, "dom");
}
@Override
public String querySelector(String selector) {
Objects.requireNonNull(selector, "selector");
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
dom.requireDocument();
dom.ensureAllElementsHaveId();
String script = ""
+ "(function(){"
+ " var sel = " + FxWebBridge.toJsLiteral(selector) + ";"
+ " var el = document.querySelector(sel);"
+ " if(!el) return null;"
+ " if(!el.id){"
+ " el.id='__auto_' + Math.floor(Math.random()*1e18).toString(36);"
+ " }"
+ " return el.id;"
+ "})();";
Object ret = engine.executeScript(script);
return ret == null ? null : String.valueOf(ret);
}));
}
@Override
public List<String> querySelectorAll(String selector) {
Objects.requireNonNull(selector, "selector");
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
dom.requireDocument();
dom.ensureAllElementsHaveId();
String script = ""
+ "(function(){"
+ " var sel=" + FxWebBridge.toJsLiteral(selector) + ";"
+ " var els=document.querySelectorAll(sel);"
+ " var out=[];"
+ " for(var i=0;i<els.length;i++){"
+ " var el=els[i];"
+ " if(!el.id){ el.id='__auto_' + Math.floor(Math.random()*1e18).toString(36); }"
+ " out.push(el.id);"
+ " }"
+ " return out;"
+ "})();";
Object ret = engine.executeScript(script);
return FxWebBridge.toStringList(ret);
}));
}
@Override
public boolean matches(String elementId, String selector) {
Objects.requireNonNull(elementId, "elementId");
Objects.requireNonNull(selector, "selector");
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
dom.requireDocument();
String script = ""
+ "(function(){"
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
+ " var sel=" + FxWebBridge.toJsLiteral(selector) + ";"
+ " var el=document.getElementById(id);"
+ " if(!el) throw new Error('Unknown element id: '+id);"
+ " return !!el.matches(sel);"
+ "})();";
Object ret = engine.executeScript(script);
return ret instanceof Boolean b ? b : Boolean.parseBoolean(String.valueOf(ret));
}));
}
@Override
public String closest(String elementId, String selector) {
Objects.requireNonNull(elementId, "elementId");
Objects.requireNonNull(selector, "selector");
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
dom.requireDocument();
String script = ""
+ "(function(){"
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
+ " var sel=" + FxWebBridge.toJsLiteral(selector) + ";"
+ " var el=document.getElementById(id);"
+ " if(!el) throw new Error('Unknown element id: '+id);"
+ " var c=el.closest(sel);"
+ " if(!c) return null;"
+ " if(!c.id){ c.id='__auto_' + Math.floor(Math.random()*1e18).toString(36); }"
+ " return c.id;"
+ "})();";
Object ret = engine.executeScript(script);
return ret == null ? null : String.valueOf(ret);
}));
}
}

View File

@@ -0,0 +1,157 @@
package org.openautonomousconnection.luascript.fx;
import javafx.scene.web.WebEngine;
import org.openautonomousconnection.luascript.hosts.StorageHost;
import java.util.List;
import java.util.Objects;
/**
* Storage host backed by localStorage/sessionStorage via JS bridge.
*/
public final class FxStorageHost implements StorageHost {
private final WebEngine engine;
private final FxDomHost dom;
public FxStorageHost(WebEngine engine, FxDomHost dom) {
this.engine = Objects.requireNonNull(engine, "engine");
this.dom = Objects.requireNonNull(dom, "dom");
}
@Override
public List<String> localKeys() {
return keys("localStorage");
}
@Override
public String localGet(String key) {
return get("localStorage", key);
}
@Override
public void localSet(String key, String value) {
set("localStorage", key, value);
}
@Override
public void localRemove(String key) {
remove("localStorage", key);
}
@Override
public void localClear() {
clear("localStorage");
}
@Override
public List<String> sessionKeys() {
return keys("sessionStorage");
}
@Override
public String sessionGet(String key) {
return get("sessionStorage", key);
}
@Override
public void sessionSet(String key, String value) {
set("sessionStorage", key, value);
}
@Override
public void sessionRemove(String key) {
remove("sessionStorage", key);
}
@Override
public void sessionClear() {
clear("sessionStorage");
}
private List<String> keys(String storageName) {
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
dom.requireDocument();
String script = ""
+ "(function(){"
+ " var s=window[" + FxWebBridge.toJsLiteral(storageName) + "];"
+ " if(!s) return [];"
+ " var out=[];"
+ " for(var i=0;i<s.length;i++){ out.push(String(s.key(i))); }"
+ " return out;"
+ "})();";
Object ret = engine.executeScript(script);
return FxWebBridge.toStringList(ret);
}));
}
private String get(String storageName, String key) {
Objects.requireNonNull(key, "key");
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
dom.requireDocument();
String script = ""
+ "(function(){"
+ " var s=window[" + FxWebBridge.toJsLiteral(storageName) + "];"
+ " if(!s) return null;"
+ " var v=s.getItem(" + FxWebBridge.toJsLiteral(key) + ");"
+ " return v===null?null:String(v);"
+ "})();";
Object ret = engine.executeScript(script);
return ret == null ? null : String.valueOf(ret);
}));
}
private void set(String storageName, String key, String value) {
Objects.requireNonNull(key, "key");
FxThreadBridge.runAndWait(() -> FxWebBridge.runWithJs(engine, () -> {
dom.requireDocument();
if (value == null) {
String script = ""
+ "(function(){"
+ " var s=window[" + FxWebBridge.toJsLiteral(storageName) + "];"
+ " if(!s) return null;"
+ " s.removeItem(" + FxWebBridge.toJsLiteral(key) + ");"
+ " return null;"
+ "})();";
engine.executeScript(script);
return;
}
String script = ""
+ "(function(){"
+ " var s=window[" + FxWebBridge.toJsLiteral(storageName) + "];"
+ " if(!s) return null;"
+ " s.setItem(" + FxWebBridge.toJsLiteral(key) + "," + FxWebBridge.toJsLiteral(value) + ");"
+ " return null;"
+ "})();";
engine.executeScript(script);
}));
}
private void remove(String storageName, String key) {
Objects.requireNonNull(key, "key");
set(storageName, key, null);
}
private void clear(String storageName) {
FxThreadBridge.runAndWait(() -> FxWebBridge.runWithJs(engine, () -> {
dom.requireDocument();
String script = ""
+ "(function(){"
+ " var s=window[" + FxWebBridge.toJsLiteral(storageName) + "];"
+ " if(!s) return null;"
+ " s.clear();"
+ " return null;"
+ "})();";
engine.executeScript(script);
}));
}
}

View File

@@ -0,0 +1,137 @@
package org.openautonomousconnection.luascript.fx;
import javafx.scene.web.WebEngine;
import org.openautonomousconnection.luascript.hosts.UtilHost;
import java.net.URI;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.*;
/**
* Utility host: Base64, random, URL parsing, query parsing, JSON stringify/normalize via JS.
*/
public final class FxUtilHost implements UtilHost {
private final WebEngine engine;
private final FxDomHost dom;
private final SecureRandom rng = new SecureRandom();
public FxUtilHost(WebEngine engine, FxDomHost dom) {
this.engine = Objects.requireNonNull(engine, "engine");
this.dom = Objects.requireNonNull(dom, "dom");
}
@Override
public String base64Encode(String text) {
String s = text == null ? "" : text;
return Base64.getEncoder().encodeToString(s.getBytes(StandardCharsets.UTF_8));
}
@Override
public String base64Decode(String base64) {
if (base64 == null) return "";
byte[] b = Base64.getDecoder().decode(base64);
return new String(b, StandardCharsets.UTF_8);
}
@Override
public String randomHex(int numBytes) {
if (numBytes <= 0) throw new IllegalArgumentException("numBytes must be > 0");
byte[] b = new byte[numBytes];
rng.nextBytes(b);
StringBuilder sb = new StringBuilder(numBytes * 2);
for (byte x : b) sb.append(String.format("%02x", x));
return sb.toString();
}
@Override
public Map<String, String> parseUrl(String url) {
Objects.requireNonNull(url, "url");
URI u = URI.create(url.trim());
Map<String, String> out = new LinkedHashMap<>();
out.put("scheme", safe(u.getScheme()));
out.put("host", safe(u.getHost()));
out.put("port", u.getPort() < 0 ? "" : String.valueOf(u.getPort()));
out.put("path", safe(u.getPath()));
out.put("query", safe(u.getQuery()));
out.put("fragment", safe(u.getFragment()));
return out;
}
@Override
public Map<String, List<String>> parseQuery(String query) {
String q = query == null ? "" : query.trim();
if (q.startsWith("?")) q = q.substring(1);
Map<String, List<String>> out = new LinkedHashMap<>();
if (q.isEmpty()) return out;
for (String part : q.split("&")) {
if (part.isEmpty()) continue;
String k;
String v;
int idx = part.indexOf('=');
if (idx < 0) {
k = decode(part);
v = "";
} else {
k = decode(part.substring(0, idx));
v = decode(part.substring(idx + 1));
}
out.computeIfAbsent(k, __ -> new ArrayList<>()).add(v);
}
return out;
}
@Override
public String jsonStringifyExpr(String elementId, String jsExpr) {
Objects.requireNonNull(elementId, "elementId");
Objects.requireNonNull(jsExpr, "jsExpr");
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
dom.requireDocument();
// NOTE: jsExpr is intentionally raw JS expression; this is a trusted host API.
// If you need untrusted usage, wrap/validate upstream.
String script = ""
+ "(function(){"
+ " var v=(" + jsExpr + ");"
+ " return JSON.stringify(v);"
+ "})();";
Object ret = engine.executeScript(script);
return ret == null ? "null" : String.valueOf(ret);
}));
}
@Override
public String jsonNormalize(String elementId, String json) {
Objects.requireNonNull(elementId, "elementId");
Objects.requireNonNull(json, "json");
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
dom.requireDocument();
String script = ""
+ "(function(){"
+ " var s=" + FxWebBridge.toJsLiteral(json) + ";"
+ " var obj=JSON.parse(s);"
+ " return JSON.stringify(obj);"
+ "})();";
Object ret = engine.executeScript(script);
return ret == null ? "null" : String.valueOf(ret);
}));
}
private static String safe(String s) {
return s == null ? "" : s;
}
private static String decode(String s) {
return URLDecoder.decode(s, StandardCharsets.UTF_8);
}
}

View File

@@ -0,0 +1,313 @@
package org.openautonomousconnection.luascript.fx;
import javafx.scene.web.WebEngine;
import org.openautonomousconnection.luascript.hosts.VideoHost;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
/**
* VideoHost implementation that renders video inside the WebView DOM using a {@code <video>} element.
*
* <p>No JavaFX overlay panes are used. Positioning is done by setting CSS via {@code style} attribute.</p>
*
* <p>Playback control is done via {@link FxDomHost#setProperty(String, String, Object)} and
* {@link FxDomHost#call(String, String, Object...)}, i.e. HTMLMediaElement methods.</p>
*
* <p>For {@code web://} URLs this host downloads the resource via {@link URLConnection} (your installed handler),
* stores it as a temporary file, then sets {@code src=file://...} because WebView won't reliably load custom schemes.</p>
*/
public final class FxVideoHost implements VideoHost {
private static final Set<String> VIDEO_EXTENSIONS = Set.of(
"mp4", "mov", "ogg", "ogv", "gif", "gifv", "avi", "m4v"
);
private static final String VIDEO_ID = "__oac_video";
private final WebEngine engine;
private final FxDomHost dom;
private volatile boolean loop;
private volatile double volume = 1.0;
private volatile double x;
private volatile double y;
private volatile double w = 640;
private volatile double h = 360;
private volatile Path lastTempFile;
/**
* Creates a new video host.
*
* @param engine web engine
* @param dom fx dom host
*/
public FxVideoHost(WebEngine engine, FxDomHost dom) {
this.engine = Objects.requireNonNull(engine, "engine");
this.dom = Objects.requireNonNull(dom, "dom");
}
@Override
public void playFile(File file) {
Objects.requireNonNull(file, "file");
if (!file.isFile()) {
throw new IllegalArgumentException("Video file not found: " + file.getAbsolutePath());
}
String ext = MediaExtensions.extensionOf(file.getName());
ensureAllowed(ext, "video", file.getName());
setSource(file.toURI().toString(), null);
resume();
}
@Override
public void playUrl(String url) {
Objects.requireNonNull(url, "url");
String u = url.trim();
if (u.isEmpty()) throw new IllegalArgumentException("URL is empty");
URI uri = URI.create(u);
String ext = MediaExtensions.extensionOf(uri.getPath());
ensureAllowed(ext, "video", uri.getPath());
String scheme = (uri.getScheme() == null) ? "" : uri.getScheme().toLowerCase(Locale.ROOT);
if ("http".equals(scheme) || "https".equals(scheme) || "file".equals(scheme)) {
setSource(uri.toString(), null);
resume();
return;
}
if ("web".equals(scheme)) {
Path tmp = null;
try {
tmp = downloadToTempFile(u, ext, "oac-video-");
rememberTemp(tmp);
setSource(tmp.toUri().toString(), tmp);
resume();
return;
} catch (IOException e) {
safeDelete(tmp);
throw new RuntimeException("Failed to load web video: " + e.getMessage(), e);
}
}
throw new IllegalArgumentException("Unsupported scheme: " + scheme);
}
@Override
public void pause() {
FxThreadBridge.runAndWait(() -> {
ensureVideoElement();
dom.call(VIDEO_ID, "pause");
});
}
@Override
public void resume() {
FxThreadBridge.runAndWait(() -> {
Element v = ensureVideoElement();
applyMediaProps();
v.setAttribute("style", mergeStyle(baseStyle(true), rectStyle(), visualStyle()));
dom.call(VIDEO_ID, "play");
});
}
@Override
public void stop() {
FxThreadBridge.runAndWait(() -> {
Element v = ensureVideoElement();
dom.call(VIDEO_ID, "pause");
dom.setProperty(VIDEO_ID, "currentTime", 0);
v.removeAttribute("src");
v.setAttribute("style", mergeStyle(baseStyle(false), rectStyle(), visualStyle()));
});
Path tmp = this.lastTempFile;
this.lastTempFile = null;
safeDelete(tmp);
}
@Override
public void hide() {
FxThreadBridge.runAndWait(() -> {
Element v = ensureVideoElement();
v.setAttribute("style", mergeStyle(baseStyle(false), rectStyle(), visualStyle()));
});
}
@Override
public void setRect(double x, double y, double w, double h) {
this.x = x;
this.y = y;
this.w = Math.max(0.0, w);
this.h = Math.max(0.0, h);
FxThreadBridge.runAndWait(() -> {
Element v = ensureVideoElement();
boolean visible = "block".equalsIgnoreCase(extractDisplay(v.getAttribute("style")));
v.setAttribute("style", mergeStyle(baseStyle(visible), rectStyle(), visualStyle()));
});
}
@Override
public void setVolume(double volume) {
this.volume = clamp01(volume);
FxThreadBridge.runAndWait(this::applyMediaProps);
}
@Override
public void setLoop(boolean loop) {
this.loop = loop;
FxThreadBridge.runAndWait(this::applyMediaProps);
}
@Override
public void seek(double seconds) {
double s = Math.max(0.0, seconds);
FxThreadBridge.runAndWait(() -> {
ensureVideoElement();
dom.setProperty(VIDEO_ID, "currentTime", s);
});
}
private void setSource(String src, Path tempFileKept) {
FxThreadBridge.runAndWait(() -> {
Element v = ensureVideoElement();
v.setAttribute("src", src);
applyMediaProps();
v.setAttribute("style", mergeStyle(baseStyle(true), rectStyle(), visualStyle()));
});
if (tempFileKept != null) {
rememberTemp(tempFileKept);
}
}
private void applyMediaProps() {
ensureVideoElement();
dom.setProperty(VIDEO_ID, "loop", loop);
dom.setProperty(VIDEO_ID, "volume", volume);
}
private Element ensureVideoElement() {
Document doc = dom.requireDocument();
Element v = doc.getElementById(VIDEO_ID);
if (v != null) return v;
Element body = (Element) doc.getElementsByTagName("body").item(0);
if (body == null) throw new IllegalStateException("No <body> element available");
v = doc.createElement("video");
v.setAttribute("id", VIDEO_ID);
// Default: no controls; script can enable via dom.setAttr(id,"controls","controls") if needed.
v.setAttribute("preload", "metadata");
body.appendChild(v);
return v;
}
private String baseStyle(boolean visible) {
return "position:fixed;"
+ "left:0;top:0;"
+ "z-index:2147483647;"
+ "background-color:black;"
+ "display:" + (visible ? "block" : "none") + ";";
}
private String rectStyle() {
return "left:" + x + "px;"
+ "top:" + y + "px;"
+ "width:" + w + "px;"
+ "height:" + h + "px;";
}
private String visualStyle() {
return "object-fit:contain;";
}
private static String mergeStyle(String... parts) {
StringBuilder sb = new StringBuilder(256);
for (String p : parts) {
if (p == null || p.isBlank()) continue;
String s = p.trim();
if (!s.endsWith(";")) s += ";";
sb.append(s);
}
return sb.toString();
}
private static String extractDisplay(String style) {
if (style == null || style.isBlank()) return "";
String[] parts = style.split(";");
for (String part : parts) {
String p = part.trim();
if (p.isEmpty()) continue;
int idx = p.indexOf(':');
if (idx <= 0) continue;
String k = p.substring(0, idx).trim().toLowerCase(Locale.ROOT);
if ("display".equals(k)) return p.substring(idx + 1).trim().toLowerCase(Locale.ROOT);
}
return "";
}
private static Path downloadToTempFile(String url, String ext, String prefix) throws IOException {
URL u = new URL(url);
URLConnection con = u.openConnection();
con.setUseCaches(false);
String safeExt = (ext == null || ext.isBlank()) ? "bin" : ext.toLowerCase(Locale.ROOT);
Path tmp = Files.createTempFile(prefix, "." + safeExt);
try (InputStream in = con.getInputStream()) {
Files.copy(in, tmp, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
safeDelete(tmp);
throw e;
}
return tmp;
}
private void rememberTemp(Path tmp) {
Path prev = this.lastTempFile;
this.lastTempFile = tmp;
safeDelete(prev);
}
private static void safeDelete(Path p) {
if (p == null) return;
try {
Files.deleteIfExists(p);
} catch (IOException ignored) {
// best-effort
}
}
private static void ensureAllowed(String ext, String kind, String source) {
String e = (ext == null) ? "" : ext.toLowerCase(Locale.ROOT);
if (e.isEmpty() || !VIDEO_EXTENSIONS.contains(e)) {
throw new IllegalArgumentException("Unsupported " + kind + " format '" + e + "' for: " + source);
}
}
private static double clamp01(double v) {
if (v < 0.0) return 0.0;
if (v > 1.0) return 1.0;
return v;
}
}

View File

@@ -0,0 +1,227 @@
package org.openautonomousconnection.luascript.fx;
import javafx.scene.web.WebEngine;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;
/**
* Shared utilities for WebEngine JS bridging.
*/
public final class FxWebBridge {
private FxWebBridge() {
}
/**
* Ensures JavaScript is enabled for the engine.
*
* @param engine engine
*/
public static void ensureJsEnabled(WebEngine engine) {
Objects.requireNonNull(engine, "engine");
if (!engine.isJavaScriptEnabled()) {
engine.setJavaScriptEnabled(true);
}
}
/**
* Executes bridge code with JavaScript temporarily enabled.
*
* @param engine engine
* @param action action
*/
public static void runWithJs(WebEngine engine, Runnable action) {
Objects.requireNonNull(action, "action");
callWithJs(engine, () -> {
action.run();
return null;
});
}
/**
* Executes bridge code with JavaScript temporarily enabled and restores the prior state.
*
* @param engine engine
* @param action action
* @param <T> result type
* @return action result
*/
public static <T> T callWithJs(WebEngine engine, Supplier<T> action) {
Objects.requireNonNull(engine, "engine");
Objects.requireNonNull(action, "action");
boolean enabledBefore = engine.isJavaScriptEnabled();
if (!enabledBefore) {
engine.setJavaScriptEnabled(true);
}
try {
return action.get();
} finally {
if (!enabledBefore) {
engine.setJavaScriptEnabled(false);
}
}
}
/**
* Converts a Java value into a safe JavaScript literal.
*
* <p>Supported: null, String, Boolean, Number (finite).</p>
*
* @param v value
* @return JS literal
*/
public static String toJsLiteral(Object v) {
if (v == null) return "null";
if (v instanceof String s) {
return "'" + escapeJsSingleQuotedString(s) + "'";
}
if (v instanceof Boolean b) {
return b ? "true" : "false";
}
if (v instanceof Number n) {
double d = n.doubleValue();
if (!Double.isFinite(d)) {
throw new IllegalArgumentException("Non-finite number is not supported for JS literal: " + d);
}
return Double.toString(d);
}
throw new IllegalArgumentException("Unsupported value type for JS literal: " + v.getClass().getName());
}
/**
* Builds a JavaScript array literal from Java values.
*
* @param values values
* @return array literal
*/
public static String toJsArrayLiteral(Object[] values) {
if (values == null || values.length == 0) return "[]";
StringBuilder sb = new StringBuilder();
sb.append('[');
for (int i = 0; i < values.length; i++) {
if (i > 0) sb.append(',');
sb.append(toJsLiteral(values[i]));
}
sb.append(']');
return sb.toString();
}
/**
* Restricts input to a plain JavaScript identifier (prevents injection).
*
* @param s value
* @param label label
* @return identifier
*/
public static String requireJsIdentifier(String s, String label) {
if (s == null) throw new IllegalArgumentException(label + " is null");
String v = s.trim();
if (v.isEmpty()) throw new IllegalArgumentException(label + " is blank");
if (!v.matches("^[A-Za-z_$][A-Za-z0-9_$]*$")) {
throw new IllegalArgumentException(label + " must be a JS identifier: " + v);
}
return v;
}
/**
* Normalizes alignment options for scrollIntoView.
*
* @param align input
* @return normalized
*/
public static String normalizeAlign(String align) {
if (align == null) return "nearest";
String a = align.trim().toLowerCase(Locale.ROOT);
return switch (a) {
case "start", "center", "end", "nearest" -> a;
default -> "nearest";
};
}
/**
* Converts a JS array-like object into a Java string list.
*
* @param jsValue raw JS value
* @return list of string values
*/
public static List<String> toStringList(Object jsValue) {
if (jsValue == null) return List.of();
if (jsValue instanceof List<?> list) {
List<String> out = new ArrayList<>(list.size());
for (Object value : list) out.add(value == null ? null : String.valueOf(value));
return out;
}
try {
Class<?> jsObj = Class.forName("netscape.javascript.JSObject");
if (jsObj.isInstance(jsValue)) {
Object lenObj = jsObj.getMethod("getMember", String.class).invoke(jsValue, "length");
int len = Integer.parseInt(String.valueOf(lenObj));
List<String> out = new ArrayList<>(len);
for (int i = 0; i < len; i++) {
Object value = jsObj.getMethod("getSlot", int.class).invoke(jsValue, i);
out.add(value == null ? null : String.valueOf(value));
}
return out;
}
} catch (Exception ignored) {
// best-effort fallback below
}
return List.of(String.valueOf(jsValue));
}
/**
* Converts a JS object into a Java string-object map when possible.
*
* @param jsValue raw JS value
* @return mapped values
*/
public static Map<String, Object> toStringObjectMap(Object jsValue) {
if (jsValue == null) return Map.of();
if (jsValue instanceof Map<?, ?> map) {
Map<String, Object> out = new LinkedHashMap<>();
for (Map.Entry<?, ?> entry : map.entrySet()) {
if (entry.getKey() == null) continue;
out.put(String.valueOf(entry.getKey()), entry.getValue());
}
return out;
}
return Map.of("value", jsValue);
}
private static String escapeJsSingleQuotedString(String s) {
if (s == null || s.isEmpty()) return "";
StringBuilder out = new StringBuilder(s.length() + 16);
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
switch (c) {
case '\'' -> out.append("\\'");
case '\\' -> out.append("\\\\");
case '\n' -> out.append("\\n");
case '\r' -> out.append("\\r");
case '\t' -> out.append("\\t");
case '\u0000' -> out.append("\\0");
case '\u2028' -> out.append("\\u2028");
case '\u2029' -> out.append("\\u2029");
default -> {
if (c < 0x20) out.append(String.format("\\u%04x", (int) c));
else out.append(c);
}
}
}
return out.toString();
}
}

View File

@@ -0,0 +1,34 @@
package org.openautonomousconnection.luascript.fx;
import java.util.Locale;
/**
* Shared extension helper for media sources.
*/
public final class MediaExtensions {
/**
* Extracts a lower-case extension without dot, or empty string if none.
*
* @param pathOrName path or file name
* @return extension without dot, lower-case, or empty string
*/
public static String extensionOf(String pathOrName) {
if (pathOrName == null) return "";
String s = pathOrName.trim();
if (s.isEmpty()) return "";
int q = s.indexOf('?');
if (q >= 0) s = s.substring(0, q);
int h = s.indexOf('#');
if (h >= 0) s = s.substring(0, h);
int slash = Math.max(s.lastIndexOf('/'), s.lastIndexOf('\\'));
String name = (slash >= 0) ? s.substring(slash + 1) : s;
int dot = name.lastIndexOf('.');
if (dot < 0 || dot == name.length() - 1) return "";
return name.substring(dot + 1).toLowerCase(Locale.ROOT);
}
}

View File

@@ -2,8 +2,53 @@ package org.openautonomousconnection.luascript.hosts;
import java.io.File; import java.io.File;
/**
* Host services for audio playback.
*/
public interface AudioHost { public interface AudioHost {
void play(File audioFile); /**
* Plays an audio file from the local filesystem.
*
* @param file local audio file
*/
void playFile(File file);
} /**
* Plays an audio resource identified by a URL string.
*
* <p>Supported schemes depend on the host implementation. Typical schemes:</p>
* <ul>
* <li>http://, https://</li>
* <li>file://</li>
* <li>web:// (custom, via installed URL handler)</li>
* </ul>
*
* @param url URL string
*/
void playUrl(String url);
/**
* Pauses playback (if any).
*/
void pause();
/**
* Stops playback (if any).
*/
void stop();
/**
* Sets volume in range [0.0, 1.0].
*
* @param volume volume
*/
void setVolume(double volume);
/**
* Enables/disables looping.
*
* @param loop true to loop
*/
void setLoop(boolean loop);
}

View File

@@ -0,0 +1,21 @@
package org.openautonomousconnection.luascript.hosts;
/**
* Clipboard access.
*/
public interface ClipboardHost {
/**
* Sets clipboard text.
*
* @param text text
*/
void setText(String text);
/**
* Gets clipboard text.
*
* @return text or empty string if none
*/
String getText();
}

View File

@@ -0,0 +1,63 @@
package org.openautonomousconnection.luascript.hosts;
import java.util.Map;
/**
* CSSOM / computed styles access.
*/
public interface CssHost {
/**
* Gets computed style value for a property.
*
* @param elementId element id
* @param property CSS property (kebab-case or camelCase accepted by browser)
* @return computed value (never null, may be empty)
*/
String getComputedStyle(String elementId, String property);
/**
* Returns a computed style snapshot of selected properties.
*
* @param elementId element id
* @param properties properties to fetch
* @return map property->value
*/
Map<String, String> getComputedStyles(String elementId, String[] properties);
/**
* Sets an inline style property (style attribute).
*
* @param elementId element id
* @param property CSS property
* @param value CSS value (empty => remove)
*/
void setInlineStyle(String elementId, String property, String value);
/**
* Gets an inline style property (from style attribute).
*
* @param elementId element id
* @param property CSS property
* @return inline value (may be empty)
*/
String getInlineStyle(String elementId, String property);
/**
* Sets a CSS variable (custom property) on an element.
*
* @param elementId element id
* @param name variable name (e.g. "--primary")
* @param value value
*/
void setCssVariable(String elementId, String name, String value);
/**
* Gets a CSS variable (computed) from an element.
*
* @param elementId element id
* @param name variable name (e.g. "--primary")
* @return computed var (may be empty)
*/
String getCssVariable(String elementId, String name);
}

View File

@@ -147,4 +147,19 @@ public interface DomHost {
* @return list of ids (never null) * @return list of ids (never null)
*/ */
List<String> queryByClass(String className); List<String> queryByClass(String className);
/**
* Sets a DOM property (e.g. video.currentTime, video.volume).
*/
void setProperty(String elementId, String property, Object value);
/**
* Gets a DOM property.
*/
Object getProperty(String elementId, String property);
/**
* Invokes a DOM method (e.g. video.play()).
*/
Object call(String elementId, String method, Object... args);
} }

View File

@@ -0,0 +1,42 @@
package org.openautonomousconnection.luascript.hosts;
import java.util.Map;
/**
* Layout and scrolling related accessors.
*
* <p>All results are plain maps to keep dependencies minimal.</p>
*/
public interface GeometryHost {
/**
* Returns bounding client rect of an element.
*
* @param elementId element id
* @return map: x,y,width,height,top,left,right,bottom
*/
Map<String, Object> getBoundingClientRect(String elementId);
/**
* Returns viewport metrics.
*
* @return map: width,height,devicePixelRatio,scrollX,scrollY
*/
Map<String, Object> getViewport();
/**
* Scrolls the document to coordinates.
*
* @param x scroll x
* @param y scroll y
*/
void scrollTo(double x, double y);
/**
* Scrolls an element into view.
*
* @param elementId element id
* @param align one of: "start","center","end","nearest" (null => "nearest")
*/
void scrollIntoView(String elementId, String align);
}

View File

@@ -10,42 +10,24 @@ import java.util.Optional;
*/ */
public interface HostServices { public interface HostServices {
/**
* Returns an optional UI host.
*
* @return ui host
*/
Optional<UiHost> ui(); Optional<UiHost> ui();
/**
* Returns an optional DOM host.
*
* @return dom host
*/
Optional<DomHost> dom(); Optional<DomHost> dom();
/**
* Returns an optional event host.
*
* @return event host
*/
Optional<EventHost> events(); Optional<EventHost> events();
/**
* Returns an optional resource host.
*
* @return resource host
*/
Optional<ResourceHost> resources(); Optional<ResourceHost> resources();
/**
* Returns an optional console host.
*
* @return console host
*/
Optional<ConsoleHost> console(); Optional<ConsoleHost> console();
Optional<AudioHost> audio(); Optional<AudioHost> audio();
Optional<ImageHost> image();
Optional<VideoHost> video();
/* NEW */
Optional<SchedulerHost> scheduler();
Optional<SelectorHost> selector();
Optional<GeometryHost> geometry();
Optional<CssHost> css();
Optional<StorageHost> storage();
Optional<UtilHost> util();
Optional<ClipboardHost> clipboard();
Optional<ObserverHost> observers();
/** /**
* Simple immutable implementation. * Simple immutable implementation.
@@ -56,55 +38,73 @@ public interface HostServices {
private final EventHost events; private final EventHost events;
private final ResourceHost resources; private final ResourceHost resources;
private final ConsoleHost console; private final ConsoleHost console;
private final AudioHost audioHost;
/** private final AudioHost audioHost;
* Creates a HostServices container. private final ImageHost imageHost;
* private final VideoHost videoHost;
* @param ui ui host
* @param dom dom host private final SchedulerHost schedulerHost;
* @param events event host private final SelectorHost selectorHost;
* @param resources resource host private final GeometryHost geometryHost;
* @param console console host private final CssHost cssHost;
*/ private final StorageHost storageHost;
public Default(UiHost ui, DomHost dom, EventHost events, ResourceHost resources, ConsoleHost console, AudioHost audioHost) { private final UtilHost utilHost;
private final ClipboardHost clipboardHost;
private final ObserverHost observerHost;
public Default(
UiHost ui,
DomHost dom,
EventHost events,
ResourceHost resources,
ConsoleHost console,
AudioHost audioHost,
ImageHost imageHost,
VideoHost videoHost,
SchedulerHost schedulerHost,
SelectorHost selectorHost,
GeometryHost geometryHost,
CssHost cssHost,
StorageHost storageHost,
UtilHost utilHost,
ClipboardHost clipboardHost,
ObserverHost observerHost
) {
this.ui = ui; this.ui = ui;
this.dom = dom; this.dom = dom;
this.events = events; this.events = events;
this.resources = resources; this.resources = resources;
this.console = console; this.console = console;
this.audioHost = audioHost; this.audioHost = audioHost;
this.imageHost = imageHost;
this.videoHost = videoHost;
this.schedulerHost = schedulerHost;
this.selectorHost = selectorHost;
this.geometryHost = geometryHost;
this.cssHost = cssHost;
this.storageHost = storageHost;
this.utilHost = utilHost;
this.clipboardHost = clipboardHost;
this.observerHost = observerHost;
} }
@Override @Override public Optional<UiHost> ui() { return Optional.ofNullable(ui); }
public Optional<UiHost> ui() { @Override public Optional<DomHost> dom() { return Optional.ofNullable(dom); }
return Optional.ofNullable(ui); @Override public Optional<EventHost> events() { return Optional.ofNullable(events); }
} @Override public Optional<ResourceHost> resources() { return Optional.ofNullable(resources); }
@Override public Optional<ConsoleHost> console() { return Optional.ofNullable(console); }
@Override public Optional<AudioHost> audio() { return Optional.ofNullable(audioHost); }
@Override public Optional<ImageHost> image() { return Optional.ofNullable(imageHost); }
@Override public Optional<VideoHost> video() { return Optional.ofNullable(videoHost); }
@Override @Override public Optional<SchedulerHost> scheduler() { return Optional.ofNullable(schedulerHost); }
public Optional<DomHost> dom() { @Override public Optional<SelectorHost> selector() { return Optional.ofNullable(selectorHost); }
return Optional.ofNullable(dom); @Override public Optional<GeometryHost> geometry() { return Optional.ofNullable(geometryHost); }
} @Override public Optional<CssHost> css() { return Optional.ofNullable(cssHost); }
@Override public Optional<StorageHost> storage() { return Optional.ofNullable(storageHost); }
@Override @Override public Optional<UtilHost> util() { return Optional.ofNullable(utilHost); }
public Optional<EventHost> events() { @Override public Optional<ClipboardHost> clipboard() { return Optional.ofNullable(clipboardHost); }
return Optional.ofNullable(events); @Override public Optional<ObserverHost> observers() { return Optional.ofNullable(observerHost); }
}
@Override
public Optional<ResourceHost> resources() {
return Optional.ofNullable(resources);
}
@Override
public Optional<ConsoleHost> console() {
return Optional.ofNullable(console);
}
@Override
public Optional<AudioHost> audio() {
return Optional.ofNullable(audioHost);
}
} }
/** /**
@@ -113,11 +113,6 @@ public interface HostServices {
final class StdoutConsole implements ConsoleHost { final class StdoutConsole implements ConsoleHost {
private final String prefix; private final String prefix;
/**
* Creates a new stdout console with a prefix.
*
* @param prefix prefix (may be empty)
*/
public StdoutConsole(String prefix) { public StdoutConsole(String prefix) {
this.prefix = Objects.requireNonNull(prefix, "prefix"); this.prefix = Objects.requireNonNull(prefix, "prefix");
} }
@@ -126,29 +121,10 @@ public interface HostServices {
return s == null ? "" : s; return s == null ? "" : s;
} }
@Override @Override public void info(String message) { System.out.println(prefix + "[info] " + safe(message)); }
public void info(String message) { @Override public void log(String message) { System.out.println(prefix + "[log] " + safe(message)); }
System.out.println(prefix + "[info] " + safe(message)); @Override public void warn(String message) { System.out.println(prefix + "[warn] " + safe(message)); }
} @Override public void error(String message) { System.err.println(prefix + "[error] " + safe(message)); }
@Override public void exception(String message) { System.err.println(prefix + "[exception] " + safe(message)); }
@Override
public void log(String message) {
System.out.println(prefix + "[log] " + safe(message));
}
@Override
public void warn(String message) {
System.out.println(prefix + "[warn] " + safe(message));
}
@Override
public void error(String message) {
System.err.println(prefix + "[error] " + safe(message));
}
@Override
public void exception(String message) {
System.err.println(prefix + "[exception] " + safe(message));
}
} }
} }

View File

@@ -0,0 +1,59 @@
package org.openautonomousconnection.luascript.hosts;
import java.io.File;
/**
* Host services for image rendering in an overlay layer above the WebView.
*/
public interface ImageHost {
/**
* Shows an image from the local filesystem.
*
* @param file image file
*/
void showFile(File file);
/**
* Shows an image from a URL string (http/https/file/web).
*
* @param url url string
*/
void showUrl(String url);
/**
* Hides the current image overlay.
*/
void hide();
/**
* Sets the drawing rectangle (in WebView pixel coordinates).
*
* @param x x
* @param y y
* @param w width
* @param h height
*/
void setRect(double x, double y, double w, double h);
/**
* If true, preserves aspect ratio within the rectangle.
*
* @param preserve preserve aspect ratio
*/
void setPreserveRatio(boolean preserve);
/**
* If true, image is smoothed when scaled.
*
* @param smooth smooth
*/
void setSmooth(boolean smooth);
/**
* Sets opacity in range [0..1].
*
* @param opacity opacity
*/
void setOpacity(double opacity);
}

View File

@@ -0,0 +1,80 @@
package org.openautonomousconnection.luascript.hosts;
import java.util.Map;
/**
* Reactive observers: Mutation/Resize/Intersection.
*
* <p>Observers emit events into Lua via a host-provided callback.</p>
*/
public interface ObserverHost {
/**
* Callback used by the host to notify observers.
*/
@FunctionalInterface
interface ObserverCallback {
/**
* Called on observer events.
*
* @param type observer type ("mutation","resize","intersection")
* @param targetId element id
* @param data payload map
*/
void onEvent(String type, String targetId, Map<String, Object> data);
}
/**
* Sets the callback to receive observer events.
*
* @param callback callback (nullable to disable)
*/
void setCallback(ObserverCallback callback);
/**
* Observes DOM mutations on an element (subtree).
*
* @param elementId element id
* @param subtree true to include subtree
* @param attributes true to observe attributes
* @param childList true to observe childList
* @param characterData true to observe text changes
*/
void observeMutations(String elementId, boolean subtree, boolean attributes, boolean childList, boolean characterData);
/**
* Stops mutation observing for an element.
*
* @param elementId element id
*/
void unobserveMutations(String elementId);
/**
* Observes resize changes of an element.
*
* @param elementId element id
*/
void observeResize(String elementId);
/**
* Stops resize observing for an element.
*
* @param elementId element id
*/
void unobserveResize(String elementId);
/**
* Observes intersection changes of an element with viewport.
*
* @param elementId element id
* @param threshold threshold in [0..1]
*/
void observeIntersection(String elementId, double threshold);
/**
* Stops intersection observing for an element.
*
* @param elementId element id
*/
void unobserveIntersection(String elementId);
}

View File

@@ -0,0 +1,53 @@
package org.openautonomousconnection.luascript.hosts;
/**
* Scheduling primitives comparable to JavaScript timers.
*
* <p>No networking. This host only provides time-based callbacks.</p>
*/
public interface SchedulerHost {
/**
* Schedules a one-shot callback after a delay.
*
* @param delayMillis delay in milliseconds (>= 0)
* @param callback callback to run on host-defined thread (typically Lua thread)
* @return handle id
*/
long setTimeout(long delayMillis, Runnable callback);
/**
* Schedules a repeating callback with fixed rate.
*
* @param intervalMillis interval in milliseconds (> 0)
* @param callback callback to run
* @return handle id
*/
long setInterval(long intervalMillis, Runnable callback);
/**
* Cancels a timeout/interval handle.
*
* @param handle handle id
* @return true if canceled
*/
boolean clear(long handle);
/**
* Schedules a callback for the next animation frame.
*
* <p>Comparable to requestAnimationFrame. The callback is invoked once.</p>
*
* @param callback callback to run
* @return handle id
*/
long requestAnimationFrame(Runnable callback);
/**
* Cancels a previously scheduled animation frame.
*
* @param handle handle id
* @return true if canceled
*/
boolean cancelAnimationFrame(long handle);
}

View File

@@ -0,0 +1,45 @@
package org.openautonomousconnection.luascript.hosts;
import java.util.List;
/**
* CSS selector based DOM querying/traversal.
*
* <p>All returned elements are identified by stable element ids.</p>
*/
public interface SelectorHost {
/**
* Returns the first element matching the selector, or null.
*
* @param selector CSS selector
* @return element id or null
*/
String querySelector(String selector);
/**
* Returns all elements matching the selector.
*
* @param selector CSS selector
* @return list of element ids (never null)
*/
List<String> querySelectorAll(String selector);
/**
* Checks if an element matches a selector.
*
* @param elementId element id
* @param selector CSS selector
* @return true if matches
*/
boolean matches(String elementId, String selector);
/**
* Returns the closest ancestor (including itself) matching selector, or null.
*
* @param elementId element id
* @param selector CSS selector
* @return closest element id or null
*/
String closest(String elementId, String selector);
}

View File

@@ -0,0 +1,67 @@
package org.openautonomousconnection.luascript.hosts;
import java.util.List;
/**
* Storage primitives comparable to localStorage/sessionStorage.
*/
public interface StorageHost {
/**
* @return keys in localStorage
*/
List<String> localKeys();
/**
* @param key key
* @return value or null
*/
String localGet(String key);
/**
* @param key key
* @param value value (null removes)
*/
void localSet(String key, String value);
/**
* Removes a key from localStorage.
*
* @param key key
*/
void localRemove(String key);
/**
* Clears localStorage.
*/
void localClear();
/**
* @return keys in sessionStorage
*/
List<String> sessionKeys();
/**
* @param key key
* @return value or null
*/
String sessionGet(String key);
/**
* @param key key
* @param value value (null removes)
*/
void sessionSet(String key, String value);
/**
* Removes a key from sessionStorage.
*
* @param key key
*/
void sessionRemove(String key);
/**
* Clears sessionStorage.
*/
void sessionClear();
}

View File

@@ -0,0 +1,70 @@
package org.openautonomousconnection.luascript.hosts;
import java.util.List;
import java.util.Map;
/**
* Utility helpers commonly available in browsers.
*
* <p>No networking.</p>
*/
public interface UtilHost {
/**
* Encodes bytes (UTF-8) as Base64.
*
* @param text text
* @return base64
*/
String base64Encode(String text);
/**
* Decodes Base64 into UTF-8 text.
*
* @param base64 base64
* @return decoded text
*/
String base64Decode(String base64);
/**
* Generates cryptographically-strong random bytes and returns as hex string.
*
* @param numBytes number of bytes (>0)
* @return hex string
*/
String randomHex(int numBytes);
/**
* Parses a URL string into components.
*
* @param url url string
* @return map containing scheme,host,port,path,query,fragment
*/
Map<String, String> parseUrl(String url);
/**
* Parses a query string into key->list(values).
*
* @param query query string (with or without leading '?')
* @return map key->values
*/
Map<String, List<String>> parseQuery(String query);
/**
* JSON.stringify via browser engine (returns JSON string).
*
* @param elementId element id that provides the JS context (ignored by some engines but kept for safety)
* @param jsExpr JS expression returning a JSON-serializable value
* @return JSON string
*/
String jsonStringifyExpr(String elementId, String jsExpr);
/**
* JSON.parse via browser engine and returns normalized JSON string (stringify(parse(x))).
*
* @param elementId element id providing JS context
* @param json json string
* @return normalized json
*/
String jsonNormalize(String elementId, String json);
}

View File

@@ -0,0 +1,74 @@
package org.openautonomousconnection.luascript.hosts;
import java.io.File;
/**
* Host services for video playback in an overlay layer above the WebView.
*/
public interface VideoHost {
/**
* Plays a video from the local filesystem.
*
* @param file video file
*/
void playFile(File file);
/**
* Plays a video from a URL string (http/https/file/web).
*
* @param url url string
*/
void playUrl(String url);
/**
* Pauses playback.
*/
void pause();
/**
* Resumes playback (if paused).
*/
void resume();
/**
* Stops playback and hides the view.
*/
void stop();
/**
* Hides the video view (does not necessarily stop decoding).
*/
void hide();
/**
* Sets the drawing rectangle (in WebView pixel coordinates).
*
* @param x x
* @param y y
* @param w width
* @param h height
*/
void setRect(double x, double y, double w, double h);
/**
* Sets volume in range [0..1].
*
* @param volume volume
*/
void setVolume(double volume);
/**
* Enables/disables looping.
*
* @param loop loop
*/
void setLoop(boolean loop);
/**
* Seeks to a position in seconds.
*
* @param seconds seconds
*/
void seek(double seconds);
}

View File

@@ -3,10 +3,7 @@ package org.openautonomousconnection.luascript.runtime;
import javafx.concurrent.Worker; import javafx.concurrent.Worker;
import javafx.scene.web.WebEngine; import javafx.scene.web.WebEngine;
import org.luaj.vm2.Globals; import org.luaj.vm2.Globals;
import org.openautonomousconnection.luascript.fx.FxDomHost; import org.openautonomousconnection.luascript.fx.*;
import org.openautonomousconnection.luascript.fx.FxEventHost;
import org.openautonomousconnection.luascript.fx.FxUiHost;
import org.openautonomousconnection.luascript.fx.FxWebViewResourceHost;
import org.openautonomousconnection.luascript.hosts.AudioHost; import org.openautonomousconnection.luascript.hosts.AudioHost;
import org.openautonomousconnection.luascript.hosts.HostServices; import org.openautonomousconnection.luascript.hosts.HostServices;
import org.openautonomousconnection.luascript.security.LuaExecutionPolicy; import org.openautonomousconnection.luascript.security.LuaExecutionPolicy;
@@ -52,7 +49,7 @@ public final class FxLuaScriptEngine implements AutoCloseable {
/** /**
* Installs a load hook that bootstraps Lua when a page finished loading. * Installs a load hook that bootstraps Lua when a page finished loading.
*/ */
public void install() { public void install(FxVideoHost videoHost, FxImageHost imageHost) {
engine.getLoadWorker().stateProperty().addListener((obs, oldState, newState) -> { engine.getLoadWorker().stateProperty().addListener((obs, oldState, newState) -> {
if (newState == Worker.State.SUCCEEDED) { if (newState == Worker.State.SUCCEEDED) {
bootstrapped.set(false); bootstrapped.set(false);
@@ -83,12 +80,27 @@ public final class FxLuaScriptEngine implements AutoCloseable {
.sandbox(true) .sandbox(true)
); );
HostServices.StdoutConsole console = new HostServices.StdoutConsole("[lua] ");
FxUiHost uiHost = new FxUiHost(engine, dom);
FxWebViewResourceHost resourceHost = new FxWebViewResourceHost(engine);
FxEventHost eventHost = new FxEventHost(dom); FxEventHost eventHost = new FxEventHost(dom);
// TODO: Default implementation or parameter for "audioHost"
HostServices services = new HostServices.Default(uiHost, dom, eventHost, resourceHost, console, audioHost); HostServices services = new HostServices.Default(
new FxUiHost(engine, dom),
dom,
eventHost,
new FxWebViewResourceHost(engine),
new HostServices.StdoutConsole("[lua] "),
new FxAudioHost(),
new FxImageHost(engine, dom),
new FxVideoHost(engine, dom),
new FxSchedulerHost(),
new FxSelectorHost(engine, dom),
new FxGeometryHost(engine, dom),
new FxCssHost(engine, dom),
new FxStorageHost(engine, dom),
new FxUtilHost(engine, dom),
new FxClipboardHost(),
new FxObserverHost(engine, dom)
);
LuaRuntime rt = new LuaRuntime(globals, services, policy); LuaRuntime rt = new LuaRuntime(globals, services, policy);
eventHost.setRouter(rt.eventRouter()); eventHost.setRouter(rt.eventRouter());

View File

@@ -5,9 +5,7 @@ import org.openautonomousconnection.luascript.events.LuaEventDispatcher;
import org.openautonomousconnection.luascript.hosts.HostServices; import org.openautonomousconnection.luascript.hosts.HostServices;
import org.openautonomousconnection.luascript.security.LuaExecutionPolicy; import org.openautonomousconnection.luascript.security.LuaExecutionPolicy;
import org.openautonomousconnection.luascript.security.LuaSecurityManager; import org.openautonomousconnection.luascript.security.LuaSecurityManager;
import org.openautonomousconnection.luascript.tables.DomTable; import org.openautonomousconnection.luascript.tables.*;
import org.openautonomousconnection.luascript.tables.EventsTable;
import org.openautonomousconnection.luascript.tables.UiTable;
import org.openautonomousconnection.luascript.tables.console.ConsoleTable; import org.openautonomousconnection.luascript.tables.console.ConsoleTable;
import java.util.Objects; import java.util.Objects;
@@ -51,6 +49,9 @@ public final class LuaRuntime implements AutoCloseable {
new ConsoleTable().inject(globals, services, overwrite); new ConsoleTable().inject(globals, services, overwrite);
new EventsTable(dispatcher).inject(globals, services, overwrite); new EventsTable(dispatcher).inject(globals, services, overwrite);
new DomTable().inject(globals, services, overwrite); new DomTable().inject(globals, services, overwrite);
new AudioTable().inject(globals, services, overwrite);
new VideoTable().inject(globals, services, overwrite);
new ImageTable().inject(globals, services, overwrite);
} }
public void bootstrapFromDom() { public void bootstrapFromDom() {

View File

@@ -1,7 +1,7 @@
package org.openautonomousconnection.luascript.security; package org.openautonomousconnection.luascript.security;
import org.luaj.vm2.*; import org.luaj.vm2.*;
import org.luaj.vm2.lib.VarArgFunction; import org.luaj.vm2.libs.VarArgFunction;
import org.openautonomousconnection.luascript.utils.LuaGlobalsFactory; import org.openautonomousconnection.luascript.utils.LuaGlobalsFactory;
import java.util.Objects; import java.util.Objects;

View File

@@ -1,29 +1,91 @@
package org.openautonomousconnection.luascript.tables; package org.openautonomousconnection.luascript.tables;
import org.luaj.vm2.LuaValue; import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.OneArgFunction; import org.luaj.vm2.libs.OneArgFunction;
import org.luaj.vm2.libs.ZeroArgFunction;
import org.openautonomousconnection.luascript.hosts.AudioHost; import org.openautonomousconnection.luascript.hosts.AudioHost;
import org.openautonomousconnection.luascript.hosts.HostServices; import org.openautonomousconnection.luascript.hosts.HostServices;
import org.openautonomousconnection.luascript.utils.ScriptTable; import org.openautonomousconnection.luascript.utils.ScriptTable;
import java.io.File; import java.io.File;
public class AudioTable extends ScriptTable { /**
protected AudioTable() { * Lua table: audio
*
* <p>Functions:</p>
* <ul>
* <li>audio.play(pathOrUrl)</li>
* <li>audio.pause()</li>
* <li>audio.stop()</li>
* <li>audio.volume(v) (0..1)</li>
* <li>audio.loop(boolean)</li>
* </ul>
*/
public final class AudioTable extends ScriptTable {
public AudioTable() {
super("audio"); super("audio");
} }
@Override @Override
protected void define(HostServices services) { protected void define(HostServices services) {
AudioHost audioHost = services.audio().orElseThrow(() -> new IllegalStateException("AudioHost not provided")); AudioHost audioHost = services.audio()
.orElseThrow(() -> new IllegalStateException("AudioHost not provided"));
table().set("play", new OneArgFunction() { table().set("play", new OneArgFunction() {
@Override @Override
public LuaValue call(LuaValue arg) { public LuaValue call(LuaValue arg) {
String fileName = arg.checkjstring(); String s = arg.checkjstring();
audioHost.play(new File(fileName));
if (looksLikeUrl(s)) {
audioHost.playUrl(s);
return LuaValue.NIL;
}
audioHost.playFile(new File(s));
return LuaValue.NIL;
}
});
table().set("pause", new ZeroArgFunction() {
@Override
public LuaValue call() {
audioHost.pause();
return LuaValue.NIL;
}
});
table().set("stop", new ZeroArgFunction() {
@Override
public LuaValue call() {
audioHost.stop();
return LuaValue.NIL;
}
});
table().set("volume", new OneArgFunction() {
@Override
public LuaValue call(LuaValue arg) {
double v = arg.checkdouble();
if (v < 0.0) v = 0.0;
if (v > 1.0) v = 1.0;
audioHost.setVolume(v);
return LuaValue.NIL;
}
});
table().set("loop", new OneArgFunction() {
@Override
public LuaValue call(LuaValue arg) {
audioHost.setLoop(arg.checkboolean());
return LuaValue.NIL; return LuaValue.NIL;
} }
}); });
} }
}
private static boolean looksLikeUrl(String s) {
if (s == null) return false;
int idx = s.indexOf("://");
return idx > 0 && idx < 16;
}
}

View File

@@ -0,0 +1,43 @@
package org.openautonomousconnection.luascript.tables;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.libs.*;
import org.openautonomousconnection.luascript.hosts.ClipboardHost;
import org.openautonomousconnection.luascript.hosts.HostServices;
import org.openautonomousconnection.luascript.utils.ScriptTable;
/**
* Lua table: clipboard
*
* <p>Functions:</p>
* <ul>
* <li>clipboard.set(text)</li>
* <li>clipboard.get() -> text</li>
* </ul>
*/
public final class ClipboardTable extends ScriptTable {
public ClipboardTable() {
super("clipboard");
}
@Override
protected void define(HostServices services) {
ClipboardHost host = services.clipboard().orElseThrow(() -> new IllegalStateException("ClipboardHost not provided"));
table().set("set", new OneArgFunction() {
@Override
public LuaValue call(LuaValue text) {
host.setText(text.isnil() ? "" : text.tojstring());
return LuaValue.NIL;
}
});
table().set("get", new ZeroArgFunction() {
@Override
public LuaValue call() {
return LuaValue.valueOf(host.getText());
}
});
}
}

View File

@@ -0,0 +1,86 @@
package org.openautonomousconnection.luascript.tables;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.libs.*;
import org.openautonomousconnection.luascript.events.JavaToLua;
import org.openautonomousconnection.luascript.hosts.CssHost;
import org.openautonomousconnection.luascript.hosts.HostServices;
import org.openautonomousconnection.luascript.utils.ScriptTable;
import java.util.Map;
/**
* Lua table: css
*
* <p>Functions:</p>
* <ul>
* <li>css.computed(id, prop) -> string</li>
* <li>css.computedMany(id, {props...}) -> map</li>
* <li>css.inlineGet(id, prop) -> string</li>
* <li>css.inlineSet(id, prop, value)</li>
* <li>css.varGet(id, name) -> string</li>
* <li>css.varSet(id, name, value)</li>
* </ul>
*/
public final class CssTable extends ScriptTable {
public CssTable() {
super("css");
}
@Override
protected void define(HostServices services) {
CssHost host = services.css().orElseThrow(() -> new IllegalStateException("CssHost not provided"));
table().set("computed", new TwoArgFunction() {
@Override
public LuaValue call(LuaValue id, LuaValue prop) {
return LuaValue.valueOf(host.getComputedStyle(id.checkjstring(), prop.checkjstring()));
}
});
table().set("computedMany", new TwoArgFunction() {
@Override
public LuaValue call(LuaValue id, LuaValue propsTable) {
if (!propsTable.istable()) throw new IllegalArgumentException("props must be a table");
int n = propsTable.length();
String[] props = new String[n];
for (int i = 1; i <= n; i++) {
props[i - 1] = propsTable.get(i).checkjstring();
}
Map<String, String> m = host.getComputedStyles(id.checkjstring(), props);
return JavaToLua.coerce(m);
}
});
table().set("inlineGet", new TwoArgFunction() {
@Override
public LuaValue call(LuaValue id, LuaValue prop) {
return LuaValue.valueOf(host.getInlineStyle(id.checkjstring(), prop.checkjstring()));
}
});
table().set("inlineSet", new ThreeArgFunction() {
@Override
public LuaValue call(LuaValue id, LuaValue prop, LuaValue value) {
host.setInlineStyle(id.checkjstring(), prop.checkjstring(), value.isnil() ? "" : value.tojstring());
return LuaValue.NIL;
}
});
table().set("varGet", new TwoArgFunction() {
@Override
public LuaValue call(LuaValue id, LuaValue name) {
return LuaValue.valueOf(host.getCssVariable(id.checkjstring(), name.checkjstring()));
}
});
table().set("varSet", new ThreeArgFunction() {
@Override
public LuaValue call(LuaValue id, LuaValue name, LuaValue value) {
host.setCssVariable(id.checkjstring(), name.checkjstring(), value.isnil() ? "" : value.tojstring());
return LuaValue.NIL;
}
});
}
}

View File

@@ -1,11 +1,8 @@
package org.openautonomousconnection.luascript.tables; package org.openautonomousconnection.luascript.tables;
import org.luaj.vm2.LuaTable; import org.luaj.vm2.*;
import org.luaj.vm2.LuaValue; import org.luaj.vm2.libs.*;
import org.luaj.vm2.lib.OneArgFunction; import org.openautonomousconnection.luascript.events.JavaToLua;
import org.luaj.vm2.lib.ThreeArgFunction;
import org.luaj.vm2.lib.TwoArgFunction;
import org.luaj.vm2.lib.ZeroArgFunction;
import org.openautonomousconnection.luascript.hosts.DomHost; import org.openautonomousconnection.luascript.hosts.DomHost;
import org.openautonomousconnection.luascript.hosts.HostServices; import org.openautonomousconnection.luascript.hosts.HostServices;
import org.openautonomousconnection.luascript.utils.ScriptTable; import org.openautonomousconnection.luascript.utils.ScriptTable;
@@ -35,6 +32,20 @@ public final class DomTable extends ScriptTable {
return t; return t;
} }
private static Object luaToJavaScalar(LuaValue v) {
if (v == null || v.isnil()) return null;
if (v.isboolean()) return v.toboolean();
if (v.isnumber()) {
// Preserve integer when possible; otherwise double.
if (v.isint()) return v.toint();
if (v.islong()) return v.tolong();
return v.todouble();
}
if (v.isstring()) return v.tojstring();
// Reject complex types for safety (no silent assumptions).
throw new LuaError("Unsupported value type for dom.setProp/call argument: " + v.typename());
}
@Override @Override
protected void define(HostServices services) { protected void define(HostServices services) {
DomHost dom = services.dom().orElseThrow(() -> new IllegalStateException("DomHost not provided")); DomHost dom = services.dom().orElseThrow(() -> new IllegalStateException("DomHost not provided"));
@@ -125,8 +136,7 @@ public final class DomTable extends ScriptTable {
table().set("children", new OneArgFunction() { table().set("children", new OneArgFunction() {
@Override @Override
public LuaValue call(LuaValue id) { public LuaValue call(LuaValue id) {
List<String> children = dom.getChildrenIds(id.checkjstring()); return toLuaArray(dom.getChildrenIds(id.checkjstring()));
return toLuaArray(children);
} }
}); });
@@ -178,5 +188,38 @@ public final class DomTable extends ScriptTable {
return LuaValue.NIL; return LuaValue.NIL;
} }
}); });
table().set("setProp", new ThreeArgFunction() {
@Override
public LuaValue call(LuaValue id, LuaValue prop, LuaValue value) {
dom.setProperty(id.checkjstring(), prop.checkjstring(), luaToJavaScalar(value));
return LuaValue.NIL;
}
});
table().set("getProp", new TwoArgFunction() {
@Override
public LuaValue call(LuaValue id, LuaValue prop) {
Object ret = dom.getProperty(id.checkjstring(), prop.checkjstring());
return JavaToLua.coerce(ret);
}
});
table().set("call", new VarArgFunction() {
@Override
public Varargs invoke(Varargs args) {
String id = args.arg(1).checkjstring();
String method = args.arg(2).checkjstring();
int n = args.narg();
Object[] argv = new Object[Math.max(0, n - 2)];
for (int i = 3; i <= n; i++) {
argv[i - 3] = luaToJavaScalar(args.arg(i));
}
Object ret = dom.call(id, method, argv);
return JavaToLua.coerce(ret);
}
});
} }
} }

View File

@@ -1,8 +1,8 @@
package org.openautonomousconnection.luascript.tables; package org.openautonomousconnection.luascript.tables;
import org.luaj.vm2.LuaValue; import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.OneArgFunction; import org.luaj.vm2.libs.OneArgFunction;
import org.luaj.vm2.lib.TwoArgFunction; import org.luaj.vm2.libs.TwoArgFunction;
import org.openautonomousconnection.luascript.events.LuaEventDispatcher; import org.openautonomousconnection.luascript.events.LuaEventDispatcher;
import org.openautonomousconnection.luascript.events.UiEventRegistry; import org.openautonomousconnection.luascript.events.UiEventRegistry;
import org.openautonomousconnection.luascript.hosts.EventHost; import org.openautonomousconnection.luascript.hosts.EventHost;
@@ -27,7 +27,7 @@ public final class EventsTable extends ScriptTable {
protected void define(HostServices services) { protected void define(HostServices services) {
EventHost host = services.events().orElseThrow(() -> new IllegalStateException("EventHost not provided")); EventHost host = services.events().orElseThrow(() -> new IllegalStateException("EventHost not provided"));
table().set("on", new org.luaj.vm2.lib.ThreeArgFunction() { table().set("on", new org.luaj.vm2.libs.ThreeArgFunction() {
@Override @Override
public LuaValue call(LuaValue elementId, LuaValue eventName, LuaValue handlerPath) { public LuaValue call(LuaValue elementId, LuaValue eventName, LuaValue handlerPath) {
String id = elementId.checkjstring(); String id = elementId.checkjstring();

View File

@@ -0,0 +1,70 @@
package org.openautonomousconnection.luascript.tables;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.libs.OneArgFunction;
import org.luaj.vm2.libs.TwoArgFunction;
import org.luaj.vm2.libs.VarArgFunction;
import org.luaj.vm2.Varargs;
import org.openautonomousconnection.luascript.events.JavaToLua;
import org.openautonomousconnection.luascript.hosts.GeometryHost;
import org.openautonomousconnection.luascript.hosts.HostServices;
import org.openautonomousconnection.luascript.utils.ScriptTable;
import java.util.Map;
/**
* Lua table: geometry
*
* <p>Functions:</p>
* <ul>
* <li>geometry.rect(id) -> map</li>
* <li>geometry.viewport() -> map</li>
* <li>geometry.scrollTo(x,y)</li>
* <li>geometry.scrollIntoView(id, align?)</li>
* </ul>
*/
public final class GeometryTable extends ScriptTable {
public GeometryTable() {
super("geometry");
}
@Override
protected void define(HostServices services) {
GeometryHost host = services.geometry().orElseThrow(() -> new IllegalStateException("GeometryHost not provided"));
table().set("rect", new OneArgFunction() {
@Override
public LuaValue call(LuaValue id) {
Map<String, Object> m = host.getBoundingClientRect(id.checkjstring());
return JavaToLua.coerce(m);
}
});
table().set("viewport", new org.luaj.vm2.libs.ZeroArgFunction() {
@Override
public LuaValue call() {
return JavaToLua.coerce(host.getViewport());
}
});
table().set("scrollTo", new VarArgFunction() {
@Override
public Varargs invoke(Varargs args) {
host.scrollTo(args.arg(1).checkdouble(), args.arg(2).checkdouble());
return LuaValue.NIL;
}
});
table().set("scrollIntoView", new VarArgFunction() {
@Override
public Varargs invoke(Varargs args) {
String id = args.arg(1).checkjstring();
String align = args.narg() >= 2 && !args.arg(2).isnil() ? args.arg(2).tojstring() : null;
host.scrollIntoView(id, align);
return LuaValue.NIL;
}
});
}
}

View File

@@ -0,0 +1,100 @@
package org.openautonomousconnection.luascript.tables;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.Varargs;
import org.luaj.vm2.libs.VarArgFunction;
import org.luaj.vm2.libs.ZeroArgFunction;
import org.openautonomousconnection.luascript.hosts.HostServices;
import org.openautonomousconnection.luascript.hosts.ImageHost;
import org.openautonomousconnection.luascript.utils.ScriptTable;
import java.io.File;
/**
* Lua table: image
*
* <p>Functions:</p>
* <ul>
* <li>image.show(pathOrUrl)</li>
* <li>image.hide()</li>
* <li>image.rect(x, y, w, h)</li>
* <li>image.opacity(v)</li>
* <li>image.preserveRatio(boolean)</li>
* <li>image.smooth(boolean)</li>
* </ul>
*/
public final class ImageTable extends ScriptTable {
public ImageTable() {
super("image");
}
@Override
protected void define(HostServices services) {
ImageHost host = services.image()
.orElseThrow(() -> new IllegalStateException("ImageHost not provided"));
table().set("show", new VarArgFunction() {
@Override
public Varargs invoke(Varargs args) {
String src = args.arg(1).checkjstring();
if (looksLikeUrl(src)) host.showUrl(src);
else host.showFile(new File(src));
return LuaValue.NIL;
}
});
table().set("hide", new ZeroArgFunction() {
@Override
public LuaValue call() {
host.hide();
return LuaValue.NIL;
}
});
table().set("rect", new VarArgFunction() {
@Override
public Varargs invoke(Varargs args) {
double x = args.arg(1).checkdouble();
double y = args.arg(2).checkdouble();
double w = args.arg(3).checkdouble();
double h = args.arg(4).checkdouble();
host.setRect(x, y, w, h);
return LuaValue.NIL;
}
});
table().set("opacity", new VarArgFunction() {
@Override
public Varargs invoke(Varargs args) {
double o = args.arg(1).checkdouble();
host.setOpacity(o);
return LuaValue.NIL;
}
});
table().set("preserveRatio", new VarArgFunction() {
@Override
public Varargs invoke(Varargs args) {
boolean b = args.arg(1).checkboolean();
host.setPreserveRatio(b);
return LuaValue.NIL;
}
});
table().set("smooth", new VarArgFunction() {
@Override
public Varargs invoke(Varargs args) {
boolean b = args.arg(1).checkboolean();
host.setSmooth(b);
return LuaValue.NIL;
}
});
}
private static boolean looksLikeUrl(String s) {
if (s == null) return false;
int idx = s.indexOf("://");
return idx > 0 && idx < 16;
}
}

View File

@@ -0,0 +1,103 @@
package org.openautonomousconnection.luascript.tables;
import org.luaj.vm2.LuaFunction;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.Varargs;
import org.luaj.vm2.libs.*;
import org.openautonomousconnection.luascript.events.JavaToLua;
import org.openautonomousconnection.luascript.hosts.HostServices;
import org.openautonomousconnection.luascript.hosts.ObserverHost;
import org.openautonomousconnection.luascript.utils.ScriptTable;
import java.util.Map;
/**
* Lua table: observers
*
* <p>Functions:</p>
* <ul>
* <li>observers.on(fn(type, targetId, dataTable))</li>
* <li>observers.mutationObserve(id, subtree, attributes, childList, characterData)</li>
* <li>observers.mutationUnobserve(id)</li>
* <li>observers.resizeObserve(id)</li>
* <li>observers.resizeUnobserve(id)</li>
* <li>observers.intersectionObserve(id, threshold)</li>
* <li>observers.intersectionUnobserve(id)</li>
* </ul>
*/
public final class ObserversTable extends ScriptTable {
public ObserversTable() {
super("observers");
}
@Override
protected void define(HostServices services) {
ObserverHost host = services.observers().orElseThrow(() -> new IllegalStateException("ObserverHost not provided"));
table().set("on", new OneArgFunction() {
@Override
public LuaValue call(LuaValue fn) {
LuaFunction cb = fn.checkfunction();
host.setCallback((type, targetId, data) -> {
LuaValue luaData = JavaToLua.coerce(data);
cb.call(LuaValue.valueOf(type), LuaValue.valueOf(targetId), luaData);
});
return LuaValue.NIL;
}
});
table().set("mutationObserve", new VarArgFunction() {
@Override
public Varargs invoke(Varargs args) {
String id = args.arg(1).checkjstring();
boolean subtree = args.arg(2).optboolean(false);
boolean attributes = args.arg(3).optboolean(true);
boolean childList = args.arg(4).optboolean(true);
boolean characterData = args.arg(5).optboolean(false);
host.observeMutations(id, subtree, attributes, childList, characterData);
return LuaValue.NIL;
}
});
table().set("mutationUnobserve", new OneArgFunction() {
@Override
public LuaValue call(LuaValue id) {
host.unobserveMutations(id.checkjstring());
return LuaValue.NIL;
}
});
table().set("resizeObserve", new OneArgFunction() {
@Override
public LuaValue call(LuaValue id) {
host.observeResize(id.checkjstring());
return LuaValue.NIL;
}
});
table().set("resizeUnobserve", new OneArgFunction() {
@Override
public LuaValue call(LuaValue id) {
host.unobserveResize(id.checkjstring());
return LuaValue.NIL;
}
});
table().set("intersectionObserve", new TwoArgFunction() {
@Override
public LuaValue call(LuaValue id, LuaValue threshold) {
host.observeIntersection(id.checkjstring(), threshold.checkdouble());
return LuaValue.NIL;
}
});
table().set("intersectionUnobserve", new OneArgFunction() {
@Override
public LuaValue call(LuaValue id) {
host.unobserveIntersection(id.checkjstring());
return LuaValue.NIL;
}
});
}
}

View File

@@ -0,0 +1,78 @@
package org.openautonomousconnection.luascript.tables;
import org.luaj.vm2.LuaFunction;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.libs.OneArgFunction;
import org.luaj.vm2.libs.TwoArgFunction;
import org.luaj.vm2.libs.VarArgFunction;
import org.luaj.vm2.Varargs;
import org.openautonomousconnection.luascript.hosts.HostServices;
import org.openautonomousconnection.luascript.hosts.SchedulerHost;
import org.openautonomousconnection.luascript.utils.ScriptTable;
/**
* Lua table: scheduler
*
* <p>Functions:</p>
* <ul>
* <li>scheduler.timeout(ms, fn) -> id</li>
* <li>scheduler.interval(ms, fn) -> id</li>
* <li>scheduler.clear(id) -> boolean</li>
* <li>scheduler.raf(fn) -> id</li>
* <li>scheduler.cancelRaf(id) -> boolean</li>
* </ul>
*/
public final class SchedulerTable extends ScriptTable {
public SchedulerTable() {
super("scheduler");
}
@Override
protected void define(HostServices services) {
SchedulerHost host = services.scheduler().orElseThrow(() -> new IllegalStateException("SchedulerHost not provided"));
table().set("timeout", new TwoArgFunction() {
@Override
public LuaValue call(LuaValue ms, LuaValue fn) {
long delay = ms.checklong();
LuaFunction cb = fn.checkfunction();
long id = host.setTimeout(delay, () -> cb.call());
return LuaValue.valueOf(id);
}
});
table().set("interval", new TwoArgFunction() {
@Override
public LuaValue call(LuaValue ms, LuaValue fn) {
long interval = ms.checklong();
LuaFunction cb = fn.checkfunction();
long id = host.setInterval(interval, () -> cb.call());
return LuaValue.valueOf(id);
}
});
table().set("clear", new OneArgFunction() {
@Override
public LuaValue call(LuaValue id) {
return LuaValue.valueOf(host.clear(id.checklong()));
}
});
table().set("raf", new OneArgFunction() {
@Override
public LuaValue call(LuaValue fn) {
LuaFunction cb = fn.checkfunction();
long id = host.requestAnimationFrame(() -> cb.call());
return LuaValue.valueOf(id);
}
});
table().set("cancelRaf", new OneArgFunction() {
@Override
public LuaValue call(LuaValue id) {
return LuaValue.valueOf(host.cancelAnimationFrame(id.checklong()));
}
});
}
}

View File

@@ -0,0 +1,74 @@
package org.openautonomousconnection.luascript.tables;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.libs.TwoArgFunction;
import org.luaj.vm2.libs.OneArgFunction;
import org.openautonomousconnection.luascript.hosts.HostServices;
import org.openautonomousconnection.luascript.hosts.SelectorHost;
import org.openautonomousconnection.luascript.utils.ScriptTable;
import java.util.List;
/**
* Lua table: selector
*
* <p>Functions:</p>
* <ul>
* <li>selector.one(css) -> id|nil</li>
* <li>selector.all(css) -> { ids... }</li>
* <li>selector.matches(id, css) -> boolean</li>
* <li>selector.closest(id, css) -> id|nil</li>
* </ul>
*/
public final class SelectorTable extends ScriptTable {
public SelectorTable() {
super("selector");
}
private static LuaValue toLuaArray(List<String> values) {
LuaTable t = new LuaTable();
if (values == null || values.isEmpty()) return t;
int i = 1;
for (String v : values) {
t.set(i++, v == null ? LuaValue.NIL : LuaValue.valueOf(v));
}
return t;
}
@Override
protected void define(HostServices services) {
SelectorHost host = services.selector().orElseThrow(() -> new IllegalStateException("SelectorHost not provided"));
table().set("one", new OneArgFunction() {
@Override
public LuaValue call(LuaValue css) {
String id = host.querySelector(css.checkjstring());
return id == null ? LuaValue.NIL : LuaValue.valueOf(id);
}
});
table().set("all", new OneArgFunction() {
@Override
public LuaValue call(LuaValue css) {
return toLuaArray(host.querySelectorAll(css.checkjstring()));
}
});
table().set("matches", new TwoArgFunction() {
@Override
public LuaValue call(LuaValue id, LuaValue css) {
return LuaValue.valueOf(host.matches(id.checkjstring(), css.checkjstring()));
}
});
table().set("closest", new TwoArgFunction() {
@Override
public LuaValue call(LuaValue id, LuaValue css) {
String out = host.closest(id.checkjstring(), css.checkjstring());
return out == null ? LuaValue.NIL : LuaValue.valueOf(out);
}
});
}
}

View File

@@ -0,0 +1,121 @@
package org.openautonomousconnection.luascript.tables;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.libs.*;
import org.openautonomousconnection.luascript.hosts.HostServices;
import org.openautonomousconnection.luascript.hosts.StorageHost;
import org.openautonomousconnection.luascript.utils.ScriptTable;
import java.util.List;
/**
* Lua table: storage
*
* <p>Functions:</p>
* <ul>
* <li>storage.localGet(key) -> string|nil</li>
* <li>storage.localSet(key, value|nil)</li>
* <li>storage.localKeys() -> {keys...}</li>
* <li>storage.localRemove(key)</li>
* <li>storage.localClear()</li>
* <li>same for session*</li>
* </ul>
*/
public final class StorageTable extends ScriptTable {
public StorageTable() {
super("storage");
}
private static LuaValue toLuaArray(List<String> values) {
LuaTable t = new LuaTable();
if (values == null || values.isEmpty()) return t;
int i = 1;
for (String v : values) t.set(i++, v == null ? LuaValue.NIL : LuaValue.valueOf(v));
return t;
}
@Override
protected void define(HostServices services) {
StorageHost host = services.storage().orElseThrow(() -> new IllegalStateException("StorageHost not provided"));
table().set("localKeys", new ZeroArgFunction() {
@Override
public LuaValue call() {
return toLuaArray(host.localKeys());
}
});
table().set("localGet", new OneArgFunction() {
@Override
public LuaValue call(LuaValue key) {
String v = host.localGet(key.checkjstring());
return v == null ? LuaValue.NIL : LuaValue.valueOf(v);
}
});
table().set("localSet", new TwoArgFunction() {
@Override
public LuaValue call(LuaValue key, LuaValue value) {
host.localSet(key.checkjstring(), value.isnil() ? null : value.tojstring());
return LuaValue.NIL;
}
});
table().set("localRemove", new OneArgFunction() {
@Override
public LuaValue call(LuaValue key) {
host.localRemove(key.checkjstring());
return LuaValue.NIL;
}
});
table().set("localClear", new ZeroArgFunction() {
@Override
public LuaValue call() {
host.localClear();
return LuaValue.NIL;
}
});
table().set("sessionKeys", new ZeroArgFunction() {
@Override
public LuaValue call() {
return toLuaArray(host.sessionKeys());
}
});
table().set("sessionGet", new OneArgFunction() {
@Override
public LuaValue call(LuaValue key) {
String v = host.sessionGet(key.checkjstring());
return v == null ? LuaValue.NIL : LuaValue.valueOf(v);
}
});
table().set("sessionSet", new TwoArgFunction() {
@Override
public LuaValue call(LuaValue key, LuaValue value) {
host.sessionSet(key.checkjstring(), value.isnil() ? null : value.tojstring());
return LuaValue.NIL;
}
});
table().set("sessionRemove", new OneArgFunction() {
@Override
public LuaValue call(LuaValue key) {
host.sessionRemove(key.checkjstring());
return LuaValue.NIL;
}
});
table().set("sessionClear", new ZeroArgFunction() {
@Override
public LuaValue call() {
host.sessionClear();
return LuaValue.NIL;
}
});
}
}

View File

@@ -2,7 +2,7 @@ package org.openautonomousconnection.luascript.tables;
import org.luaj.vm2.LuaTable; import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue; import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.OneArgFunction; import org.luaj.vm2.libs.OneArgFunction;
import org.openautonomousconnection.luascript.hosts.HostServices; import org.openautonomousconnection.luascript.hosts.HostServices;
import org.openautonomousconnection.luascript.hosts.UiHost; import org.openautonomousconnection.luascript.hosts.UiHost;
import org.openautonomousconnection.luascript.utils.ScriptTable; import org.openautonomousconnection.luascript.utils.ScriptTable;
@@ -35,7 +35,7 @@ public final class UiTable extends ScriptTable {
} }
}); });
table().set("prompt", new org.luaj.vm2.lib.TwoArgFunction() { table().set("prompt", new org.luaj.vm2.libs.TwoArgFunction() {
@Override @Override
public LuaValue call(LuaValue msg, LuaValue def) { public LuaValue call(LuaValue msg, LuaValue def) {
String out = ui.prompt( String out = ui.prompt(
@@ -46,7 +46,7 @@ public final class UiTable extends ScriptTable {
} }
}); });
table().set("setText", new org.luaj.vm2.lib.TwoArgFunction() { table().set("setText", new org.luaj.vm2.libs.TwoArgFunction() {
@Override @Override
public LuaValue call(LuaValue id, LuaValue text) { public LuaValue call(LuaValue id, LuaValue text) {
ui.setText(id.checkjstring(), text.isnil() ? "" : text.tojstring()); ui.setText(id.checkjstring(), text.isnil() ? "" : text.tojstring());
@@ -62,7 +62,7 @@ public final class UiTable extends ScriptTable {
} }
}); });
table().set("setHtml", new org.luaj.vm2.lib.TwoArgFunction() { table().set("setHtml", new org.luaj.vm2.libs.TwoArgFunction() {
@Override @Override
public LuaValue call(LuaValue id, LuaValue html) { public LuaValue call(LuaValue id, LuaValue html) {
ui.setHtml(id.checkjstring(), html.isnil() ? "" : html.tojstring()); ui.setHtml(id.checkjstring(), html.isnil() ? "" : html.tojstring());
@@ -77,7 +77,7 @@ public final class UiTable extends ScriptTable {
} }
}); });
table().set("setValue", new org.luaj.vm2.lib.TwoArgFunction() { table().set("setValue", new org.luaj.vm2.libs.TwoArgFunction() {
@Override @Override
public LuaValue call(LuaValue id, LuaValue value) { public LuaValue call(LuaValue id, LuaValue value) {
ui.setValue(id.checkjstring(), value.isnil() ? "" : value.tojstring()); ui.setValue(id.checkjstring(), value.isnil() ? "" : value.tojstring());
@@ -92,7 +92,7 @@ public final class UiTable extends ScriptTable {
} }
}); });
table().set("setEnabled", new org.luaj.vm2.lib.TwoArgFunction() { table().set("setEnabled", new org.luaj.vm2.libs.TwoArgFunction() {
@Override @Override
public LuaValue call(LuaValue id, LuaValue enabled) { public LuaValue call(LuaValue id, LuaValue enabled) {
ui.setEnabled(id.checkjstring(), enabled.toboolean()); ui.setEnabled(id.checkjstring(), enabled.toboolean());
@@ -100,7 +100,7 @@ public final class UiTable extends ScriptTable {
} }
}); });
table().set("setVisible", new org.luaj.vm2.lib.TwoArgFunction() { table().set("setVisible", new org.luaj.vm2.libs.TwoArgFunction() {
@Override @Override
public LuaValue call(LuaValue id, LuaValue visible) { public LuaValue call(LuaValue id, LuaValue visible) {
ui.setVisible(id.checkjstring(), visible.toboolean()); ui.setVisible(id.checkjstring(), visible.toboolean());
@@ -108,7 +108,7 @@ public final class UiTable extends ScriptTable {
} }
}); });
table().set("addClass", new org.luaj.vm2.lib.TwoArgFunction() { table().set("addClass", new org.luaj.vm2.libs.TwoArgFunction() {
@Override @Override
public LuaValue call(LuaValue id, LuaValue cls) { public LuaValue call(LuaValue id, LuaValue cls) {
ui.addClass(id.checkjstring(), cls.checkjstring()); ui.addClass(id.checkjstring(), cls.checkjstring());
@@ -116,7 +116,7 @@ public final class UiTable extends ScriptTable {
} }
}); });
table().set("removeClass", new org.luaj.vm2.lib.TwoArgFunction() { table().set("removeClass", new org.luaj.vm2.libs.TwoArgFunction() {
@Override @Override
public LuaValue call(LuaValue id, LuaValue cls) { public LuaValue call(LuaValue id, LuaValue cls) {
ui.removeClass(id.checkjstring(), cls.checkjstring()); ui.removeClass(id.checkjstring(), cls.checkjstring());
@@ -124,21 +124,21 @@ public final class UiTable extends ScriptTable {
} }
}); });
table().set("toggleClass", new org.luaj.vm2.lib.TwoArgFunction() { table().set("toggleClass", new org.luaj.vm2.libs.TwoArgFunction() {
@Override @Override
public LuaValue call(LuaValue id, LuaValue cls) { public LuaValue call(LuaValue id, LuaValue cls) {
return LuaValue.valueOf(ui.toggleClass(id.checkjstring(), cls.checkjstring())); return LuaValue.valueOf(ui.toggleClass(id.checkjstring(), cls.checkjstring()));
} }
}); });
table().set("hasClass", new org.luaj.vm2.lib.TwoArgFunction() { table().set("hasClass", new org.luaj.vm2.libs.TwoArgFunction() {
@Override @Override
public LuaValue call(LuaValue id, LuaValue cls) { public LuaValue call(LuaValue id, LuaValue cls) {
return LuaValue.valueOf(ui.hasClass(id.checkjstring(), cls.checkjstring())); return LuaValue.valueOf(ui.hasClass(id.checkjstring(), cls.checkjstring()));
} }
}); });
table().set("setStyle", new org.luaj.vm2.lib.ThreeArgFunction() { table().set("setStyle", new org.luaj.vm2.libs.ThreeArgFunction() {
@Override @Override
public LuaValue call(LuaValue id, LuaValue prop, LuaValue value) { public LuaValue call(LuaValue id, LuaValue prop, LuaValue value) {
ui.setStyle(id.checkjstring(), prop.checkjstring(), value.isnil() ? "" : value.tojstring()); ui.setStyle(id.checkjstring(), prop.checkjstring(), value.isnil() ? "" : value.tojstring());
@@ -146,14 +146,14 @@ public final class UiTable extends ScriptTable {
} }
}); });
table().set("getStyle", new org.luaj.vm2.lib.TwoArgFunction() { table().set("getStyle", new org.luaj.vm2.libs.TwoArgFunction() {
@Override @Override
public LuaValue call(LuaValue id, LuaValue prop) { public LuaValue call(LuaValue id, LuaValue prop) {
return LuaValue.valueOf(ui.getStyle(id.checkjstring(), prop.checkjstring())); return LuaValue.valueOf(ui.getStyle(id.checkjstring(), prop.checkjstring()));
} }
}); });
table().set("setAttr", new org.luaj.vm2.lib.ThreeArgFunction() { table().set("setAttr", new org.luaj.vm2.libs.ThreeArgFunction() {
@Override @Override
public LuaValue call(LuaValue id, LuaValue name, LuaValue value) { public LuaValue call(LuaValue id, LuaValue name, LuaValue value) {
ui.setAttribute(id.checkjstring(), name.checkjstring(), value.isnil() ? "" : value.tojstring()); ui.setAttribute(id.checkjstring(), name.checkjstring(), value.isnil() ? "" : value.tojstring());
@@ -161,7 +161,7 @@ public final class UiTable extends ScriptTable {
} }
}); });
table().set("getAttr", new org.luaj.vm2.lib.TwoArgFunction() { table().set("getAttr", new org.luaj.vm2.libs.TwoArgFunction() {
@Override @Override
public LuaValue call(LuaValue id, LuaValue name) { public LuaValue call(LuaValue id, LuaValue name) {
String v = ui.getAttribute(id.checkjstring(), name.checkjstring()); String v = ui.getAttribute(id.checkjstring(), name.checkjstring());
@@ -169,7 +169,7 @@ public final class UiTable extends ScriptTable {
} }
}); });
table().set("removeAttr", new org.luaj.vm2.lib.TwoArgFunction() { table().set("removeAttr", new org.luaj.vm2.libs.TwoArgFunction() {
@Override @Override
public LuaValue call(LuaValue id, LuaValue name) { public LuaValue call(LuaValue id, LuaValue name) {
ui.removeAttribute(id.checkjstring(), name.checkjstring()); ui.removeAttribute(id.checkjstring(), name.checkjstring());
@@ -202,13 +202,13 @@ public final class UiTable extends ScriptTable {
}); });
LuaTable viewport = new LuaTable(); LuaTable viewport = new LuaTable();
viewport.set("width", new org.luaj.vm2.lib.ZeroArgFunction() { viewport.set("width", new org.luaj.vm2.libs.ZeroArgFunction() {
@Override @Override
public LuaValue call() { public LuaValue call() {
return LuaValue.valueOf(ui.viewportWidth()); return LuaValue.valueOf(ui.viewportWidth());
} }
}); });
viewport.set("height", new org.luaj.vm2.lib.ZeroArgFunction() { viewport.set("height", new org.luaj.vm2.libs.ZeroArgFunction() {
@Override @Override
public LuaValue call() { public LuaValue call() {
return LuaValue.valueOf(ui.viewportHeight()); return LuaValue.valueOf(ui.viewportHeight());
@@ -216,7 +216,7 @@ public final class UiTable extends ScriptTable {
}); });
table().set("viewport", viewport); table().set("viewport", viewport);
table().set("now", new org.luaj.vm2.lib.ZeroArgFunction() { table().set("now", new org.luaj.vm2.libs.ZeroArgFunction() {
@Override @Override
public LuaValue call() { public LuaValue call() {
return LuaValue.valueOf(ui.nowMillis()); return LuaValue.valueOf(ui.nowMillis());

View File

@@ -0,0 +1,83 @@
package org.openautonomousconnection.luascript.tables;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.libs.*;
import org.openautonomousconnection.luascript.events.JavaToLua;
import org.openautonomousconnection.luascript.hosts.HostServices;
import org.openautonomousconnection.luascript.hosts.UtilHost;
import org.openautonomousconnection.luascript.utils.ScriptTable;
/**
* Lua table: util
*
* <p>Functions:</p>
* <ul>
* <li>util.base64Encode(text)</li>
* <li>util.base64Decode(b64)</li>
* <li>util.randomHex(bytes)</li>
* <li>util.parseUrl(url) -> map</li>
* <li>util.parseQuery(query) -> map(key->array)</li>
* <li>util.jsonStringifyExpr(elementId, jsExpr) -> jsonString</li>
* <li>util.jsonNormalize(elementId, json) -> jsonString</li>
* </ul>
*/
public final class UtilTable extends ScriptTable {
public UtilTable() {
super("util");
}
@Override
protected void define(HostServices services) {
UtilHost host = services.util().orElseThrow(() -> new IllegalStateException("UtilHost not provided"));
table().set("base64Encode", new OneArgFunction() {
@Override
public LuaValue call(LuaValue text) {
return LuaValue.valueOf(host.base64Encode(text.isnil() ? "" : text.tojstring()));
}
});
table().set("base64Decode", new OneArgFunction() {
@Override
public LuaValue call(LuaValue b64) {
return LuaValue.valueOf(host.base64Decode(b64.isnil() ? "" : b64.tojstring()));
}
});
table().set("randomHex", new OneArgFunction() {
@Override
public LuaValue call(LuaValue bytes) {
return LuaValue.valueOf(host.randomHex(bytes.checkint()));
}
});
table().set("parseUrl", new OneArgFunction() {
@Override
public LuaValue call(LuaValue url) {
return JavaToLua.coerce(host.parseUrl(url.checkjstring()));
}
});
table().set("parseQuery", new OneArgFunction() {
@Override
public LuaValue call(LuaValue q) {
return JavaToLua.coerce(host.parseQuery(q.isnil() ? "" : q.tojstring()));
}
});
table().set("jsonStringifyExpr", new TwoArgFunction() {
@Override
public LuaValue call(LuaValue elementId, LuaValue jsExpr) {
return LuaValue.valueOf(host.jsonStringifyExpr(elementId.checkjstring(), jsExpr.checkjstring()));
}
});
table().set("jsonNormalize", new TwoArgFunction() {
@Override
public LuaValue call(LuaValue elementId, LuaValue json) {
return LuaValue.valueOf(host.jsonNormalize(elementId.checkjstring(), json.checkjstring()));
}
});
}
}

View File

@@ -0,0 +1,127 @@
package org.openautonomousconnection.luascript.tables;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.Varargs;
import org.luaj.vm2.libs.VarArgFunction;
import org.luaj.vm2.libs.ZeroArgFunction;
import org.openautonomousconnection.luascript.hosts.HostServices;
import org.openautonomousconnection.luascript.hosts.VideoHost;
import org.openautonomousconnection.luascript.utils.ScriptTable;
import java.io.File;
/**
* Lua table: video
*
* <p>Functions:</p>
* <ul>
* <li>video.play(pathOrUrl)</li>
* <li>video.pause()</li>
* <li>video.resume()</li>
* <li>video.stop()</li>
* <li>video.hide()</li>
* <li>video.rect(x, y, w, h)</li>
* <li>video.volume(v)</li>
* <li>video.loop(boolean)</li>
* <li>video.seek(seconds)</li>
* </ul>
*/
public final class VideoTable extends ScriptTable {
public VideoTable() {
super("video");
}
@Override
protected void define(HostServices services) {
VideoHost host = services.video()
.orElseThrow(() -> new IllegalStateException("VideoHost not provided"));
table().set("play", new VarArgFunction() {
@Override
public Varargs invoke(Varargs args) {
String src = args.arg(1).checkjstring();
if (looksLikeUrl(src)) host.playUrl(src);
else host.playFile(new File(src));
return LuaValue.NIL;
}
});
table().set("pause", new ZeroArgFunction() {
@Override
public LuaValue call() {
host.pause();
return LuaValue.NIL;
}
});
table().set("resume", new ZeroArgFunction() {
@Override
public LuaValue call() {
host.resume();
return LuaValue.NIL;
}
});
table().set("stop", new ZeroArgFunction() {
@Override
public LuaValue call() {
host.stop();
return LuaValue.NIL;
}
});
table().set("hide", new ZeroArgFunction() {
@Override
public LuaValue call() {
host.hide();
return LuaValue.NIL;
}
});
table().set("rect", new VarArgFunction() {
@Override
public Varargs invoke(Varargs args) {
double x = args.arg(1).checkdouble();
double y = args.arg(2).checkdouble();
double w = args.arg(3).checkdouble();
double h = args.arg(4).checkdouble();
host.setRect(x, y, w, h);
return LuaValue.NIL;
}
});
table().set("volume", new VarArgFunction() {
@Override
public Varargs invoke(Varargs args) {
double v = args.arg(1).checkdouble();
host.setVolume(v);
return LuaValue.NIL;
}
});
table().set("loop", new VarArgFunction() {
@Override
public Varargs invoke(Varargs args) {
boolean b = args.arg(1).checkboolean();
host.setLoop(b);
return LuaValue.NIL;
}
});
table().set("seek", new VarArgFunction() {
@Override
public Varargs invoke(Varargs args) {
double s = args.arg(1).checkdouble();
host.seek(s);
return LuaValue.NIL;
}
});
}
private static boolean looksLikeUrl(String s) {
if (s == null) return false;
int idx = s.indexOf("://");
return idx > 0 && idx < 16;
}
}

View File

@@ -1,24 +1,28 @@
package org.openautonomousconnection.luascript.tables.console; package org.openautonomousconnection.luascript.tables.console;
import org.luaj.vm2.LuaValue; import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.OneArgFunction; import org.luaj.vm2.libs.OneArgFunction;
import org.openautonomousconnection.luascript.hosts.ConsoleHost;
import org.openautonomousconnection.luascript.hosts.HostServices; import org.openautonomousconnection.luascript.hosts.HostServices;
import org.openautonomousconnection.luascript.utils.ScriptTable; import org.openautonomousconnection.luascript.utils.ScriptTable;
public class ConsoleLogTable extends ScriptTable { /**
/** * Lua table: console.log
* Creates a new script table with the given global name. */
*/ public final class ConsoleLogTable extends ScriptTable {
public ConsoleLogTable() { public ConsoleLogTable() {
super("log"); super("log");
} }
@Override @Override
protected void define(HostServices services) { protected void define(HostServices services) {
ConsoleHost console = services.console().orElseThrow(() -> new IllegalStateException("ConsoleHost not provided"));
table().set("info", new OneArgFunction() { table().set("info", new OneArgFunction() {
@Override @Override
public LuaValue call(LuaValue arg) { public LuaValue call(LuaValue arg) {
services.console().get().info(arg.isnil() ? "nil" : arg.tojstring()); console.info(arg.isnil() ? "nil" : arg.tojstring());
return LuaValue.NIL; return LuaValue.NIL;
} }
}); });
@@ -26,7 +30,7 @@ public class ConsoleLogTable extends ScriptTable {
table().set("log", new OneArgFunction() { table().set("log", new OneArgFunction() {
@Override @Override
public LuaValue call(LuaValue arg) { public LuaValue call(LuaValue arg) {
services.console().get().log(arg.isnil() ? "nil" : arg.tojstring()); console.log(arg.isnil() ? "nil" : arg.tojstring());
return LuaValue.NIL; return LuaValue.NIL;
} }
}); });
@@ -34,9 +38,9 @@ public class ConsoleLogTable extends ScriptTable {
table().set("warn", new OneArgFunction() { table().set("warn", new OneArgFunction() {
@Override @Override
public LuaValue call(LuaValue arg) { public LuaValue call(LuaValue arg) {
services.console().get().warn(arg.isnil() ? "nil" : arg.tojstring()); console.warn(arg.isnil() ? "nil" : arg.tojstring());
return LuaValue.NIL; return LuaValue.NIL;
} }
}); });
} }
} }

View File

@@ -1,24 +1,28 @@
package org.openautonomousconnection.luascript.tables.console; package org.openautonomousconnection.luascript.tables.console;
import org.luaj.vm2.LuaValue; import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.OneArgFunction; import org.luaj.vm2.libs.OneArgFunction;
import org.openautonomousconnection.luascript.hosts.ConsoleHost;
import org.openautonomousconnection.luascript.hosts.HostServices; import org.openautonomousconnection.luascript.hosts.HostServices;
import org.openautonomousconnection.luascript.utils.ScriptTable; import org.openautonomousconnection.luascript.utils.ScriptTable;
public class ConsoleStacktraceTable extends ScriptTable { /**
/** * Lua table: console.stacktrace
* Creates a new script table with the given global name. */
*/ public final class ConsoleStacktraceTable extends ScriptTable {
public ConsoleStacktraceTable() { public ConsoleStacktraceTable() {
super("stacktrace"); super("stacktrace");
} }
@Override @Override
protected void define(HostServices services) { protected void define(HostServices services) {
ConsoleHost console = services.console().orElseThrow(() -> new IllegalStateException("ConsoleHost not provided"));
table().set("print", new OneArgFunction() { table().set("print", new OneArgFunction() {
@Override @Override
public LuaValue call(LuaValue arg) { public LuaValue call(LuaValue arg) {
services.console().get().error(arg.isnil() ? "nil" : arg.tojstring()); console.error(arg.isnil() ? "nil" : arg.tojstring());
return LuaValue.NIL; return LuaValue.NIL;
} }
}); });
@@ -26,9 +30,9 @@ public class ConsoleStacktraceTable extends ScriptTable {
table().set("exception", new OneArgFunction() { table().set("exception", new OneArgFunction() {
@Override @Override
public LuaValue call(LuaValue arg) { public LuaValue call(LuaValue arg) {
services.console().get().exception(arg.isnil() ? "nil" : arg.tojstring()); console.exception(arg.isnil() ? "nil" : arg.tojstring());
return LuaValue.NIL; return LuaValue.NIL;
} }
}); });
} }
} }

View File

@@ -3,10 +3,11 @@ package org.openautonomousconnection.luascript.tables.console;
import org.openautonomousconnection.luascript.hosts.HostServices; import org.openautonomousconnection.luascript.hosts.HostServices;
import org.openautonomousconnection.luascript.utils.ScriptTable; import org.openautonomousconnection.luascript.utils.ScriptTable;
public class ConsoleTable extends ScriptTable { /**
/** * Lua table: console
* Creates a new script table with the given global name. */
*/ public final class ConsoleTable extends ScriptTable {
public ConsoleTable() { public ConsoleTable() {
super("console"); super("console");
} }
@@ -16,4 +17,4 @@ public class ConsoleTable extends ScriptTable {
injectChild(new ConsoleLogTable(), services, true); injectChild(new ConsoleLogTable(), services, true);
injectChild(new ConsoleStacktraceTable(), services, true); injectChild(new ConsoleStacktraceTable(), services, true);
} }
} }

View File

@@ -3,8 +3,8 @@ package org.openautonomousconnection.luascript.utils;
import org.luaj.vm2.Globals; import org.luaj.vm2.Globals;
import org.luaj.vm2.LuaTable; import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue; import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.DebugLib; import org.luaj.vm2.libs.DebugLib;
import org.luaj.vm2.lib.jse.JsePlatform; import org.luaj.vm2.libs.jse.JsePlatform;
import java.util.Objects; import java.util.Objects;
import java.util.function.Consumer; import java.util.function.Consumer;

View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,65 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright © 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, “this License” refers to version 3 of the GNU Lesser General Public License, and the “GNU GPL” refers to version 3 of the GNU General Public License.
“The Library” refers to a covered work governed by this License, other than an Application or a Combined Work as defined below.
An “Application” is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library.
A “Combined Work” is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the “Linked Version”.
The “Minimal Corresponding Source” for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version.
The “Corresponding Application Code” for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version:
a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following:
a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license document.
c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.
1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version.
e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library.

View File

@@ -0,0 +1,88 @@
Eclipse Public License - v 1.0
THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
1. DEFINITIONS
"Contribution" means:
a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and
b) in the case of each subsequent Contributor:
i) changes to the Program, and
ii) additions to the Program;
where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program.
"Contributor" means any person or entity that distributes the Program.
"Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program.
"Program" means the Contributions distributed in accordance with this Agreement.
"Recipient" means anyone who receives the Program under this Agreement, including all Contributors.
2. GRANT OF RIGHTS
a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form.
b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder.
c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program.
d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement.
3. REQUIREMENTS
A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that:
a) it complies with the terms and conditions of this Agreement; and
b) its license agreement:
i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose;
ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits;
iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and
iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange.
When the Program is made available in source code form:
a) it must be made available under this Agreement; and
b) a copy of this Agreement must be included with each copy of the Program.
Contributors may not remove or alter any copyright notices contained within the Program.
Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution.
4. COMMERCIAL DISTRIBUTION
Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense.
For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages.
5. NO WARRANTY
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement , including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations.
6. DISCLAIMER OF LIABILITY
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
7. GENERAL
If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.
If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed.
All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive.
Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved.
This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation.

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2007 LuaJ. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,65 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright © 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, “this License” refers to version 3 of the GNU Lesser General Public License, and the “GNU GPL” refers to version 3 of the GNU General Public License.
“The Library” refers to a covered work governed by this License, other than an Application or a Combined Work as defined below.
An “Application” is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library.
A “Combined Work” is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the “Linked Version”.
The “Minimal Corresponding Source” for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version.
The “Corresponding Application Code” for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version:
a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following:
a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license document.
c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.
1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version.
e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library.

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 GSMA Inclusive Tech Lab
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.