Upload Base

This commit is contained in:
oxmc
2025-03-28 11:39:20 -04:00
commit b831aa747f
187 changed files with 20962 additions and 0 deletions

22
.gitignore vendored Normal file
View File

@@ -0,0 +1,22 @@
# eclipse
bin
*.launch
.settings
.metadata
.classpath
.project
# idea
out
*.ipr
*.iws
*.iml
.idea
# gradle
build
.gradle
# other
eclipse
run

32
LICENSE Normal file
View File

@@ -0,0 +1,32 @@
This project contains modified and unmodified source files from the JCEF project https://code.google.com/p/javachromiumembedded/
The following license applies to all file in the org.cef package, as well as all files in the native folder.
Copyright (c) 2008-2013 Marshall A. Greenblatt. Portions Copyright (c)
2006-2009 Google Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the name Chromium Embedded
Framework nor the names of its contributors may be used to endorse
or promote products derived from this software without specific prior
written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 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
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

36
README.md Normal file
View File

@@ -0,0 +1,36 @@
# MCEF
Minecraft Chromium Embedded Framework (MCEF) is an API to allow Minecraft Modders to add custom web browsers into Minecraft.
The project was initialy made for WebDisplays (www.minecraftforum.net/forums/mapping-and-modding/minecraft-mods/1291044-web-displays-browse-on-the-internet-in-minecraft).
It is based on JCEF (https://code.google.com/p/javachromiumembedded/), which is based on CEF (https://bitbucket.org/chromiumembedded/java-cef) which is based on chromium (http://www.chromium.org).
# Features
- 2D & 3D web view rendering (not only in GUIs)
- Java -> JavaScript (IBrowser.runJavaScript)
- JavaScript -> Java (IJSQueryHandler)
- Embedded files (mod://modname/file.html => /assets/modname/html/file.html)
- HTML5, CSS3 are supported.
# What can I do with this?
- If you're tired of Minecraft's GuiScreen, you can make a HTML/JS/CSS GUI.
- Open a link to your wiki.
- Whatever you want, but please not a WebDisplays clone.
# Currently supported platforms
- Windows 10 x64
- macOS (Intel-based Macs only) - needs work, frequent crashes
- Linux x64 (tested on Fedora 34 and Ubuntu 20.04)
# For players
This is the Github project of MCEF; here you can only read the source code of it.
You can download the mod in its latest version from here: http://www.minecraftforum.net/forums/mapping-and-modding/minecraft-mods/2324969-minecraft-chromium-embedded-framework-mcef
# For modders
**DONT** copy the net.montoyo.mcef.api package into your project. Instead, download the latest API release from https://github.com/montoyo/mcef/releases and put it in the libs folder. Users will have to download the mod from the MinecraftForum thread.
To understand how it works, you may look at the net.montoyo.mcef.example package, which demos:
* The IBrowser interface, how to draw it and control it
* The IJSQueryHandler interface, how to handle JavaScript queries
* The IDisplayHandler interface, how to handle browser URL changes
* How to use the mod:// scheme
# For forkers
Don't forget to add "-Dfml.coreMods.load=net.montoyo.mcef.coremod.ShutdownPatcher" to the VM options, otherwise the Java process will hang forever!

122
build.gradle Normal file
View File

@@ -0,0 +1,122 @@
buildscript {
repositories {
maven { url = 'https://files.minecraftforge.net/maven' }
jcenter()
mavenCentral()
}
dependencies {
classpath 'net.minecraftforge.gradle:ForgeGradle:3.+'
}
}
apply plugin: 'net.minecraftforge.gradle'
// Only edit below this line, the above code adds and enables the necessary things for Forge to be setup.
apply plugin: 'eclipse'
apply plugin: 'maven-publish'
version = '1.12.2-1.41'
group = 'net.montoyo.mcef'
archivesBaseName = 'mcef'
sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly.
minecraft {
// The mappings can be changed at any time, and must be in the following format.
// snapshot_YYYYMMDD Snapshot are built nightly.
// stable_# Stables are built at the discretion of the MCP team.
// Use non-default mappings at your own risk. they may not always work.
// Simply re-run your setup task after changing the mappings to update your workspace.
//mappings channel: 'snapshot', version: '20171003-1.12'
mappings channel: 'snapshot', version: '20171003-1.12'
// makeObfSourceJar = false // an Srg named sources jar is made by default. uncomment this to disable.
// accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg')
// Default run configurations.
// These can be tweaked, removed, or duplicated as needed.
runs {
client {
workingDirectory project.file('run')
// Recommended logging data for a userdev environment
property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP'
// Recommended logging level for the console
property 'forge.logging.console.level', 'debug'
}
server {
// Recommended logging data for a userdev environment
property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP'
// Recommended logging level for the console
property 'forge.logging.console.level', 'debug'
}
}
}
dependencies {
// Specify the version of Minecraft to use, If this is any group other then 'net.minecraft' it is assumed
// that the dep is a ForgeGradle 'patcher' dependency. And it's patches will be applied.
// The userdev artifact is a special name and will get all sorts of transformations applied to it.
minecraft 'net.minecraftforge:forge:1.12.2-14.23.5.2855'
// You may put jars on which you depend on in ./libs or you may define them like so..
// compile "some.group:artifact:version:classifier"
// compile "some.group:artifact:version"
// Real examples
// compile 'com.mod-buildcraft:buildcraft:6.0.8:dev' // adds buildcraft to the dev env
// compile 'com.googlecode.efficient-java-matrix-library:ejml:0.24' // adds ejml to the dev env
// The 'provided' configuration is for optional dependencies that exist at compile-time but might not at runtime.
// provided 'com.mod-buildcraft:buildcraft:6.0.8:dev'
// These dependencies get remapped to your current MCP mappings
// deobf 'com.mod-buildcraft:buildcraft:6.0.8:dev'
// For more info...
// http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html
// http://www.gradle.org/docs/current/userguide/dependency_management.html
}
// Example for how to get properties into the manifest for reading by the runtime..
jar {
manifest {
attributes([
"Specification-Title" : "mcef",
"Specification-Vendor" : "mcef",
"Specification-Version" : "3", // We are version 3 of ourselves
"Implementation-Title" : project.name,
"Implementation-Version" : project.version,
"Implementation-Vendor" : "mcef",
"Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ")
])
}
}
// Example configuration to allow publishing using the maven-publish task
// This is the preferred method to reobfuscate your jar file
jar.finalizedBy('reobfJar')
// However if you are in a multi-project build, dev time needs unobfed jar files, so you can delay the obfuscation until publishing by doing
//publish.dependsOn('reobfJar')
processResources {
// this will ensure that this task is redone when the versions change.
inputs.property "version", project.version
inputs.property "mcversion", "${mc_version}"
// replace stuff in mcmod.info, nothing else
from(sourceSets.main.resources.srcDirs) {
include 'mcmod.info'
// replace version and mcversion
expand 'version': project.version, 'mcversion': "${mc_version}"
}
// copy everything else except the mcmod.info
from(sourceSets.main.resources.srcDirs) {
exclude 'mcmod.info'
}
}

5
gradle.properties Normal file
View File

@@ -0,0 +1,5 @@
# Sets default memory used for gradle commands. Can be overridden by user or command line properties.
# This is required to provide enough memory for the Minecraft decompilation process.
org.gradle.jvmargs=-Xmx3G
org.gradle.daemon=false
mc_version=1.12.2-14.23.5.2855

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip

172
gradlew vendored Normal file
View File

@@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

84
gradlew.bat vendored Normal file
View File

@@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -0,0 +1,224 @@
package net.montoyo.mcef;
import net.montoyo.mcef.api.*;
import net.montoyo.mcef.utilities.Log;
public class BaseProxy implements API {
public void onPreInit() {
}
public void onInit() {
Log.info("MCEF is running on server. Nothing to do.");
}
@Override
public IBrowser createBrowser(String url, boolean transparent) {
Log.warning("A mod called API.createBrowser() from server! Returning null...");
return null;
}
@Override
public IBrowser createBrowser(String url) {
return createBrowser(url, false);
}
@Override
public void registerDisplayHandler(IDisplayHandler idh) {
Log.warning("A mod called API.registerDisplayHandler() from server!");
}
@Override
public boolean isVirtual() {
return true;
}
@Override
public void openExampleBrowser(String url) {
Log.warning("A mod called API.openExampleBrowser() from server! URL: %s", url);
}
@Override
public void registerJSQueryHandler(IJSQueryHandler iqh) {
Log.warning("A mod called API.registerJSQueryHandler() from server!");
}
@Override
public String mimeTypeFromExtension(String ext) {
Log.warning("A mod called API.mimeTypeFromExtension() from server!");
return null;
}
@Override
public void registerScheme(String name, Class<? extends IScheme> schemeClass, boolean std, boolean local, boolean displayIsolated, boolean secure, boolean corsEnabled, boolean cspBypassing, boolean fetchEnabled) {
Log.warning("A mod called API.registerScheme() from server!");
}
@Override
public boolean isSchemeRegistered(String name) {
Log.warning("A mod called API.isSchemeRegistered() from server!");
return false;
}
public void onShutdown() {
}
private static final int PUNYCODE_TMIN = 1;
private static final int PUNYCODE_TMAX = 26;
private static final int PUNYCODE_SKEW = 38;
private static final int PUNYCODE_DAMP = 700;
private static final int PUNYCODE_INITIAL_BIAS = 72;
private static final int PUNYCODE_INITIAL_N = 128;
private static int punycodeBiasAdapt(int delta, int numPoints, boolean firstTime) {
if(firstTime)
delta /= PUNYCODE_DAMP;
else
delta /= 2;
int k = 0;
delta = delta + delta / numPoints;
while(delta > ((36 - PUNYCODE_TMIN) * PUNYCODE_TMAX) / 2) {
delta /= 36 - PUNYCODE_TMIN;
k += 36;
}
return k + ((36 - PUNYCODE_TMIN + 1) * delta) / (delta + PUNYCODE_SKEW);
}
private static void punycodeEncodeNumber(StringBuilder dst, int q, int bias) {
boolean keepGoing = true;
for(int k = 36; keepGoing; k += 36) {
//Compute & clamp threshold
int t = k - bias;
if(t < PUNYCODE_TMIN)
t = PUNYCODE_TMIN;
else if(t > PUNYCODE_TMAX)
t = PUNYCODE_TMAX;
//Compute digit
int digit;
if(q < t) {
digit = q;
keepGoing = false;
} else {
digit = t + (q - t) % (36 - t);
q = (q - t) / (36 - t);
}
//Encode digit
if(digit < 26)
dst.append((char) ('a' + digit));
else
dst.append((char) ('0' + digit - 26));
}
}
private static String punycodeEncodeString(int[] input) {
StringBuilder output = new StringBuilder();
for(int i = 0; i < input.length; i++) {
if(input[i] < 128)
output.append((char) input[i]);
}
int n = PUNYCODE_INITIAL_N;
int delta = 0;
int bias = PUNYCODE_INITIAL_BIAS;
int h = output.length();
int b = h;
if(b > 0)
output.append('-');
while(h < input.length) {
int m = Integer.MAX_VALUE;
for(int i = 0; i < input.length; i++) {
if(input[i] >= n && input[i] < m)
m = input[i];
}
delta = delta + (m - n) * (h + 1);
n = m;
for(int i = 0; i < input.length; i++) {
int c = input[i];
if(c < n)
delta++;
if(c == n) {
punycodeEncodeNumber(output, delta, bias);
bias = punycodeBiasAdapt(delta, h + 1, h == b);
delta = 0;
h++;
}
}
delta++;
n++;
}
return "xn--" + output.toString();
}
@Override
public String punycode(String url) {
int protoEnd = url.indexOf("://");
if(protoEnd < 0)
protoEnd = 0;
else
protoEnd += 3;
int hostEnd = url.indexOf('/', protoEnd);
if(hostEnd < 0)
hostEnd = url.length();
String hostname = url.substring(protoEnd, hostEnd);
boolean doTransform = false;
for(int i = 0; i < hostname.length(); i++) {
if(hostname.charAt(i) >= 128) {
doTransform = true;
break;
}
}
if(!doTransform)
return url;
String[] parts = hostname.split("\\.");
StringBuilder sb = new StringBuilder();
boolean first = true;
sb.append(url, 0, protoEnd);
for(String p: parts) {
doTransform = false;
for(int i = 0; i < p.length(); i++) {
if(p.charAt(i) >= 128) {
doTransform = true;
break;
}
}
if(first)
first = false;
else
sb.append('.');
if(doTransform)
sb.append(punycodeEncodeString(p.codePoints().toArray()));
else
sb.append(p);
}
sb.append(url, hostEnd, url.length());
return sb.toString();
}
}

View File

@@ -0,0 +1,102 @@
package net.montoyo.mcef;
import net.montoyo.mcef.utilities.Log;
import org.apache.commons.io.IOUtils;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.cert.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class LetsEncryptAdder {
private static final String CERT_ALIAS = "lets-encrypt-x3-cross-signed";
private static final String KEYSTORE_PASSWORD = "changeit";
public static void addLetsEncryptCertificate() throws Exception {
try (InputStream certStream = LetsEncryptAdder.class
.getResourceAsStream("/assets/letsencrypt/isrgrootx1.der")) {
if (certStream == null) {
throw new FileNotFoundException("Let's Encrypt root certificate file not found.");
}
Path ksPath = Paths.get(System.getProperty("java.home"), "lib", "security", "cacerts");
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
try (InputStream ksInputStream = Files.newInputStream(ksPath)) {
keyStore.load(ksInputStream, KEYSTORE_PASSWORD.toCharArray());
}
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate certificate;
try (BufferedInputStream caInput = new BufferedInputStream(certStream)) {
certificate = cf.generateCertificate(caInput);
}
keyStore.setCertificateEntry(CERT_ALIAS, certificate);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
SSLContext.setDefault(sslContext);
}
}
public static void doStuff() {
String version = System.getProperty("java.version");
Pattern versionPattern = Pattern.compile("^(\\d+)(?:\\.(\\d+))?.*?_?(\\d+)?");
Matcher matcher = versionPattern.matcher(version);
int majorVersion = 7;
int minorVersion = 110; // Default fallback
if (matcher.matches()) {
majorVersion = Integer.parseInt(matcher.group(1));
if (matcher.group(3) != null) {
minorVersion = Integer.parseInt(matcher.group(3));
}
} else {
Log.info("[Let'sEncrypt SSL] Failed to parse Java version. Applying fix anyway.");
}
if ((majorVersion == 7 && minorVersion >= 111) || (majorVersion == 8 && minorVersion >= 101)) {
Log.info("[Let'sEncrypt SSL] Not running as Java version is at least Java " + majorVersion + "u"
+ minorVersion);
return;
}
try {
Log.info("[Let'sEncrypt SSL] Adding Let's Encrypt certificate...");
addLetsEncryptCertificate();
Log.info("[Let'sEncrypt SSL] Done! Attempting to connect to https://helloworld.letsencrypt.org...");
URL url = new URL("https://helloworld.letsencrypt.org");
URLConnection conn = url.openConnection();
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
String response;
try (InputStream inputStream = conn.getInputStream()) {
response = IOUtils.toString(inputStream);
}
if (response.isEmpty()) {
Log.error("[Let'sEncrypt SSL] Unknown error: Unable to access Let's Encrypt resources.");
} else {
Log.info("[Let'sEncrypt SSL] Success! You can now access Let's Encrypt-secured resources.");
}
} catch (Exception e) {
Log.error("[Let'sEncrypt SSL] Failed to add the Let's Encrypt root certificate.", e);
}
}
}

View File

@@ -0,0 +1,98 @@
package net.montoyo.mcef;
import net.minecraftforge.common.config.Configuration;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.SidedProxy;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.montoyo.mcef.utilities.Log;
import net.montoyo.mcef.LetsEncryptAdder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Mod(modid = "mcef", name = "MCEF", version = MCEF.VERSION)
public class MCEF {
public static final String VERSION = "1.41";
public static boolean ENABLE_EXAMPLE;
public static boolean SKIP_UPDATES;
public static boolean WARN_UPDATES;
public static boolean USE_FORGE_SPLASH;
public static String FORCE_MIRROR = null;
public static String HOME_PAGE;
private static String DEF_CEF_ARGS;
public static String[] CEF_ARGS = new String[0];
public static boolean CHECK_VRAM_LEAK;
public static boolean SHUTDOWN_JCEF;
public static boolean SECURE_MIRRORS_ONLY;
@Mod.Instance(owner = "mcef")
public static MCEF INSTANCE;
@SidedProxy(serverSide = "net.montoyo.mcef.BaseProxy", clientSide = "net.montoyo.mcef.client.ClientProxy")
public static BaseProxy PROXY;
@Mod.EventHandler
public void onPreInit(FMLPreInitializationEvent ev) {
Log.info("PREINT, Loading MCEF config...");
Configuration cfg = new Configuration(ev.getSuggestedConfigurationFile());
// Default CEF arguments
DEF_CEF_ARGS = "--enable-widevine --disable-gpu --disable-gpu-compositing";
//Config: main
SKIP_UPDATES = cfg.getBoolean("skipUpdates", "main", false, "Do not update binaries.");
WARN_UPDATES = cfg.getBoolean("warnUpdates", "main", true, "Tells in the chat if a new version of MCEF is available.");
USE_FORGE_SPLASH = cfg.getBoolean("useForgeSplash", "main", true, "Use Forge's splash screen to display resource download progress (may be unstable).");
SHUTDOWN_JCEF = cfg.getBoolean("shutdownJcef", "main", false, "Set this to true if your Java process hangs after closing Minecraft. This is disabled by default because it makes the launcher think Minecraft crashed...");
SECURE_MIRRORS_ONLY = cfg.getBoolean("secureMirrorsOnly", "main", true, "Only enable secure (HTTPS) mirror. This should be kept to true unless you know what you're doing.");
// Merge default and user CEF arguments:
String[] defaultArgs = DEF_CEF_ARGS.split("\\s+");
String[] userArgs = cfg.getString("cefArgs", "main", DEF_CEF_ARGS, "Command line arguments passed to CEF. For advanced users.").split("\\s+");
List<String> combinedArgs = new ArrayList<>(Arrays.asList(defaultArgs)); // Start with the default args
combinedArgs.addAll(Arrays.asList(userArgs)); // Add the user-provided args
// Convert back to array
CEF_ARGS = combinedArgs.toArray(new String[0]);
String mirror = cfg.getString("forcedMirror", "main", "", "A URL that contains every MCEF resources; for instance https://montoyo.net/jcef.").trim();
if (mirror.length() > 0)
FORCE_MIRROR = mirror;
//Config: exampleBrowser
ENABLE_EXAMPLE = cfg.getBoolean("enable", "exampleBrowser", true, "Set this to false if you don't want to enable the F10 browser.");
HOME_PAGE = cfg.getString("home", "exampleBrowser", "mod://mcef/home.html", "The home page of the F10 browser.");
//Config: debug
CHECK_VRAM_LEAK = cfg.getBoolean("checkForVRAMLeak", "debug", false, "Track allocated OpenGL textures to make sure there's no leak");
// Save the configuration
cfg.save();
// Add Let's Encrypt certificate
try {
LetsEncryptAdder.addLetsEncryptCertificate();
} catch (Exception e) {
Log.error("Failed to add Let's Encrypt certificate: " + e.getMessage());
}
PROXY.onPreInit();
}
@Mod.EventHandler
public void onInit(FMLInitializationEvent ev) {
Log.info("Now initializing MCEF v%s...", VERSION);
PROXY.onInit();
}
//Called by Minecraft.run() if the ShutdownPatcher succeeded
public static void onMinecraftShutdown() {
Log.info("Minecraft shutdown hook called!");
PROXY.onShutdown();
}
}

View File

@@ -0,0 +1,96 @@
package net.montoyo.mcef.api;
public interface API {
/**
* Creates a web view and loads the specified URL.
*
* @param url The URL to start from.
* @param transp True is the web view can be transparent
* @return The created web view, or null if this is run on server.
*/
IBrowser createBrowser(String url, boolean transp);
/**
* Same as {@link #createBrowser(String, boolean) createBrowser} but with transp set to false.
*
* @param url The URL to start from.
* @return The created web view, or null if this is run on server.
*/
IBrowser createBrowser(String url);
/**
* Registers a display handler.
* @param idh The display handler to register.
* @see IDisplayHandler
*/
void registerDisplayHandler(IDisplayHandler idh);
/**
* Registers a JavaScript query handler.
* @param iqh The JavaScript query handler to register.
* @see IJSQueryHandler
*/
void registerJSQueryHandler(IJSQueryHandler iqh);
/**
* Call this to know if MCEF is in virtual mode.
* MCEF switches in virtual mode if something failed to load.
* When in virtual mode, {@link #createBrowser(String, boolean) createBrowser} will generate fake browsers that does nothing.
*
* @return true if MCEF is in virtual mode.
*/
boolean isVirtual();
/**
* Opens the example browser UI.
* @param url The URL to load.
* @see net.montoyo.mcef.example.ExampleMod
*/
void openExampleBrowser(String url);
/**
* Returns a mime type from a file extension,
* or null, if there is no mapping for this extension.
*
* @param ext File extension, without the '.'
* @return A mime type corresponding to this extension, or null if none could be found.
*/
String mimeTypeFromExtension(String ext);
/**
* Registers a scheme (custom URLs).
* This has to be done in preInit, init happens too late.
*
* @param name The name of the scheme
* @param schemeClass The class that will be instantiated for request
* @param std Whether the scheme has standards URLs, like "protocol://host/path". Non standard is just "procotol:"
* @param local If the scheme is local, some special security rules are applied, just like the "file:///" scheme.
* @param displayIsolated iframes (and such things) from an external scheme cannot access pages from this scheme.
* @param secure Whether this scheme is considered as secure (like https)
* @param corsEnabled To allow this scheme to send CORS requests
* @param cspBypassing true if this scheme can bypass Content-Security-Policy (CSP) checks
* @param fetchEnabled true if this scheme can perform Fetch API requests
*
* @see org.cef.callback.CefSchemeRegistrar
*/
void registerScheme(String name, Class<? extends IScheme> schemeClass, boolean std, boolean local, boolean displayIsolated, boolean secure, boolean corsEnabled, boolean cspBypassing, boolean fetchEnabled);
/**
* Checks whether the scheme with name 'name' is already registered.
*
* @param name The name of the scheme
* @return true if it is registered, false otherwise.
*/
boolean isSchemeRegistered(String name);
/**
* If the hostname of the passed URL contains some illegal characters, it converts the hostname to punnycode.
* If it fails to parse the URL or if the specified URL contains a valid hostname, returns the input string.
*
* @param url The URL to transform
* @return The transformed URL
*/
String punycode(String url);
}

View File

@@ -0,0 +1,138 @@
package net.montoyo.mcef.api;
public interface IBrowser {
/**
* Destroys the web view.
*/
void close();
/**
* Resizes the web view.
*
* @param width The new width in pixels.
* @param height The new height in pixels.
*/
void resize(int width, int height);
/**
* Renders the web view into Minecraft.
*
* @param x1 The first X coord of the rectangle to render the web view on (left).
* @param y1 The first Y coord of the rectangle to render the web view on (top).
* @param x2 The second X coord of the rectangle to render the web view on (right).
* @param y2 The second Y coord of the rectangle to render the web view on (bottom).
*/
void draw(double x1, double y1, double x2, double y2);
/**
* Gets the OpenGL texture ID of the web view.
* @return the OpenGL texture ID of the web view.
*/
int getTextureID();
/**
* Simulates a mouse move.
*
* @param x The X coord of the mouse.
* @param y The Y coord of the mouse.
* @param mods The key modifiers (shift, ctrl, alt)
* @param left true if the mouse is out of (left) the web view.
*/
void injectMouseMove(int x, int y, int mods, boolean left);
/**
* Simulates a mouse click.
*
* @param x The X coord of the mouse.
* @param y The Y coord of the mouse.
* @param mods The key modifiers (shift, ctrl, alt)
* @param btn The mouse button to press. See {@link java.awt.event.MouseEvent}
* @param pressed true if the button is pressed, false if it is released.
* @param ccnt The click count. You probably want this to be 1.
*/
void injectMouseButton(int x, int y, int mods, int btn, boolean pressed, int ccnt);
/**
* Simulates a keyboard type.
*
* @param c The typed character.
* @param mods The key modifiers (shift, ctrl, alt)
*/
void injectKeyTyped(char c, int mods);
/**
* Simulates a key press.
*
* @param keyCode LWJGL key code of the pressed key.
* @param c The resulting character
* @param mods The key modifiers (shift, ctrl, alt)
*/
void injectKeyPressedByKeyCode(int keyCode, char c, int mods);
/**
* Simulates a key release.
*
* @param keyCode LWJGL key code of the pressed key.
* @param c The resulting character
* @param mods The key modifiers (shift, ctrl, alt)
*/
void injectKeyReleasedByKeyCode(int keyCode, char c, int mods);
/**
* Simulates a mouse wheel.
*
* @param x The X coord of the mouse.
* @param y The Y coord of the mouse.
* @param mods The key modifiers (shift, ctrl, alt)
* @param amount The amount to scroll.
* @param rot The number of "clicks" by which the mouse wheel was rotated.
*/
void injectMouseWheel(int x, int y, int mods, int amount, int rot);
/**
* Runs JavaScript code on the web view.
*
* @param script The script to run.
* @param frame The URL of the frame to run the script on. Let this EMPTY (!= null) for the default frame.
*/
void runJS(String script, String frame);
/**
* Changes the current page's address.
* @param url The URL to load.
*/
void loadURL(String url);
/**
* Returns to the previous address.
*/
void goBack();
/**
* Undoes {@link #goBack()}
*/
void goForward();
/**
* Retrieves the current browser's location.
* @return The current browser's URL.
*/
String getURL();
/**
* Asynchronously retrieves the current page's source code.
* FIXME: This is asynchronous; so you won't get your source right after calling this method!
*
* @param isv An object that handles strings.
*/
void visitSource(IStringVisitor isv);
/**
* Checks if the page is currently being loaded.
*
* @return true if the page is still being loaded, false otherwise.
*/
boolean isPageLoading();
}

View File

@@ -0,0 +1,35 @@
package net.montoyo.mcef.api;
public interface IDisplayHandler {
/**
* Handle address changes.
* @param browser The browser generating the event.
* @param url The new URL.
*/
void onAddressChange(IBrowser browser, String url);
/**
* Handle title changes.
* @param browser The browser generating the event.
* @param title The new title.
*/
void onTitleChange(IBrowser browser, String title);
/**
* Called when the browser is about to display a tooltip.
*
* @param browser The browser generating the event.
* @param text Contains the text that will be displayed in the tooltip.
*/
void onTooltip(IBrowser browser, String text);
/**
* Called when the browser receives a status message.
*
* @param browser The browser generating the event.
* @param value Contains the text that will be displayed in the status message.
*/
void onStatusMessage(IBrowser browser, String value);
}

View File

@@ -0,0 +1,24 @@
package net.montoyo.mcef.api;
/**
* Use this interface to answer to JavaScript queries.
* @author montoyo
*
*/
public interface IJSQueryCallback {
/**
* If the query succeeded, call this.
* @param response Whatever you want.
*/
void success(String response);
/**
* If the query failed, call this.
*
* @param errId Whatever you want.
* @param errMsg Whatever you want.
*/
void failure(int errId, String errMsg);
}

View File

@@ -0,0 +1,39 @@
package net.montoyo.mcef.api;
/**
* Use this to handle asynchronous JavaScript queries.
* Queries are sent to Java using var request_id = window.mcefQuery({ request: 'my_request', persistent: false, onSuccess: function(response) {}, onFailure: function(error_code, error_message) {} })
* Queries may be cancelled using window.mcefCancel(request_id)
*
* @author montoyo
*
*/
public interface IJSQueryHandler {
/**
* Handles a JavaScript query. Queries are created using the following JavaScript code:
* var request_id = window.mcefQuery({ request: 'my_request', persistent: false, onSuccess: function(response) {}, onFailure: function(error_code, error_message) {} })
*
* Be aware that this method is NOT called from the main Minecraft thread, and you have to handle synchronisation yourself.
*
* @param b The browser the query has been created from.
* @param queryId The unique query identifier.
* @param query The "request" field.
* @param persistent If the query is persistent or not. If it is, then cb will remain valid when leaving this method.
* @param cb Use this to answer the query; this means call the JS onSuccess and onFailure functions.
* @return true if the query was handled.
*/
boolean handleQuery(IBrowser b, long queryId, String query, boolean persistent, IJSQueryCallback cb);
/**
* Handles a JavaScript query cancellation. Queries are cancelled using the following JavaScript code:
* window.mcefCancel(request_id)
*
* Be aware that this method is NOT called from the main Minecraft thread, and you have to handle synchronisation yourself.
*
* @param b The browser the query has been cancelled from.
* @param queryId The unique query identifier given in {@link #handleQuery(IBrowser, long, String, boolean, IJSQueryCallback)}
*/
void cancelQuery(IBrowser b, long queryId);
}

View File

@@ -0,0 +1,9 @@
package net.montoyo.mcef.api;
public interface IScheme {
SchemePreResponse processRequest(String url);
void getResponseHeaders(ISchemeResponseHeaders resp);
boolean readResponse(ISchemeResponseData data);
}

View File

@@ -0,0 +1,9 @@
package net.montoyo.mcef.api;
public interface ISchemeResponseData {
byte[] getDataArray();
int getBytesToRead();
void setAmountRead(int rd);
}

View File

@@ -0,0 +1,11 @@
package net.montoyo.mcef.api;
public interface ISchemeResponseHeaders {
void setMimeType(String mt);
void setStatus(int status);
void setStatusText(String st);
void setResponseLength(int len);
void setRedirectURL(String redirURL);
}

View File

@@ -0,0 +1,7 @@
package net.montoyo.mcef.api;
public interface IStringVisitor {
void visit(String str);
}

View File

@@ -0,0 +1,29 @@
package net.montoyo.mcef.api;
import net.minecraftforge.fml.common.Loader;
public class MCEFApi {
/**
* Call this to get the API instance.
* @return the MCEF API or null if something failed.
*/
public static API getAPI() {
try {
Class cls = Class.forName("net.montoyo.mcef.MCEF");
return (API) cls.getField("PROXY").get(null);
} catch(Throwable t) {
t.printStackTrace();
return null;
}
}
/**
* Checks if MCEF was loaded by forge.
* @return true if it is loaded. false otherwise.
*/
public static boolean isMCEFLoaded() {
return Loader.isModLoaded("mcef");
}
}

View File

@@ -0,0 +1,9 @@
package net.montoyo.mcef.api;
public enum SchemePreResponse {
NOT_HANDLED,
HANDLED_CONTINUE,
HANDLED_CANCEL
}

View File

@@ -0,0 +1,103 @@
package net.montoyo.mcef.client;
import net.montoyo.mcef.MCEF;
import net.montoyo.mcef.api.IScheme;
import net.montoyo.mcef.utilities.Log;
import org.cef.CefApp;
import org.cef.browser.CefBrowser;
import org.cef.browser.CefFrame;
import org.cef.callback.CefSchemeHandlerFactory;
import org.cef.callback.CefSchemeRegistrar;
import org.cef.handler.CefAppHandlerAdapter;
import org.cef.handler.CefResourceHandler;
import org.cef.network.CefRequest;
import java.util.HashMap;
import java.util.Map;
public class AppHandler extends CefAppHandlerAdapter {
public AppHandler() {
super(new String[] {});
}
private static class SchemeData {
private Class<? extends IScheme> cls;
private boolean std;
private boolean local;
private boolean dispIsolated;
private boolean secure;
private boolean corsEnabled;
private boolean cspBypassing;
private boolean fetchEnabled;
private SchemeData(Class<? extends IScheme> cls, boolean std, boolean local, boolean dispIsolated, boolean secure, boolean corsEnabled, boolean cspBypassing, boolean fetchEnabled) {
this.cls = cls;
this.std = std;
this.local = local;
this.dispIsolated = dispIsolated;
this.secure = secure;
this.corsEnabled = corsEnabled;
this.cspBypassing = cspBypassing;
this.fetchEnabled = fetchEnabled;
}
}
private final HashMap<String, SchemeData> schemeMap = new HashMap<>();
public void registerScheme(String name, Class<? extends IScheme> cls, boolean std, boolean local, boolean dispIsolated, boolean secure, boolean corsEnabled, boolean cspBypassing, boolean fetchEnabled) {
schemeMap.put(name, new SchemeData(cls, std, local, dispIsolated, secure, corsEnabled, cspBypassing, fetchEnabled));
}
public boolean isSchemeRegistered(String name) {
return schemeMap.containsKey(name);
}
@Override
public void onRegisterCustomSchemes(CefSchemeRegistrar reg) {
int cnt = 0;
for(Map.Entry<String, SchemeData> entry : schemeMap.entrySet()) {
SchemeData v = entry.getValue();
if(reg.addCustomScheme(entry.getKey(), v.std, v.local, v.dispIsolated, v.secure, v.corsEnabled, v.cspBypassing, v.fetchEnabled))
cnt++;
else
Log.error("Could not register scheme %s", entry.getKey());
}
Log.info("%d schemes registered", cnt);
}
@Override
public void onContextInitialized() {
CefApp app = ((ClientProxy) MCEF.PROXY).getCefApp();
for(Map.Entry<String, SchemeData> entry : schemeMap.entrySet())
app.registerSchemeHandlerFactory(entry.getKey(), "", new SchemeHandlerFactory(entry.getValue().cls));
}
private static class SchemeHandlerFactory implements CefSchemeHandlerFactory {
private Class<? extends IScheme> cls;
private SchemeHandlerFactory(Class<? extends IScheme> cls) {
this.cls = cls;
}
@Override
public CefResourceHandler create(CefBrowser browser, CefFrame frame, String schemeName, CefRequest request) {
try {
return new SchemeResourceHandler(cls.newInstance());
} catch(Throwable t) {
t.printStackTrace();
return null;
}
}
}
}

View File

@@ -0,0 +1,374 @@
package net.montoyo.mcef.client;
import net.minecraft.client.Minecraft;
import net.minecraft.util.text.Style;
import net.minecraft.util.text.TextComponentString;
import net.minecraft.util.text.TextFormatting;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.client.SplashProgress;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.PlayerEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import net.montoyo.mcef.BaseProxy;
import net.montoyo.mcef.MCEF;
import net.montoyo.mcef.api.IBrowser;
import net.montoyo.mcef.api.IDisplayHandler;
import net.montoyo.mcef.api.IJSQueryHandler;
import net.montoyo.mcef.api.IScheme;
import net.montoyo.mcef.coremod.ShutdownPatcher;
import net.montoyo.mcef.example.ExampleMod;
import net.montoyo.mcef.remote.RemoteConfig;
import net.montoyo.mcef.utilities.ForgeProgressListener;
import net.montoyo.mcef.utilities.IProgressListener;
import net.montoyo.mcef.utilities.Log;
import net.montoyo.mcef.utilities.Util;
import net.montoyo.mcef.virtual.VirtualBrowser;
import org.cef.CefApp;
import org.cef.CefClient;
import org.cef.CefSettings;
import org.cef.OS;
import org.cef.browser.CefBrowser;
import org.cef.browser.CefBrowserOsr;
import org.cef.browser.CefMessageRouter;
import org.cef.browser.CefMessageRouter.CefMessageRouterConfig;
import org.cef.browser.CefRenderer;
import org.cef.handler.CefLifeSpanHandlerAdapter;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ClientProxy extends BaseProxy {
public static String ROOT = ".";
public static String JCEF_ROOT = ".";
public static boolean VIRTUAL = false;
private CefApp cefApp;
private CefClient cefClient;
private CefMessageRouter cefRouter;
private final ArrayList<CefBrowserOsr> browsers = new ArrayList<>();
private String updateStr;
private final Minecraft mc = Minecraft.getMinecraft();
private final DisplayHandler displayHandler = new DisplayHandler();
private final HashMap<String, String> mimeTypeMap = new HashMap<>();
private final AppHandler appHandler = new AppHandler();
private ExampleMod exampleMod;
@Override
public void onPreInit() {
exampleMod = new ExampleMod();
exampleMod.onPreInit(); //Do it even if example mod is disabled because it registers the "mod://" scheme
}
@Override
public void onInit() {
appHandler.setArgs(MCEF.CEF_ARGS);
boolean enableForgeSplash = false;
try {
Field f = SplashProgress.class.getDeclaredField("enabled");
f.setAccessible(true);
enableForgeSplash = f.getBoolean(null);
} catch (Throwable t) {
t.printStackTrace();
}
ROOT = mc.mcDataDir.getAbsolutePath().replaceAll("\\\\", "/");
if (ROOT.endsWith("."))
ROOT = ROOT.substring(0, ROOT.length() - 1);
if (ROOT.endsWith("/"))
ROOT = ROOT.substring(0, ROOT.length() - 1);
JCEF_ROOT = ROOT + "/jcef";
File fileListing = new File(new File(ROOT), "config");
IProgressListener ipl;
RemoteConfig cfg = new RemoteConfig();
if (MCEF.USE_FORGE_SPLASH && enableForgeSplash)
ipl = new ForgeProgressListener();
else
ipl = new UpdateFrame();
cfg.load();
if (!cfg.downloadMCEF(ipl)) {
Log.warning("Going in virtual mode; couldn't download resources.");
VIRTUAL = true;
return;
}
updateStr = cfg.getUpdateString();
ipl.onProgressEnd();
if (OS.isLinux()) {
File subproc = new File(JCEF_ROOT, "jcef_helper");
// Attempt to make the CEF subprocess executable if not
if (!subproc.canExecute()) {
try {
int retCode = Runtime.getRuntime().exec(new String[]{"/usr/bin/chmod", "+x", subproc.getAbsolutePath()}).waitFor();
if (retCode != 0)
throw new RuntimeException("chmod exited with code " + retCode);
} catch (Throwable t) {
Log.errorEx("Error while giving execution rights to jcef_helper. MCEF will enter virtual mode. You can fix this by chmoding jcef_helper manually.", t);
VIRTUAL = true;
}
}
}
if (VIRTUAL)
return;
CefSettings settings = new CefSettings();
settings.windowless_rendering_enabled = true;
settings.background_color = settings.new ColorType(0, 255, 255, 255);
settings.cache_path = (new File(JCEF_ROOT, "cache")).getAbsolutePath();
CefApp.startup(MCEF.CEF_ARGS);
cefApp = CefApp.getInstance(settings);
// Custom scheme broken on Linux, for now
if (!OS.isLinux()) {
CefApp.addAppHandler(appHandler);
}
loadMimeTypeMapping();
cefClient = cefApp.createClient();
Log.info(cefApp.getVersion().toString());
cefRouter = CefMessageRouter.create(new CefMessageRouterConfig("mcefQuery", "mcefCancel"));
cefClient.addMessageRouter(cefRouter);
cefClient.addDisplayHandler(displayHandler);
cefClient.addLifeSpanHandler(new CefLifeSpanHandlerAdapter() {
@Override
public boolean doClose(CefBrowser browser) {
browser.close(true);
return false;
}
});
if (!ShutdownPatcher.didPatchSucceed()) {
Log.warning("ShutdownPatcher failed to patch Minecraft.run() method; starting ShutdownThread...");
(new ShutdownThread()).start();
}
MinecraftForge.EVENT_BUS.register(this);
if (MCEF.ENABLE_EXAMPLE)
exampleMod.onInit();
Log.info("MCEF loaded successfuly.");
}
public CefApp getCefApp() {
return cefApp;
}
@Override
public IBrowser createBrowser(String url, boolean transp) {
if (VIRTUAL)
return new VirtualBrowser();
CefBrowserOsr ret = (CefBrowserOsr) cefClient.createBrowser(url, true, transp);
ret.setCloseAllowed();
ret.createImmediately();
browsers.add(ret);
return ret;
}
@Override
public void registerDisplayHandler(IDisplayHandler idh) {
displayHandler.addHandler(idh);
}
@Override
public boolean isVirtual() {
return VIRTUAL;
}
@Override
public void openExampleBrowser(String url) {
if (MCEF.ENABLE_EXAMPLE)
exampleMod.showScreen(url);
}
@Override
public void registerJSQueryHandler(IJSQueryHandler iqh) {
if (!VIRTUAL)
cefRouter.addHandler(new MessageRouter(iqh), false);
}
@Override
public void registerScheme(String name, Class<? extends IScheme> schemeClass, boolean std, boolean local, boolean displayIsolated, boolean secure, boolean corsEnabled, boolean cspBypassing, boolean fetchEnabled) {
appHandler.registerScheme(name, schemeClass, std, local, displayIsolated, secure, corsEnabled, cspBypassing, fetchEnabled);
}
@Override
public boolean isSchemeRegistered(String name) {
return appHandler.isSchemeRegistered(name);
}
@SubscribeEvent
public void onTick(TickEvent.RenderTickEvent ev) {
if (ev.phase == TickEvent.Phase.START) {
mc.mcProfiler.startSection("MCEF");
if (cefApp != null)
cefApp.N_DoMessageLoopWork();
for (CefBrowserOsr b : browsers)
b.mcefUpdate();
displayHandler.update();
mc.mcProfiler.endSection();
}
}
@SubscribeEvent
public void onLogin(PlayerEvent.PlayerLoggedInEvent ev) {
if (updateStr == null || !MCEF.WARN_UPDATES)
return;
Style cs = new Style();
cs.setColor(TextFormatting.LIGHT_PURPLE);
TextComponentString cct = new TextComponentString(updateStr);
cct.setStyle(cs);
ev.player.sendMessage(cct);
}
public void removeBrowser(CefBrowserOsr b) {
browsers.remove(b);
}
@Override
public IBrowser createBrowser(String url) {
return createBrowser(url, false);
}
private void runMessageLoopFor(long ms) {
final long start = System.currentTimeMillis();
do {
cefApp.N_DoMessageLoopWork();
} while (System.currentTimeMillis() - start < ms);
}
@Override
public void onShutdown() {
if (VIRTUAL)
return;
Log.info("Shutting down JCEF...");
CefBrowserOsr.CLEANUP = false; //Workaround
for (CefBrowserOsr b : browsers)
b.close();
browsers.clear();
if (MCEF.CHECK_VRAM_LEAK)
CefRenderer.dumpVRAMLeak();
runMessageLoopFor(100);
CefApp.forceShutdownState();
cefClient.dispose();
if (MCEF.SHUTDOWN_JCEF)
cefApp.N_Shutdown();
}
public void loadMimeTypeMapping() {
Pattern p = Pattern.compile("^(\\S+)\\s+(\\S+)\\s*(\\S*)\\s*(\\S*)$");
String line = "";
int cLine = 0;
mimeTypeMap.clear();
try {
BufferedReader br = new BufferedReader(new InputStreamReader(ClientProxy.class.getResourceAsStream("/assets/mcef/mime.types")));
while (true) {
cLine++;
line = br.readLine();
if (line == null)
break;
line = line.trim();
if (!line.startsWith("#")) {
Matcher m = p.matcher(line);
if (!m.matches())
continue;
mimeTypeMap.put(m.group(2), m.group(1));
if (m.groupCount() >= 4 && !m.group(3).isEmpty()) {
mimeTypeMap.put(m.group(3), m.group(1));
if (m.groupCount() >= 5 && !m.group(4).isEmpty())
mimeTypeMap.put(m.group(4), m.group(1));
}
}
}
Util.close(br);
} catch (Throwable e) {
Log.error("[Mime Types] Error while parsing \"%s\" at line %d:", line, cLine);
e.printStackTrace();
}
Log.info("Loaded %d mime types", mimeTypeMap.size());
}
@Override
public String mimeTypeFromExtension(String ext) {
ext = ext.toLowerCase();
String ret = mimeTypeMap.get(ext);
if (ret != null)
return ret;
//If the mimeTypeMap couldn't be loaded, fall back to common things
switch (ext) {
case "htm":
case "html":
return "text/html";
case "css":
return "text/css";
case "js":
return "text/javascript";
case "png":
return "image/png";
case "jpg":
case "jpeg":
return "image/jpeg";
case "gif":
return "image/gif";
case "svg":
return "image/svg+xml";
case "xml":
return "text/xml";
case "txt":
return "text/plain";
default:
return null;
}
}
}

View File

@@ -0,0 +1,116 @@
package net.montoyo.mcef.client;
import net.montoyo.mcef.api.IDisplayHandler;
import org.cef.CefSettings;
import org.cef.browser.CefBrowser;
import org.cef.browser.CefBrowserOsr;
import org.cef.browser.CefFrame;
import org.cef.handler.CefDisplayHandler;
import java.util.ArrayList;
public class DisplayHandler implements CefDisplayHandler {
private final ArrayList<IDisplayHandler> list = new ArrayList<>();
private final ArrayList<EventData> queue = new ArrayList<>();
private enum EventType {
ADDRESS_CHANGE,
TITLE_CHANGE,
TOOLTIP,
STATUS_MESSAGE;
}
private static final class EventData {
private final CefBrowser browser;
private final String data;
private final EventType type;
private EventData(CefBrowser b, String d, EventType t) {
browser = b;
data = d;
type = t;
}
private void execute(IDisplayHandler idh) {
switch(type) {
case ADDRESS_CHANGE:
idh.onAddressChange((CefBrowserOsr) browser, data);
break;
case TITLE_CHANGE:
idh.onTitleChange((CefBrowserOsr) browser, data);
break;
case TOOLTIP:
idh.onTooltip((CefBrowserOsr) browser, data);
break;
case STATUS_MESSAGE:
idh.onStatusMessage((CefBrowserOsr) browser, data);
break;
}
}
}
@Override
public void onAddressChange(CefBrowser browser, CefFrame frame, String url) {
synchronized(queue) {
queue.add(new EventData(browser, url, EventType.ADDRESS_CHANGE));
}
}
@Override
public void onTitleChange(CefBrowser browser, String title) {
synchronized(queue) {
queue.add(new EventData(browser, title, EventType.TITLE_CHANGE));
}
}
@Override
public boolean onTooltip(CefBrowser browser, String text) {
synchronized(queue) {
queue.add(new EventData(browser, text, EventType.TOOLTIP));
}
return false;
}
@Override
public void onStatusMessage(CefBrowser browser, String value) {
synchronized(queue) {
queue.add(new EventData(browser, value, EventType.STATUS_MESSAGE));
}
}
@Override
public boolean onConsoleMessage(CefBrowser browser, CefSettings.LogSeverity level, String message, String source, int line) {
return false;
}
@Override
public boolean onCursorChange(CefBrowser browser, int cursorType) {
return false;
}
public void addHandler(IDisplayHandler h) {
list.add(h);
}
public void update() {
synchronized(queue) {
while(!queue.isEmpty()) {
EventData ed = queue.remove(0);
for(IDisplayHandler idh : list)
ed.execute(idh);
}
}
}
}

View File

@@ -0,0 +1,29 @@
package net.montoyo.mcef.client;
import net.montoyo.mcef.api.IJSQueryHandler;
import org.cef.browser.CefBrowser;
import org.cef.browser.CefBrowserOsr;
import org.cef.browser.CefFrame;
import org.cef.callback.CefQueryCallback;
import org.cef.handler.CefMessageRouterHandlerAdapter;
public class MessageRouter extends CefMessageRouterHandlerAdapter {
private IJSQueryHandler handler;
public MessageRouter(IJSQueryHandler h) {
handler = h;
}
@Override
public boolean onQuery(CefBrowser browser, CefFrame frame, long query_id, String request, boolean persistent, CefQueryCallback callback) {
return handler.handleQuery((CefBrowserOsr) browser, query_id, request, persistent, new QueryCallback(callback));
}
@Override
public void onQueryCanceled(CefBrowser browser, CefFrame frame, long query_id) {
handler.cancelQuery((CefBrowserOsr) browser, query_id);
}
}

View File

@@ -0,0 +1,24 @@
package net.montoyo.mcef.client;
import org.cef.callback.CefQueryCallback;
import net.montoyo.mcef.api.IJSQueryCallback;
public class QueryCallback implements IJSQueryCallback {
private CefQueryCallback cb;
public QueryCallback(CefQueryCallback cb) {
this.cb = cb;
}
@Override
public void success(String response) {
cb.success(response);
}
@Override
public void failure(int errId, String errMsg) {
cb.failure(errId, errMsg);
}
}

View File

@@ -0,0 +1,48 @@
package net.montoyo.mcef.client;
import net.montoyo.mcef.api.IScheme;
import net.montoyo.mcef.api.SchemePreResponse;
import org.cef.callback.CefCallback;
import org.cef.handler.CefResourceHandlerAdapter;
import org.cef.misc.IntRef;
import org.cef.misc.StringRef;
import org.cef.network.CefRequest;
import org.cef.network.CefResponse;
public class SchemeResourceHandler extends CefResourceHandlerAdapter {
private final IScheme scheme;
public SchemeResourceHandler(IScheme scm) {
scheme = scm;
}
@Override
public boolean processRequest(CefRequest request, CefCallback callback) {
SchemePreResponse resp = scheme.processRequest(request.getURL());
switch(resp) {
case HANDLED_CONTINUE:
callback.Continue();
return true;
case HANDLED_CANCEL:
callback.cancel();
return true;
default:
return false;
}
}
@Override
public void getResponseHeaders(CefResponse response, IntRef response_length, StringRef redirectUrl) {
scheme.getResponseHeaders(new SchemeResponseHeaders(response, response_length, redirectUrl));
}
@Override
public boolean readResponse(byte[] data_out, int bytes_to_read, IntRef bytes_read, CefCallback callback) {
return scheme.readResponse(new SchemeResponseData(data_out, bytes_to_read, bytes_read));
}
}

View File

@@ -0,0 +1,33 @@
package net.montoyo.mcef.client;
import net.montoyo.mcef.api.ISchemeResponseData;
import org.cef.misc.IntRef;
public class SchemeResponseData implements ISchemeResponseData {
private final byte[] data;
private final int toRead;
private final IntRef read;
public SchemeResponseData(byte[] data, int toRead, IntRef read) {
this.data = data;
this.toRead = toRead;
this.read = read;
}
@Override
public byte[] getDataArray() {
return data;
}
@Override
public int getBytesToRead() {
return toRead;
}
@Override
public void setAmountRead(int rd) {
read.set(rd);
}
}

View File

@@ -0,0 +1,45 @@
package net.montoyo.mcef.client;
import net.montoyo.mcef.api.ISchemeResponseHeaders;
import org.cef.misc.IntRef;
import org.cef.misc.StringRef;
import org.cef.network.CefResponse;
public class SchemeResponseHeaders implements ISchemeResponseHeaders {
private final CefResponse response;
private final IntRef length;
private final StringRef redirURL;
public SchemeResponseHeaders(CefResponse r, IntRef l, StringRef url) {
response = r;
length = l;
redirURL = url;
}
@Override
public void setMimeType(String mt) {
response.setMimeType(mt);
}
@Override
public void setStatus(int status) {
response.setStatus(status);
}
@Override
public void setStatusText(String st) {
response.setStatusText(st);
}
@Override
public void setResponseLength(int len) {
length.set(len);
}
@Override
public void setRedirectURL(String r) {
redirURL.set(r);
}
}

View File

@@ -0,0 +1,69 @@
package net.montoyo.mcef.client;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import net.montoyo.mcef.MCEF;
import org.cef.CefApp;
import org.cef.browser.CefBrowserOsr;
import net.minecraft.client.Minecraft;
import net.montoyo.mcef.utilities.Log;
public class ShutdownThread extends Thread {
private Field running = null;
private Minecraft mc = Minecraft.getMinecraft();
public ShutdownThread() {
super("MCEF-Shutdown");
setDaemon(false);
try {
Field[] fields = Minecraft.class.getDeclaredFields();
for(Field f: fields) {
if(f.getType().equals(Boolean.TYPE)) {
//Log.info("Minecraft.%s: %s", f.getName(), Modifier.toString(f.getModifiers()));
if(f.getModifiers() == Modifier.VOLATILE) {
f.setAccessible(true);
running = f;
Log.info("volatile boolean Minecraft.running => %s", f.getName());
break;
}
}
}
} catch(Throwable t) {
Log.warning("Can't detect Minecraft shutdown:");
t.printStackTrace();
}
}
@Override
public void run() {
if(running == null)
return;
Log.info("Minecraft shutdown detection thread started.");
while(true) {
try {
if(!running.getBoolean(mc))
break;
} catch(Throwable t) {
Log.warning("Can't detect Minecraft shutdown:");
t.printStackTrace();
return;
}
try {
sleep(100);
} catch(Throwable t) {}
}
MCEF.PROXY.onShutdown();
}
}

View File

@@ -0,0 +1,20 @@
package net.montoyo.mcef.client;
import net.montoyo.mcef.api.IStringVisitor;
import org.cef.callback.CefStringVisitor;
public class StringVisitor implements CefStringVisitor {
IStringVisitor isv;
public StringVisitor(IStringVisitor isv) {
this.isv = isv;
}
@Override
public void visit(String string) {
isv.visit(string);
}
}

View File

@@ -0,0 +1,79 @@
package net.montoyo.mcef.client;
import java.awt.Dimension;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import net.montoyo.mcef.utilities.IProgressListener;
import net.montoyo.mcef.utilities.Log;
import net.montoyo.mcef.utilities.Util;
public class UpdateFrame extends JFrame implements IProgressListener {
private JLabel label = new JLabel("Preparing...");
private JProgressBar pbar = new JProgressBar();
public UpdateFrame() {
setTitle("Minecraft ChromiumEF");
setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
setLocationRelativeTo(null);
JPanel lpane = new JPanel();
lpane.setLayout(new BoxLayout(lpane, BoxLayout.LINE_AXIS));
lpane.add(label);
lpane.add(Box.createHorizontalGlue());
Dimension dim = new Dimension(5, 5);
JPanel pane = new JPanel();
pane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
pane.setLayout(new BoxLayout(pane, BoxLayout.PAGE_AXIS));
pane.add(lpane);
pane.add(new Box.Filler(dim, dim, dim));
pane.add(pbar);
setContentPane(pane);
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
SwingUtilities.updateComponentTreeUI(this);
} catch(Throwable t) {
Log.info("Note: couldn't set system look & feel.");
}
setVisible(true);
dim = new Dimension(50, 26);
pbar.setMinimumSize(dim);
pbar.setPreferredSize(dim);
setMinimumSize(new Dimension(540, 90));
pack();
}
@Override
public void onProgressed(double d) {
int val = (int) Util.clamp(d, 0.d, 100.d);
pbar.setValue(val);
}
@Override
public void onTaskChanged(String name) {
Log.info("Task changed to \"%s\"", name);
label.setText(name);
}
@Override
public void onProgressEnd() {
dispose();
}
}

View File

@@ -0,0 +1,114 @@
package net.montoyo.mcef.coremod;
import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin;
import net.minecraft.launchwrapper.IClassTransformer;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.objectweb.asm.*;
import java.util.Map;
@IFMLLoadingPlugin.Name(value = "ShutdownPatcher")
@IFMLLoadingPlugin.TransformerExclusions(value = "net.montoyo.mcef.")
@IFMLLoadingPlugin.SortingIndex(value = 90007531) //It has to run after the searge-name transformation
@IFMLLoadingPlugin.MCVersion("1.12.2")
public class ShutdownPatcher implements IFMLLoadingPlugin, IClassTransformer {
private static boolean PATCH_OK = false;
private static final String OBF_SHUTDOWN_METHOD = "func_71405_e"; //The "searge-obfuscated" name of the Minecraft.shutdownMinecraftApplet() method
public static boolean didPatchSucceed() {
return PATCH_OK;
}
@Override
public String[] getASMTransformerClass() {
return new String[] { "net.montoyo.mcef.coremod.ShutdownPatcher" };
}
@Override
public String getModContainerClass() {
return null;
}
@Override
public String getSetupClass() {
return null;
}
@Override
public void injectData(Map<String, Object> data) {
}
@Override
public String getAccessTransformerClass() {
return null;
}
@Override
public byte[] transform(String name, String deobfName, byte[] cls) {
if(!deobfName.equals("net.minecraft.client.Minecraft"))
return cls;
boolean envObf = !name.equals(deobfName); //If the current environment is obfuscated
log("Now transforming %s, aka %s (obfuscated: %s)", name, deobfName, envObf ? "yes" : "no");
try {
ClassReader cr = new ClassReader(cls);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
McVisitor cv = new McVisitor(cw, envObf);
cr.accept(cv, 0);
return cw.toByteArray();
} catch(Throwable t) {
t.printStackTrace();
log("Failed to setup Minecraft shutdown detector.");
}
return cls; //Abort class transforming
}
private static class ShutdownMCAppletVisitor extends MethodVisitor {
public ShutdownMCAppletVisitor(MethodVisitor mv) {
super(Opcodes.ASM5, mv);
}
@Override
public void visitCode() {
mv.visitCode();
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "net/montoyo/mcef/MCEF", "onMinecraftShutdown", "()V", false);
PATCH_OK = true;
log("Target section has been patched.");
}
}
private static class McVisitor extends ClassVisitor {
private final boolean envObf;
public McVisitor(ClassVisitor cv, boolean obf) {
super(Opcodes.ASM5, cv);
envObf = obf;
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
if(access == Opcodes.ACC_PUBLIC && desc.equals("()V")) { //void shutdownMinecraftApplet()
if((envObf && name.equals(OBF_SHUTDOWN_METHOD)) || name.equals("shutdownMinecraftApplet")) {
log("shutdownMinecraftApplet() method found; transforming...");
return new ShutdownMCAppletVisitor(mv);
}
}
return mv;
}
}
private static void log(String str, Object ... args) {
LogManager.getLogger("MCEF").log(Level.INFO, String.format(str, args));
}
}

View File

@@ -0,0 +1,250 @@
package net.montoyo.mcef.example;
import net.minecraft.client.renderer.GlStateManager;
import net.montoyo.mcef.MCEF;
import net.montoyo.mcef.utilities.Log;
import org.lwjgl.opengl.GL11;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.GuiTextField;
import net.montoyo.mcef.api.API;
import net.montoyo.mcef.api.IBrowser;
import net.montoyo.mcef.api.MCEFApi;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
public class BrowserScreen extends GuiScreen {
IBrowser browser = null;
private GuiButton back = null;
private GuiButton fwd = null;
private GuiButton go = null;
private GuiButton min = null;
private GuiButton vidMode = null;
private GuiTextField url = null;
private String urlToLoad = null;
private static final String YT_REGEX1 = "^https?://(?:www\\.)?youtube\\.com/watch\\?v=([a-zA-Z0-9_\\-]+)$";
private static final String YT_REGEX2 = "^https?://(?:www\\.)?youtu\\.be/([a-zA-Z0-9_\\-]+)$";
private static final String YT_REGEX3 = "^https?://(?:www\\.)?youtube\\.com/embed/([a-zA-Z0-9_\\-]+)(\\?.+)?$";
public BrowserScreen() {
urlToLoad = MCEF.HOME_PAGE;
}
public BrowserScreen(String url) {
urlToLoad = (url == null) ? MCEF.HOME_PAGE : url;
}
@Override
public void initGui() {
ExampleMod.INSTANCE.hudBrowser = null;
if(browser == null) {
//Grab the API and make sure it isn't null.
API api = MCEFApi.getAPI();
if(api == null)
return;
//Create a browser and resize it to fit the screen
browser = api.createBrowser((urlToLoad == null) ? MCEF.HOME_PAGE : urlToLoad, false);
urlToLoad = null;
}
//Resize the browser if window size changed
if(browser != null)
browser.resize(mc.displayWidth, mc.displayHeight - scaleY(20));
//Create GUI
Keyboard.enableRepeatEvents(true);
buttonList.clear();
if(url == null) {
buttonList.add(back = (new GuiButton(0, 0, 0, 20, 20, "<")));
buttonList.add(fwd = (new GuiButton(1, 20, 0, 20, 20, ">")));
buttonList.add(go = (new GuiButton(2, width - 60, 0, 20, 20, "Go")));
buttonList.add(min = (new GuiButton(3, width - 20, 0, 20, 20, "_")));
buttonList.add(vidMode = (new GuiButton(4, width - 40, 0, 20, 20, "YT")));
vidMode.enabled = false;
url = new GuiTextField(5, fontRenderer, 40, 0, width - 100, 20);
url.setMaxStringLength(65535);
//url.setText("mod://mcef/home.html");
} else {
buttonList.add(back);
buttonList.add(fwd);
buttonList.add(go);
buttonList.add(min);
buttonList.add(vidMode);
//Handle resizing
vidMode.x = width - 40;
go.x = width - 60;
min.x = width - 20;
String old = url.getText();
url = new GuiTextField(5, fontRenderer, 40, 0, width - 100, 20);
url.setMaxStringLength(65535);
url.setText(old);
}
}
public int scaleY(int y) {
double sy = ((double) y) / ((double) height) * ((double) mc.displayHeight);
return (int) sy;
}
public void loadURL(String url) {
if(browser == null)
urlToLoad = url;
else
browser.loadURL(url);
}
@Override
public void updateScreen() {
if(urlToLoad != null && browser != null) {
browser.loadURL(urlToLoad);
urlToLoad = null;
}
}
@Override
public void drawScreen(int i1, int i2, float f) {
//Render the URL box first because it overflows a bit
url.drawTextBox();
//Render buttons
super.drawScreen(i1, i2, f);
//Renders the browser if itsn't null
if(browser != null) {
GlStateManager.disableDepth();
GlStateManager.enableTexture2D();
GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f);
browser.draw(.0d, height, width, 20.d); //Don't forget to flip Y axis.
GlStateManager.enableDepth();
}
}
@Override
public void onGuiClosed() {
//Make sure to close the browser when you don't need it anymore.
if(!ExampleMod.INSTANCE.hasBackup() && browser != null)
browser.close();
Keyboard.enableRepeatEvents(false);
}
@Override
public void handleInput() {
while(Keyboard.next()) {
if(Keyboard.getEventKey() == Keyboard.KEY_ESCAPE) {
mc.displayGuiScreen(null);
return;
}
boolean pressed = Keyboard.getEventKeyState();
boolean focused = url.isFocused();
char key = Keyboard.getEventCharacter();
int num = Keyboard.getEventKey();
if(browser != null && !focused) { //Inject events into browser
if(pressed)
browser.injectKeyPressedByKeyCode(num, key, 0);
else
browser.injectKeyReleasedByKeyCode(num, key, 0);
if(key != 0)
browser.injectKeyTyped(key, 0);
}
//Forward event to text box.
if(!pressed && focused && num == Keyboard.KEY_RETURN)
actionPerformed(go);
else if(pressed)
url.textboxKeyTyped(key, num);
}
while(Mouse.next()) {
int btn = Mouse.getEventButton();
boolean pressed = Mouse.getEventButtonState();
int sx = Mouse.getEventX();
int sy = Mouse.getEventY();
int wheel = Mouse.getEventDWheel();
if(browser != null) { //Inject events into browser. TODO: Handle mods & leaving.
int y = mc.displayHeight - sy - scaleY(20); //Don't forget to flip Y axis.
if(wheel != 0)
browser.injectMouseWheel(sx, y, 0, 1, wheel);
else if(btn == -1)
browser.injectMouseMove(sx, y, 0, y < 0);
else
browser.injectMouseButton(sx, y, 0, btn + 1, pressed, 1);
}
if(pressed) { //Forward events to GUI.
int x = sx * width / mc.displayWidth;
int y = height - (sy * height / mc.displayHeight) - 1;
try {
mouseClicked(x, y, btn);
} catch(Throwable t) {
t.printStackTrace();
}
url.mouseClicked(x, y, btn);
}
}
}
//Called by ExampleMod when the current browser's URL changes.
public void onUrlChanged(IBrowser b, String nurl) {
if(b == browser && url != null) {
url.setText(nurl);
vidMode.enabled = nurl.matches(YT_REGEX1) || nurl.matches(YT_REGEX2) || nurl.matches(YT_REGEX3);
}
}
//Handle button clicks
@Override
protected void actionPerformed(GuiButton src) {
if(browser == null)
return;
if(src.id == 0)
browser.goBack();
else if(src.id == 1)
browser.goForward();
else if(src.id == 2) {
String fixedURL = ExampleMod.INSTANCE.getAPI().punycode(url.getText());
browser.loadURL(fixedURL);
} else if(src.id == 3) {
ExampleMod.INSTANCE.setBackup(this);
mc.displayGuiScreen(null);
} else if(src.id == 4) {
String loc = browser.getURL();
String vId = null;
boolean redo = false;
if(loc.matches(YT_REGEX1))
vId = loc.replaceFirst(YT_REGEX1, "$1");
else if(loc.matches(YT_REGEX2))
vId = loc.replaceFirst(YT_REGEX2, "$1");
else if(loc.matches(YT_REGEX3))
redo = true;
if(vId != null || redo) {
ExampleMod.INSTANCE.setBackup(this);
mc.displayGuiScreen(new ScreenCfg(browser, vId));
}
}
}
}

View File

@@ -0,0 +1,167 @@
package net.montoyo.mcef.example;
import net.minecraftforge.common.MinecraftForge;
import net.montoyo.mcef.utilities.Log;
import org.lwjgl.input.Keyboard;
import net.minecraft.client.Minecraft;
import net.minecraft.client.settings.KeyBinding;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.montoyo.mcef.api.API;
import net.montoyo.mcef.api.IBrowser;
import net.montoyo.mcef.api.IDisplayHandler;
import net.montoyo.mcef.api.IJSQueryCallback;
import net.montoyo.mcef.api.IJSQueryHandler;
import net.montoyo.mcef.api.MCEFApi;
import net.minecraftforge.fml.client.registry.ClientRegistry;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import net.minecraftforge.fml.relauncher.Side;
/**
* An example mod that shows you how to use MCEF.
* Assuming that it is client-side only and that onInit() is called on initialization.
* This example shows a simple 2D web browser when pressing F6.
*
* @author montoyo
*
*/
public class ExampleMod implements IDisplayHandler, IJSQueryHandler {
public static ExampleMod INSTANCE;
public ScreenCfg hudBrowser = null;
private KeyBinding key = new KeyBinding("Open Browser", Keyboard.KEY_F10, "key.categories.misc");
private Minecraft mc = Minecraft.getMinecraft();
private BrowserScreen backup = null;
private API api;
public API getAPI() {
return api;
}
public void onPreInit() {
//Grab the API and make sure it isn't null.
api = MCEFApi.getAPI();
if(api == null)
return;
api.registerScheme("mod", ModScheme.class, true, false, false, true, true, false, false);
}
public void onInit() {
INSTANCE = this;
//Register key binding and listen to the FML event bus for ticks.
ClientRegistry.registerKeyBinding(key);
MinecraftForge.EVENT_BUS.register(this);
if(api != null) {
//Register this class to handle onAddressChange and onQuery events
api.registerDisplayHandler(this);
api.registerJSQueryHandler(this);
}
}
public void setBackup(BrowserScreen bu) {
backup = bu;
}
public boolean hasBackup() {
return (backup != null);
}
public void showScreen(String url) {
if(mc.currentScreen instanceof BrowserScreen)
((BrowserScreen) mc.currentScreen).loadURL(url);
else if(hasBackup()) {
mc.displayGuiScreen(backup);
backup.loadURL(url);
backup = null;
} else
mc.displayGuiScreen(new BrowserScreen(url));
}
public IBrowser getBrowser() {
if(mc.currentScreen instanceof BrowserScreen)
return ((BrowserScreen) mc.currentScreen).browser;
else if(backup != null)
return backup.browser;
else
return null;
}
@SubscribeEvent
public void onTick(TickEvent ev) {
if(ev.phase == TickEvent.Phase.START && ev.side == Side.CLIENT && ev.type == TickEvent.Type.CLIENT) {
//Check if our key was pressed
if(key.isPressed() && !(mc.currentScreen instanceof BrowserScreen)) {
//Display the web browser UI.
mc.displayGuiScreen(hasBackup() ? backup : new BrowserScreen());
backup = null;
}
}
}
@Override
public void onAddressChange(IBrowser browser, String url) {
//Called by MCEF if a browser's URL changes. Forward this event to the screen.
if(mc.currentScreen instanceof BrowserScreen)
((BrowserScreen) mc.currentScreen).onUrlChanged(browser, url);
else if(hasBackup())
backup.onUrlChanged(browser, url);
}
@Override
public void onTitleChange(IBrowser browser, String title) {
}
@Override
public void onTooltip(IBrowser browser, String text) {
}
@Override
public void onStatusMessage(IBrowser browser, String value) {
}
@Override
public boolean handleQuery(IBrowser b, long queryId, String query, boolean persistent, IJSQueryCallback cb) {
if(b != null && query.equalsIgnoreCase("username")) {
if(b.getURL().startsWith("mod://")) {
//Only allow MCEF URLs to get the player's username to keep his identity secret
mc.addScheduledTask(() -> {
//Add this to a scheduled task because this is NOT called from the main Minecraft thread...
try {
String name = mc.getSession().getUsername();
cb.success(name);
} catch(Throwable t) {
cb.failure(500, "Internal error.");
Log.warning("Could not get username from JavaScript:");
t.printStackTrace();
}
});
} else
cb.failure(403, "Can't access username from external page");
return true;
}
return false;
}
@Override
public void cancelQuery(IBrowser b, long queryId) {
}
@SubscribeEvent
public void onDrawHUD(RenderGameOverlayEvent.Post ev) {
if(hudBrowser != null)
hudBrowser.drawScreen(0, 0, 0.f);
}
}

View File

@@ -0,0 +1,78 @@
package net.montoyo.mcef.example;
import java.io.IOException;
import java.io.InputStream;
import net.montoyo.mcef.MCEF;
import net.montoyo.mcef.api.*;
import net.montoyo.mcef.utilities.Log;
public class ModScheme implements IScheme {
private String contentType = null;
private InputStream is = null;
@Override
public SchemePreResponse processRequest(String url) {
url = url.substring("mod://".length());
int pos = url.indexOf('/');
if(pos < 0)
return SchemePreResponse.NOT_HANDLED;
String mod = removeSlashes(url.substring(0, pos));
String loc = removeSlashes(url.substring(pos + 1));
if(mod.length() <= 0 || loc.length() <= 0 || mod.charAt(0) == '.' || loc.charAt(0) == '.') {
Log.warning("Invalid URL " + url);
return SchemePreResponse.NOT_HANDLED;
}
is = ModScheme.class.getResourceAsStream("/assets/" + mod.toLowerCase() + "/html/" + loc.toLowerCase());
if(is == null) {
Log.warning("Resource " + url + " NOT found!");
return SchemePreResponse.NOT_HANDLED; //Mhhhhh... 404?
}
contentType = null;
pos = loc.lastIndexOf('.');
if(pos >= 0 && pos < loc.length() - 2)
contentType = MCEF.PROXY.mimeTypeFromExtension(loc.substring(pos + 1));
return SchemePreResponse.HANDLED_CONTINUE;
}
private String removeSlashes(String loc) {
int i = 0;
while(i < loc.length() && loc.charAt(i) == '/')
i++;
return loc.substring(i);
}
@Override
public void getResponseHeaders(ISchemeResponseHeaders rep) {
if(contentType != null)
rep.setMimeType(contentType);
rep.setStatus(200);
rep.setStatusText("OK");
rep.setResponseLength(-1);
}
@Override
public boolean readResponse(ISchemeResponseData data) {
try {
int ret = is.read(data.getDataArray(), 0, data.getBytesToRead());
if(ret <= 0)
is.close();
data.setAmountRead(Math.max(ret, 0));
return ret > 0;
} catch(IOException e) {
e.printStackTrace();
return false;
}
}
}

View File

@@ -0,0 +1,120 @@
package net.montoyo.mcef.example;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.montoyo.mcef.api.IBrowser;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.GL11;
public class ScreenCfg extends GuiScreen {
private IBrowser browser;
private int width = 320;
private int height = 180;
private int x = 10;
private int y = 10;
private int offsetX = 0;
private int offsetY = 0;
private boolean dragging = false;
private boolean resizing = false;
private boolean drawSquare = true;
public ScreenCfg(IBrowser b, String vId) {
browser = b;
if(vId != null)
b.loadURL("https://www.youtube.com/embed/" + vId + "?autoplay=1");
b.resize(width, height);
}
@Override
public void handleInput() {
while(Keyboard.next()) {
if(Keyboard.getEventKey() == Keyboard.KEY_ESCAPE) {
drawSquare = false;
ExampleMod.INSTANCE.hudBrowser = this;
browser.injectMouseMove(-10, -10, 0, true);
mc.displayGuiScreen(null);
return;
}
}
while(Mouse.next()) {
int btn = Mouse.getEventButton();
boolean pressed = Mouse.getEventButtonState();
int sx = Mouse.getEventX();
int sy = mc.displayHeight - Mouse.getEventY();
if(btn == 1 && pressed && sx >= x && sy >= y && sx < x + width && sy < y + height) {
browser.injectMouseMove(sx - x, sy - y, 0, false);
browser.injectMouseButton(sx - x, sy - y, 0, 1, true, 1);
browser.injectMouseButton(sx - x, sy - y, 0, 1, false, 1);
} else if(dragging) {
if(btn == 0 && !pressed)
dragging = false;
else {
x = sx + offsetX;
y = sy + offsetY;
}
} else if(resizing) {
if(btn == 0 && !pressed) {
resizing = false;
browser.resize(width, height);
} else {
int w = sx - x;
int h = sy - y;
if(w >= 32 && h >= 18) {
if(h >= w) {
double dw = ((double) h) * (16.0 / 9.0);
width = (int) dw;
height = h;
} else {
double dh = ((double) w) * (9.0 / 16.0);
width = w;
height = (int) dh;
}
}
}
} else if(pressed && btn == 0 && sx >= x && sy >= y && sx < x + width && sy < y + height) { //In browser rect
dragging = true;
offsetX = x - sx;
offsetY = y - sy;
} else if(pressed && btn == 0 && sx >= x + width && sy >= y + height && sx < x + width + 10 && sy < y + height + 10) //In resize rect
resizing = true;
}
}
@Override
public void drawScreen(int i1, int i2, float f) {
GL11.glDisable(GL11.GL_DEPTH_TEST);
GL11.glEnable(GL11.GL_TEXTURE_2D);
browser.draw(unscaleX(x), unscaleY(height + y), unscaleX(width + x), unscaleY(y));
if(drawSquare) {
Tessellator t = Tessellator.getInstance();
BufferBuilder vb = t.getBuffer();
vb.begin(GL11.GL_LINE_LOOP, DefaultVertexFormats.POSITION_COLOR);
vb.pos(unscaleX(x + width), unscaleY(y + height), 0.0).color(255, 255, 255, 255).endVertex();
vb.pos(unscaleX(x + width + 10), unscaleY(y + height), 0.0).color(255, 255, 255, 255).endVertex();
vb.pos(unscaleX(x + width + 10), unscaleY(y + height + 10), 0.0).color(255, 255, 255, 255).endVertex();
vb.pos(unscaleX(x + width), unscaleY(y + height + 10), 0.0).color(255, 255, 255, 255).endVertex();
t.draw();
}
GL11.glEnable(GL11.GL_DEPTH_TEST);
}
public double unscaleX(int x) {
return ((double) x) / ((double) mc.displayWidth) * ((double) super.width);
}
public double unscaleY(int y) {
return ((double) y) / ((double) mc.displayHeight) * ((double) super.height);
}
}

View File

@@ -0,0 +1,104 @@
package net.montoyo.mcef.remote;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
/**
* An object representing an HTTP(S) mirror to download the resources from.
*
* @author montoyo
* @see {@link net.montoyo.mcef.remote.MirrorManager}
*/
public final class Mirror {
/**
* Whether the mirror is HTTPS or not
*/
public static final int FLAG_SECURE = 1;
/**
* Whether this mirror has been forced by the user in the MCEF configuration file
*/
public static final int FLAG_FORCED = 4;
private final String name;
private final String url;
private final int flags;
/**
* Constructs a Mirror from its name, URL, and flags.
*
* @param name The name of the mirror
* @param url The corresponding URL
* @param flags Its flags
*/
public Mirror(String name, String url, int flags) {
this.name = name;
this.url = url;
this.flags = flags;
}
/**
* @return The name of the mirror
*/
public String getName() {
return name;
}
/**
* @return The URL of the mirror
*/
public String getURL() {
return url;
}
/**
* @return The flags of this mirror
*/
public int getFlags() {
return flags;
}
/**
* @return Whether the secure flag is set
* @see #FLAG_SECURE
*/
public boolean isSecure() {
return (flags & FLAG_SECURE) != 0;
}
/**
* @return Whether this mirror has been forced by the user
* @see #FLAG_FORCED
*/
public boolean isForced() {
return (flags & FLAG_FORCED) != 0;
}
/**
* @return A string informing the user of which mirror was selected
*/
public String getInformationString() {
return isForced() ? ("Mirror location forced by user to: " + url) : ("Selected mirror: " + name);
}
/**
* Opens a connection to the mirror's resource corresponding to the URL.
*
* @param name The URL of the resource, relative to the root of the mirror website.
* @return A connection to this resource, with timeout set up.
* @throws MalformedURLException if the mirror's URL is invalid or if name is invalid.
* @throws IOException if an I/O exception occurs.
*/
public HttpURLConnection getResource(String name) throws MalformedURLException, IOException {
HttpURLConnection ret = (HttpURLConnection) (new URL(url + '/' + name)).openConnection();
ret.setConnectTimeout(30000);
ret.setReadTimeout(15000);
ret.setRequestProperty("User-Agent", "MCEF");
return ret;
}
}

View File

@@ -0,0 +1,92 @@
package net.montoyo.mcef.remote;
import net.montoyo.mcef.MCEF;
import net.montoyo.mcef.utilities.Log;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;
/**
* MirrorManager keeps track of valid & broken mirrors that should be used to download MCEF resources.
* It also makes sure only HTTPS mirrors are used, depending on the user's choices.
*
* @author montoyo
*/
public class MirrorManager {
private static final Mirror[] defaultMirrors = new Mirror[] {
// HTTPS mirrors
new Mirror("oxmc-mcef-mirror", "https://cdn.oxmc.me/internal/mcef", Mirror.FLAG_SECURE),
//new Mirror("ds58-mcef-mirror", "https://ds58-mcef-mirror.ewr1.vultrobjects.com", Mirror.FLAG_SECURE),
};
/**
* The unique instance of the MirrorManager
*/
public static final MirrorManager INSTANCE = new MirrorManager();
private final ArrayList<Mirror> mirrors = new ArrayList<>();
private final Random r = new Random();
private Mirror current;
private MirrorManager() {
markCurrentMirrorAsBroken();
}
private void reset() {
mirrors.clear();
if(MCEF.FORCE_MIRROR != null)
mirrors.add(new Mirror("user-forced", MCEF.FORCE_MIRROR, Mirror.FLAG_FORCED));
else {
ArrayList<Mirror> lst = new ArrayList<>(Arrays.asList(defaultMirrors));
//Begin by adding all HTTPS mirrors in a random fashion
while(!lst.isEmpty()) {
Mirror m = lst.remove(r.nextInt(lst.size()));
if(m.isSecure())
mirrors.add(m);
}
//Then add all non-secure mirrors, if user didn't disable them
if(!MCEF.SECURE_MIRRORS_ONLY) {
lst.addAll(Arrays.asList(defaultMirrors));
while(!lst.isEmpty()) {
Mirror m = lst.remove(r.nextInt(lst.size()));
if(!m.isSecure())
mirrors.add(m);
}
}
}
}
/**
* @return The active mirror to be used
*/
public Mirror getCurrent() {
return current;
}
/**
* Marks the active mirror as broken and chooses another one
*
* @return false if all mirrors were tested and the list was reset
*/
public boolean markCurrentMirrorAsBroken() {
boolean ret = true;
if(mirrors.isEmpty()) {
reset();
ret = false;
}
current = mirrors.remove(0);
Log.info(current.getInformationString());
return ret;
}
}

View File

@@ -0,0 +1,354 @@
package net.montoyo.mcef.remote;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.commons.io.FileUtils;
import net.minecraft.client.Minecraft;
import net.montoyo.mcef.setup.FileListing;
import org.cef.OS;
import net.montoyo.mcef.MCEF;
import net.montoyo.mcef.client.ClientProxy;
import net.montoyo.mcef.utilities.IProgressListener;
import net.montoyo.mcef.utilities.Log;
import net.montoyo.mcef.utilities.Platform;
import net.montoyo.mcef.utilities.Util;
import net.montoyo.mcef.utilities.Version;
import net.montoyo.mcef.utilities.ForgeVersionParser;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonIOException;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import static net.montoyo.mcef.client.ClientProxy.JCEF_ROOT;
/**
* A class for updating and parsing the remote configuration file.
* @author montoyo
*
*/
public class RemoteConfig {
private static String jcefCommit;
private boolean useLegacyJcef = false;
private String version = null;
private static final String PLATFORM = Platform.getPlatform();
private static final String PLATFORMFULL = Platform.getPlatformFull();
public RemoteConfig() {
}
/**
* Parses the MCEF configuration file.
*
* @param f The configuration file.
* @return The parsed configuration file.
*/
private JsonObject readConfig(File f) {
try {
return (new JsonParser()).parse(new FileReader(f)).getAsJsonObject();
} catch(JsonIOException e) {
Log.error("IOException while reading remote config.");
e.printStackTrace();
return null;
} catch(FileNotFoundException e) {
Log.error("Couldn't find remote config.");
e.printStackTrace();
return null;
} catch(Exception e) {
Log.error("Syntax error in remote config.");
e.printStackTrace();
return null;
}
}
/**
* Updates the MCEF configuration file and parses it.
* @return The parsed configuration file.
*/
private JsonObject readConfig() {
File newCfg = new File(JCEF_ROOT, "mcef.new");
File cfgFle = new File(JCEF_ROOT, "mcef.json");
// Get the mirror URL
Mirror m = MirrorManager.INSTANCE.getCurrent();
String mirrorHost = m.getURL();
String cfgUrl = mirrorHost + "/mcef.json";
boolean ok = Util.downloadFileSafe(cfgUrl, newCfg, null);
if(ok) {
Util.delete(cfgFle);
if(newCfg.renameTo(cfgFle))
return readConfig(cfgFle);
else {
Log.warning("Couldn't rename mcef.new to mcef.json.");
return readConfig(newCfg);
}
} else {
Log.warning("Couldn't read remote config. Using local configuration file.");
return readConfig(cfgFle);
}
}
/**
* Updates the MCEF configuration file and parses it.
* Fills the resources, extract and version fields from it.
*/
public void load() {
JsonObject json = readConfig();
if(json == null) {
Log.error("Could NOT read either remote and local configuration files. Entering virtual mode.");
ClientProxy.VIRTUAL = true;
return;
}
Log.info("Detected platform: %s", PLATFORM);
// Check if the config file contains the MCEF version
JsonElement ver = json.get(MCEF.VERSION);
if(ver == null || !ver.isJsonObject()) {
Log.error("Config file does NOT contain this MCEF jar's version (wtf??). Entering virtual mode.");
ClientProxy.VIRTUAL = true;
return;
}
// Get the version data
JsonObject vData = ver.getAsJsonObject();
// Create an instance of ForgeVersionParser
ForgeVersionParser forgeVersionParser = new ForgeVersionParser();
// Get the Minecraft version
String mcVersion = forgeVersionParser.parse(Minecraft.getMinecraft().getVersion());
// Otherwise we fallback to hardcoded version
if (mcVersion == null || mcVersion.isEmpty() || mcVersion.contains("forge")) {
mcVersion = "1.12.2";
}
// Check for old_versions first
JsonElement oldVersions = vData.get("old_versions");
if (oldVersions != null && oldVersions.isJsonObject()) {
JsonObject oldVersionsObj = oldVersions.getAsJsonObject();
// Check if the current Minecraft version exists in old_versions
if (oldVersionsObj.has(mcVersion)) {
JsonElement versionData = oldVersionsObj.get(mcVersion);
// Parse the options for the current Minecraft version
if (versionData != null && versionData.isJsonObject()) {
JsonObject versionDataObj = versionData.getAsJsonObject();
vData = versionDataObj;
// Read the "use_legacy_jcef" option, which uses the old JCEF version
JsonElement useLegacyJcefOpt = versionDataObj.get("use_legacy_jcef");
if (useLegacyJcefOpt != null && useLegacyJcefOpt.isJsonPrimitive()) {
this.useLegacyJcef = useLegacyJcefOpt.getAsBoolean();
Log.info("Use Legacy JCEF: %s", useLegacyJcef);
// get the jcef_version object
JsonElement jcefVersion = versionDataObj.get("jcef_version");
if (jcefVersion != null && jcefVersion.isJsonPrimitive()) {
String commitHash = jcefVersion.getAsString();
//Log.info("JCEF Commit: %s", commitHash);
jcefCommit = commitHash;
} else {
Log.error("Config file is missing \"jcef_version\" object. Entering virtual mode.");
ClientProxy.VIRTUAL = true;
return;
}
}
}
} else {
Log.info("Minecraft version not found in old_versions. Using the latest version.");
}
}
// if the "use_legacy_jcef" option is not true we check for git_commit
if (!useLegacyJcef) {
// Check for the git_commit object
JsonElement gitCommit = vData.get("git_commit");
if (gitCommit != null && gitCommit.isJsonPrimitive()) {
String commitHash = gitCommit.getAsString();
//Log.info("Git Commit: %s", commitHash);
jcefCommit = commitHash;
} else {
Log.error("Config file is missing \"git_commit\" object. Entering virtual mode.");
ClientProxy.VIRTUAL = true;
return;
}
}
// Check for the supported_platforms object
JsonElement cat = vData.get("supported_platforms");
if(cat == null || !cat.isJsonArray()) {
Log.error("Config file is missing \"supported_platforms\" object. Entering virtual mode.");
ClientProxy.VIRTUAL = true;
return;
}
// Check if the current platform is supported
JsonArray catArr = cat.getAsJsonArray();
boolean platformSupported = false;
for (JsonElement element : catArr) {
if (element.getAsString().equals(PLATFORM)) {
platformSupported = true;
break;
}
}
if (!platformSupported) {
Log.error("Your platform isn't supported by MCEF yet. Entering virtual mode.");
ClientProxy.VIRTUAL = true;
return;
}
// Check for the latestVersions object
JsonElement mcVersions = json.get("latestVersions");
if(mcVersions != null && mcVersions.isJsonObject()) {
JsonElement cVer = mcVersions.getAsJsonObject().get(mcVersion);
if(cVer != null && cVer.isJsonPrimitive())
version = cVer.getAsString();
}
}
/**
* Detects missing files, download them, and extracts them.
*
* @param ipl The progress listener.
* @return true if the operation was successful.
*/
public boolean downloadMCEF(IProgressListener ipl) {
Mirror m = MirrorManager.INSTANCE.getCurrent();
String mirrorHost = m.getURL();
String baseURL;
if (this.useLegacyJcef) {
baseURL = mirrorHost+"/legacy/"+jcefCommit+"/";
} else {
baseURL = mirrorHost+"/java-cef-builds/"+jcefCommit+"/";
}
String targz = PLATFORMFULL+".tar.gz";
//https://mcef-download.cinemamod.com/java-cef-builds/eaeb3d4370aa3526ee237ad1981ad59af3de4dd1/windows_amd64.tar.gz
// or
//https://mcef-download.cinemamod.com/legacy/1.33/windows_amd64.tar.gz
String JAVA_CEF_DOWNLOAD_URL = baseURL+targz;
String JAVA_CEF_CHECKSUM_DOWNLOAD_URL = baseURL+targz+".sha256";
boolean shouldDownload = false;
try {
ipl = Util.secure(ipl);
// Prepare the file paths and URLs
File mcefLibrariesPath = new File(JCEF_ROOT);
File cefRuntimeFolder = new File(mcefLibrariesPath, "cef_runtime");
String downloadUrl = JAVA_CEF_DOWNLOAD_URL;
String checksumUrl = JAVA_CEF_CHECKSUM_DOWNLOAD_URL;
File tarGzFile = new File(mcefLibrariesPath, PLATFORMFULL + ".tar.gz");
File checksumFile = new File(mcefLibrariesPath, PLATFORMFULL + ".tar.gz.sha256");
File checksumTempFile = new File(mcefLibrariesPath, PLATFORMFULL + ".tar.gz.sha256.temp");
// Log the file paths
//Log.info("JCEF libraries path: %s", mcefLibrariesPath.getAbsolutePath());
//Log.info("JCEF tar.gz file: %s", tarGzFile.getAbsolutePath());
//Log.info("JCEF checksum file: %s", checksumFile.getAbsolutePath());
//Log.info("JCEF checksum temp file: %s", checksumTempFile.getAbsolutePath());
// Download the checksum file
Util.downloadFile(checksumUrl, checksumTempFile, ipl);
// Step 3: Verify checksum
if (checksumFile.exists()) {
boolean sameContent = FileUtils.contentEquals(checksumFile, checksumTempFile);
if (sameContent) {
// Before we delete the temp file, we need to check if the JCEF build is already downloaded
if (cefRuntimeFolder.exists()) {
Log.info("JCEF build already downloaded, Skipping download.");
checksumTempFile.delete();
shouldDownload = false;
} else {
Log.info("JCEF build not downloaded yet and or missing, Downloading JCEF build.");
checksumTempFile.renameTo(checksumFile);
shouldDownload = true;
}
} else {
Log.warning("Checksum file doesn't match. Redownloading JCEF build.");
checksumTempFile.renameTo(checksumFile);
shouldDownload = true;
}
} else {
// If the checksum file doesn't exist, then we need to download the JCEF build
shouldDownload = true;
checksumTempFile.renameTo(checksumFile);
}
// Step 4: Download the JCEF build if necessary
if (shouldDownload && !MCEF.SKIP_UPDATES) {
// Download the JCEF build
Util.downloadFile(downloadUrl, tarGzFile, ipl);
// Extract the tar.gz file
Util.extractTarGz(tarGzFile, mcefLibrariesPath, ipl);
// Define extracted folder (platform_arch) and target folder (cef_runtime)
File extractedFolder = new File(mcefLibrariesPath, PLATFORMFULL);
// Ensure the old cef_runtime folder is removed before renaming
if (cefRuntimeFolder.exists()) {
FileUtils.deleteDirectory(cefRuntimeFolder);
}
// Rename extracted folder to cef_runtime
if (extractedFolder.exists()) {
boolean renamed = extractedFolder.renameTo(cefRuntimeFolder);
if (!renamed) {
Log.warning("Failed to rename extracted folder to cef_runtime");
}
} else {
Log.warning("Extracted folder does not exist, rename skipped.");
}
// Delete the tar.gz file
if (tarGzFile.exists()) {
tarGzFile.delete();
}
}
return true;
} catch (IOException e) {
Log.error("Failed to download or extract JCEF build: " + e.getMessage());
e.printStackTrace();
return false;
}
}
/**
* Returns an info string if an MCEF update is available.
* @return an info string if a newer version exists, null otherwise.
*/
public String getUpdateString() {
if(version == null)
return null;
Version cur = new Version(MCEF.VERSION);
Version cfg = new Version(version);
if(cfg.isBiggerThan(cur))
return "New MCEF version available. Current: " + cur + ", latest: " + cfg + '.';
return null;
}
}

View File

@@ -0,0 +1,348 @@
package net.montoyo.mcef.setup;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class CfgParser {
private static abstract class Line {
public abstract void write(BufferedWriter bw) throws IOException;
public abstract void read(String content, Matcher m);
}
private static class CommentLine extends Line {
private String data;
public CommentLine(String d) {
data = d;
}
@Override
public void write(BufferedWriter bw) throws IOException {
bw.write(data + "\n");
}
@Override
public void read(String content, Matcher m) {
data = content;
}
}
private static class BeginCategoryLine extends Line {
public static final String REGEX = "^(\\s*)([a-z]+)(\\s+)\\{(\\s*)$";
private String prefix;
private String category;
private String inBetween;
private String suffix;
public BeginCategoryLine() {
}
public BeginCategoryLine(String name) {
prefix = "";
category = name;
inBetween = " ";
suffix = "";
}
@Override
public void write(BufferedWriter bw) throws IOException {
bw.write(prefix);
bw.write(category);
bw.write(inBetween);
bw.write("{");
bw.write(suffix + "\n");
}
@Override
public void read(String content, Matcher m) {
prefix = m.group(1);
category = m.group(2);
inBetween = m.group(3);
suffix = m.group(4);
}
public String getCategoryName() {
return category;
}
}
private static class EndCategoryLine extends Line {
public static final String REGEX = "^(\\s*)\\}(\\s*)$";
private String prefix;
private String suffix;
public EndCategoryLine() {
prefix = "";
suffix = "";
}
@Override
public void write(BufferedWriter bw) throws IOException {
bw.write(prefix);
bw.write("}");
bw.write(suffix + "\n");
}
@Override
public void read(String content, Matcher m) {
prefix = m.group(1);
suffix = m.group(2);
}
}
private static class PropertyLine extends Line {
public static final String REGEX = "^(\\s*)([A-Z])\\:([A-Za-z]+)=(.*)$";
private String prefix;
private char type;
private String key;
private String value;
public PropertyLine() {
}
public PropertyLine(char t, String key, String val) {
prefix = " ";
type = t;
this.key = key;
value = val;
}
@Override
public void write(BufferedWriter bw) throws IOException {
bw.write(prefix);
bw.write(type + ":" + key);
bw.write("=" + value + "\n");
}
@Override
public void read(String content, Matcher m) {
prefix = m.group(1);
type = m.group(2).charAt(0);
key = m.group(3);
value = m.group(4);
}
public String getKey() {
return key;
}
public String getValue() {
return value;
}
public boolean getBooleanValue(boolean def) {
String data = value.trim().toLowerCase();
if(data.equals("false"))
return false;
else if(data.equals("true"))
return true;
else {
value = def ? "true" : "false";
return def;
}
}
public void setValue(String v) {
value = v;
}
}
private enum LineType {
CATEGORY_BEGIN(BeginCategoryLine.REGEX, BeginCategoryLine.class),
CATEGORY_END(EndCategoryLine.REGEX, EndCategoryLine.class),
PROPERTY(PropertyLine.REGEX, PropertyLine.class);
private final Pattern pattern;
private final Class<? extends Line> cls;
LineType(String regex, Class<? extends Line> cls) {
pattern = Pattern.compile(regex);
this.cls = cls;
}
public static Line parseLine(String l) {
for(LineType lt : values()) {
Matcher m = lt.pattern.matcher(l);
if(m.matches()) {
try {
Line ret = lt.cls.newInstance();
ret.read(l, m);
return ret;
} catch(Throwable t) {
System.err.println("Could not instantiate line class \"" + lt.cls.getCanonicalName() + "\":");
t.printStackTrace();
return null;
}
}
}
return null;
}
}
private ArrayList<Line> lines = new ArrayList<Line>();
private HashMap<String, HashMap<String, PropertyLine>> data = new HashMap<String, HashMap<String, PropertyLine>>();
private File location;
public CfgParser(File loc) {
location = loc;
}
public boolean load() {
try {
unsafeRead();
return true;
} catch(Throwable t) {
System.err.println("Could not read config file \"" + location.getAbsolutePath() + "\":");
t.printStackTrace();
return false;
}
}
private void unsafeRead() throws Throwable {
lines.clear();
data.clear();
BufferedReader br = new BufferedReader(new FileReader(location));
String line;
String currentCategory = null;
int lineCnt = 0;
while((line = br.readLine()) != null) {
String trimmed = line.trim();
Line l;
if(trimmed.isEmpty() || trimmed.charAt(0) == '#')
l = new CommentLine(line);
else
l = LineType.parseLine(line);
if(l == null)
throw new RuntimeException("Could not parse line #" + (lineCnt + 1) + ".");
if(l instanceof BeginCategoryLine) {
if(currentCategory == null) {
currentCategory = ((BeginCategoryLine) l).getCategoryName();
data.put(currentCategory, new HashMap<String, PropertyLine>());
} else
throw new RuntimeException("At line #" + (lineCnt + 1) + ": Forgot to close brackets.");
} else if(l instanceof EndCategoryLine) {
if(currentCategory == null)
throw new RuntimeException("At line #" + (lineCnt + 1) + ": Closing non-opened bracket.");
else
currentCategory = null;
} else if(l instanceof PropertyLine) {
if(currentCategory == null)
throw new RuntimeException("At line #" + (lineCnt + 1) + ": Setting property outside brackets.");
else
data.get(currentCategory).put(((PropertyLine) l).getKey(), (PropertyLine) l);
}
lines.add(l);
lineCnt++;
}
SetupUtil.silentClose(br);
}
public boolean save() {
try {
unsafeWrite();
return true;
} catch(Throwable t) {
System.err.println("Could not write config file \"" + location.getAbsolutePath() + "\":");
t.printStackTrace();
return false;
}
}
private void unsafeWrite() throws Throwable {
BufferedWriter bw = new BufferedWriter(new FileWriter(location));
for(Line l : lines)
l.write(bw);
SetupUtil.silentClose(bw);
}
private int findCategoryBeginning(String cat) {
for(int i = 0; i < lines.size(); i++) {
Line l = lines.get(i);
if(l instanceof BeginCategoryLine && ((BeginCategoryLine) l).getCategoryName().equals(cat))
return i;
}
return -1;
}
private PropertyLine getValue(char type, String category, String key, String def) {
HashMap<String, PropertyLine> subdata;
if(data.containsKey(category)) {
subdata = data.get(category);
if(subdata.containsKey(key))
return subdata.get(key);
else {
int pos = findCategoryBeginning(category);
if(pos < 0)
throw new RuntimeException("Could not find beginning for category \"" + category + "\"! This should NOT happen!");
PropertyLine pl = new PropertyLine(type, key, def);
lines.add(pos + 1, pl);
subdata.put(key, pl);
return pl;
}
} else {
BeginCategoryLine bcl = new BeginCategoryLine(category);
PropertyLine pl = new PropertyLine(type, key, def);
EndCategoryLine ecl = new EndCategoryLine();
lines.add(bcl);
lines.add(pl);
lines.add(ecl);
subdata = new HashMap<String, PropertyLine>();
subdata.put(key, pl);
data.put(category, subdata);
return pl;
}
}
public String getStringValue(String category, String key, String def) {
return getValue('S', category, key, def).getValue();
}
public boolean getBooleanValue(String category, String key, boolean def) {
return getValue('B', category, key, def ? "true" : "false").getBooleanValue(def);
}
public void setStringValue(String category, String key, String val) {
getValue('S', category, key, val).setValue(val);
}
public void setBooleanValue(String category, String key, boolean val) {
String data = val ? "true" : "false";
getValue('B', category, key, data).setValue(data);
}
}

View File

@@ -0,0 +1,223 @@
package net.montoyo.mcef.setup;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.border.TitledBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.File;
public class ConfigForm implements ActionListener, WindowListener {
private CfgParser config;
private JFrame parent;
private JFrame frame;
private JPanel contentPane;
//Main
private JPanel cMainPane;
private JTextField cMainForcedMirror;
private JCheckBox cMainSkipUpdates;
private JCheckBox cMainForgeSplash;
private JCheckBox cMainWarnUpdates;
//Browser
private JPanel cBrowserPane;
private JCheckBox cBrowserEnable;
private JTextField cBrowserHome;
//Buttons
private JPanel btnPane;
private JButton btnOk;
private JButton btnBack;
private JButton btnApply;
public ConfigForm(JFrame p, File cfgFile) {
GridBagConstraints c;
config = new CfgParser(cfgFile);
parent = p;
config.load();
//Frame and content pane
frame = new JFrame("MCEF Setup - Configuration");
frame.setMinimumSize(new Dimension(500, 1));
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(this);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(3, 3, 3, 3));
contentPane.setLayout(new GridBagLayout());
//Main
cMainPane = new JPanel();
cMainPane.setBorder(new TitledBorder("Main"));
cMainPane.setLayout(new GridBagLayout());
cMainForcedMirror = new JTextField();
cMainSkipUpdates = new JCheckBox();
cMainForgeSplash = new JCheckBox();
cMainWarnUpdates = new JCheckBox();
addFormComponent(cMainPane, 0, "Forced mirror", cMainForcedMirror);
addFormComponent(cMainPane, 1, "Skip updates", cMainSkipUpdates);
addFormComponent(cMainPane, 2, "Use forge splash", cMainForgeSplash);
addFormComponent(cMainPane, 3, "Warn updates", cMainWarnUpdates);
cMainForcedMirror.setText(config.getStringValue("main", "forcedMirror", ""));
cMainSkipUpdates.setSelected(config.getBooleanValue("main", "skipUpdates", false));
cMainForgeSplash.setSelected(config.getBooleanValue("main", "useForgeSplash", true));
cMainWarnUpdates.setSelected(config.getBooleanValue("main", "warnUpdates", true));
c = new GridBagConstraints();
c.gridy = 4;
c.fill = GridBagConstraints.VERTICAL;
c.weighty = 1.0;
cMainPane.add(Box.createVerticalGlue(), c);
c = new GridBagConstraints();
c.fill = GridBagConstraints.BOTH;
c.weightx = 1.0;
c.weighty = 0.5;
contentPane.add(cMainPane, c);
//Browser
cBrowserPane = new JPanel();
cBrowserPane.setBorder(new TitledBorder("Browser"));
cBrowserPane.setLayout(new GridBagLayout());
cBrowserEnable = new JCheckBox();
cBrowserHome = new JTextField();
addFormComponent(cBrowserPane, 0, "Enable", cBrowserEnable);
addFormComponent(cBrowserPane, 1, "Home page", cBrowserHome);
cBrowserEnable.setSelected(config.getBooleanValue("examplebrowser", "enable", true));
cBrowserHome.setText(config.getStringValue("examplebrowser", "home", "mod://mcef/home.html"));
c = new GridBagConstraints();
c.gridy = 2;
c.fill = GridBagConstraints.VERTICAL;
c.weighty = 1.0;
cBrowserPane.add(Box.createVerticalGlue(), c);
c = new GridBagConstraints();
c.gridy = 1;
c.fill = GridBagConstraints.BOTH;
c.weightx = 1.0;
c.weighty = 0.5;
contentPane.add(cBrowserPane, c);
//Buttons
btnPane = new JPanel();
btnPane.setLayout(new GridBagLayout());
btnOk = new JButton("Ok");
btnBack = new JButton("Back");
btnApply = new JButton("Apply");
btnOk.addActionListener(this);
btnBack.addActionListener(this);
btnApply.addActionListener(this);
c = new GridBagConstraints();
c.fill = GridBagConstraints.HORIZONTAL;
c.weightx = 1.0;
btnPane.add(Box.createHorizontalGlue(), c);
addFormButton(1, btnOk);
addFormButton(2, btnBack);
addFormButton(3, btnApply);
c = new GridBagConstraints();
c.gridy = 2;
c.fill = GridBagConstraints.HORIZONTAL;
c.weightx = 1.0;
contentPane.add(btnPane, c);
//Display
frame.setContentPane(contentPane);
frame.pack();
parent.setVisible(false);
frame.setVisible(true);
}
private void addFormComponent(JPanel pane, int line, String label, Component comp) {
GridBagConstraints c = new GridBagConstraints();
c.insets = new Insets(0, 3, 3, 3);
c.fill = GridBagConstraints.HORIZONTAL;
c.gridy = line;
pane.add(new JLabel(label), c);
c = new GridBagConstraints();
c.insets = new Insets(0, 0, 3, 3);
c.gridx = 1;
c.gridy = line;
c.fill = GridBagConstraints.HORIZONTAL;
c.weightx = 1.0;
pane.add(comp, c);
}
private void addFormButton(int x, JButton btn) {
GridBagConstraints c = new GridBagConstraints();
c.insets = new Insets(3, 0, 3, 3);
c.gridx = x;
btnPane.add(btn, c);
}
private void saveChanges() {
config.setStringValue("main", "forcedMirror", cMainForcedMirror.getText());
config.setBooleanValue("main", "skipUpdates", cMainSkipUpdates.isSelected());
config.setBooleanValue("main", "useForgeSplash", cMainForgeSplash.isSelected());
config.setBooleanValue("main", "warnUpdates", cMainWarnUpdates.isSelected());
config.setBooleanValue("examplebrowser", "enable", cBrowserEnable.isSelected());
config.setStringValue("examplebrowser", "home", cBrowserHome.getText());
if(!config.save())
JOptionPane.showMessageDialog(frame, "Could not save configuration file.\nMake sure you have the permissions to write in the config folder.", "Error", JOptionPane.ERROR_MESSAGE);
}
@Override
public void actionPerformed(ActionEvent e) {
if(e.getSource() == btnBack)
windowClosing(null);
else if(e.getSource() == btnApply)
saveChanges();
else if(e.getSource() == btnOk) {
saveChanges();
windowClosing(null);
}
}
@Override
public void windowOpened(WindowEvent e) {
}
@Override
public void windowClosing(WindowEvent e) {
parent.setVisible(true);
frame.dispose();
}
@Override
public void windowClosed(WindowEvent e) {
}
@Override
public void windowIconified(WindowEvent e) {
}
@Override
public void windowDeiconified(WindowEvent e) {
}
@Override
public void windowActivated(WindowEvent e) {
}
@Override
public void windowDeactivated(WindowEvent e) {
}
}

View File

@@ -0,0 +1,17 @@
package net.montoyo.mcef.setup;
import java.util.Comparator;
final class DefaultComparator implements Comparator<String> {
@Override
public int compare(String a, String b) {
return a.compareTo(b);
}
@Override
public boolean equals(Object o) {
return o != null && o instanceof DefaultComparator;
}
}

View File

@@ -0,0 +1,47 @@
package net.montoyo.mcef.setup;
import java.io.File;
/**
* This class will be extracted in another temporary JAR in order to self-destroy the MCEF jar after exit.
* It has to be standalone and must remain as light as possible for the user's convenience.
*/
public class Deleter {
//Sorry about the copy/paste, but this class needs to be standalone!
private static boolean tryDelete(File f) {
if(!f.exists())
return true;
if(f.delete())
return true;
else {
File dst = new File(f.getParentFile(), f.getName() + "_" + System.currentTimeMillis() % ((int) (Math.random() * 10000)) + ".tmp");
if(f.renameTo(dst)) {
if(!dst.delete())
dst.deleteOnExit();
return true;
} else
return false;
}
}
public static void main(String[] args) {
File f = new File(args[0]);
String lowerName = f.getName().toLowerCase();
if(lowerName.startsWith("mcef") && lowerName.endsWith(".jar")) {
for(int i = 0; i < 30; i++) {
if(tryDelete(f))
return;
try {
Thread.sleep(3000);
} catch(Throwable t) {}
}
}
}
}

View File

@@ -0,0 +1,120 @@
package net.montoyo.mcef.setup;
import java.io.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class FileListing {
private ArrayList<String> fileNames = new ArrayList<String>();
private File location;
public FileListing(File dir) {
location = new File(dir, "mcefFiles.lst");
if(location.exists())
load();
}
public boolean load() {
try {
unsafeLoad();
return true;
} catch(Throwable t) {
System.err.println("Coud not read file listing:");
t.printStackTrace();
return false;
}
}
private void unsafeLoad() throws Throwable {
BufferedReader br = new BufferedReader(new FileReader(location));
String line;
fileNames.clear();
while((line = br.readLine()) != null) {
line = line.trim();
if(line.length() > 0 && line.charAt(0) != '#' && line.charAt(0) != '.' && line.charAt(0) != '/' && line.charAt(0) != '\\')
fileNames.add(line);
}
SetupUtil.silentClose(br);
}
public boolean save() {
try {
unsafeSave();
return true;
} catch(Throwable t) {
System.err.println("Coud not write file listing:");
t.printStackTrace();
return false;
}
}
private void unsafeSave() throws Throwable {
if(location.exists())
SetupUtil.tryDelete(location);
BufferedWriter bw = new BufferedWriter(new FileWriter(location));
bw.write("# DO NOT EDIT THIS FILE. IT HAS BEEN AUTOMATICALLY GENERATED.\n");
bw.write("# This file contains the list of files installed by MCEF.\n");
bw.write("# If you remove MCEF, they are no longer needed and you can safely remove them,\n");
bw.write("# or you can let the uninstaller do it for you. Just run the MCEF mod jar using Java.\n\n");
for(String f : fileNames)
bw.write(f + "\n");
SetupUtil.silentClose(bw);
}
public void addFile(String f) {
if(!fileNames.contains(f))
fileNames.add(f);
}
public boolean addZip(String fname) {
try {
addZipUnsafe(fname);
return true;
} catch(Throwable t) {
System.err.println("Coud not list file in ZIP archive \"" + fname + "\":");
t.printStackTrace();
return false;
}
}
private void addZipUnsafe(String fname) throws Throwable {
ArrayList<String> files = new ArrayList<String>();
ZipInputStream zis = new ZipInputStream(new FileInputStream(fname));
ZipEntry ze;
while((ze = zis.getNextEntry()) != null) {
String name = ze.getName();
if(ze.isDirectory() && (name.endsWith("/") || name.endsWith("\\")))
files.add(name.substring(0, name.length() - 1));
else
files.add(name);
}
SetupUtil.silentClose(zis);
files.sort(new SlashComparator(new DefaultComparator()));
for(String t: files)
addFile(t); //Use addFile instead of fileNames.addAll() to remove duplicates
}
public Iterator<String> iterator() {
return fileNames.iterator();
}
public boolean selfDestruct() {
return SetupUtil.tryDelete(location);
}
}

View File

@@ -0,0 +1,200 @@
package net.montoyo.mcef.setup;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.File;
public class McLocationPrompt implements ActionListener, WindowListener {
private JFrame parent;
private JFrame frame;
private GridLayout layout;
private JPanel mainPane;
private JTextField locationField;
private JButton btnLocate;
private JButton btnBack;
private JButton btnOk;
private String action;
public McLocationPrompt(JFrame p, String action) {
parent = p;
this.action = action;
//Setup
frame = new JFrame("MCEF Setup - Minecraft location");
frame.setMinimumSize(new Dimension(500, 1));
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(this);
mainPane = new JPanel();
layout = new GridLayout(3, 1, 3, 3);
mainPane.setBorder(new EmptyBorder(3, 3, 3, 3));
mainPane.setLayout(layout);
//First line: label
mainPane.add(new JLabel("Please tell us where Minecraft is installed:"));
//Second line: field location, locate button
JPanel line = new JPanel(new GridBagLayout());
line.setMinimumSize(new Dimension(1, 250));
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.BOTH;
c.weightx = 1.0;
c.weighty = 1.0;
c.insets = new Insets(0, 0, 0, 3);
line.add(locationField = new JTextField(), c);
c = new GridBagConstraints();
c.fill = GridBagConstraints.VERTICAL;
c.gridx = 1;
c.weighty = 1.0;
line.add(btnLocate = new JButton("..."), c);
btnLocate.addActionListener(this);
mainPane.add(line);
//Third line: gap, back, ok
line = new JPanel(new GridBagLayout());
c = new GridBagConstraints();
c.fill = GridBagConstraints.HORIZONTAL;
c.weightx = 1.0;
line.add(Box.createHorizontalGlue(), c);
c = new GridBagConstraints();
c.gridx = 1;
c.insets = new Insets(0, 0, 0, 3);
line.add(btnBack = new JButton("Back"), c);
btnBack.addActionListener(this);
c = new GridBagConstraints();
c.gridx = 2;
line.add(btnOk = new JButton("Ok"), c);
btnOk.addActionListener(this);
mainPane.add(line);
//Fill location field
try {
locationField.setText(autoLocateMinecraft());
} catch(Throwable t) {
System.err.println("Note: could not locate Minecraft:");
t.printStackTrace();
}
//Display
frame.setContentPane(mainPane);
frame.pack();
parent.setVisible(false);
frame.setVisible(true);
}
private String autoLocateMinecraft() {
File cDir = (new File(".")).getAbsoluteFile();
if(cDir.getName().equals("mods")) {
File pFile = cDir.getParentFile();
File saves = new File(pFile, "saves");
File rpacks = new File(pFile, "resourcepacks");
if(saves.exists() && saves.isDirectory() && rpacks.exists() && rpacks.isDirectory())
return pFile.getAbsolutePath();
}
File root = new File(System.getProperty("user.home", "."));
String os = System.getProperty("os.name").toLowerCase();
if(os.contains("win"))
root = new File(System.getenv("APPDATA"));
else if(os.contains("mac"))
root = new File(new File(root, "Library"), "Application Support");
root = new File(root, ".minecraft");
return root.exists() ? root.getAbsolutePath() : "";
}
@Override
public void actionPerformed(ActionEvent e) {
if(e.getSource() == btnLocate) {
JFileChooser fc = new JFileChooser();
fc.setDialogTitle("Where's Minecraft?");
fc.setCurrentDirectory(new File(locationField.getText()));
fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
if(fc.showOpenDialog(frame) == JFileChooser.APPROVE_OPTION)
locationField.setText(fc.getSelectedFile().getAbsolutePath());
} else if(e.getSource() == btnBack) {
parent.setVisible(true);
frame.dispose();
} else if(e.getSource() == btnOk) {
File loc = new File(locationField.getText());
if(!loc.exists() || !loc.isDirectory()) {
JOptionPane.showMessageDialog(frame, "The selected directory does not exist.", "Error", JOptionPane.ERROR_MESSAGE);
return;
}
File saves = new File(loc, "saves");
File rpacks = new File(loc, "resourcepacks");
if(!saves.exists() || !saves.isDirectory() || !rpacks.exists() || !rpacks.isDirectory()) {
if(JOptionPane.showConfirmDialog(frame, "The selected directory does not look like a valid Minecraft setup...\nWould you like to continue?", "Hmmm...", JOptionPane.YES_NO_OPTION) == JOptionPane.NO_OPTION)
return;
}
if(action.equals("configure")) {
File configDir = new File(loc, "config");
if(!configDir.exists())
configDir.mkdirs();
new ConfigForm(parent, new File(configDir, "MCEF.cfg"));
frame.dispose();
return;
}
try {
if(((Boolean) Processes.class.getMethod(action, JFrame.class, File.class).invoke(null, frame, loc)).booleanValue()) {
parent.setVisible(true);
frame.dispose();
}
} catch(Throwable t) {
System.err.println("Could not execute action \"" + action + "\":");
t.printStackTrace();
JOptionPane.showMessageDialog(frame, "Could not execute action \"" + action + "\".\nThis shouldn't happen; please contact mod author.", "Error", JOptionPane.ERROR_MESSAGE);
}
}
}
@Override
public void windowOpened(WindowEvent e) {
}
@Override
public void windowClosing(WindowEvent e) {
parent.setVisible(true);
frame.dispose();
}
@Override
public void windowClosed(WindowEvent e) {
}
@Override
public void windowIconified(WindowEvent e) {
}
@Override
public void windowDeiconified(WindowEvent e) {
}
@Override
public void windowActivated(WindowEvent e) {
}
@Override
public void windowDeactivated(WindowEvent e) {
}
}

View File

@@ -0,0 +1,125 @@
package net.montoyo.mcef.setup;
import javax.swing.*;
import java.io.File;
import java.util.Iterator;
public class Processes {
public static boolean install(JFrame parent, File dst) {
File curJar = SetupUtil.getSelfJarLocation();
if(curJar == null) {
JOptionPane.showMessageDialog(parent, "Could not locate the current JAR file.\nThis shouldn't happen, contact mod author.\nCannot continue.", "Error", JOptionPane.ERROR_MESSAGE);
return false;
}
File mods = new File(dst, "mods");
if(mods.exists()) {
File[] modList = mods.listFiles();
for(File f: modList) {
String fname = f.getName().toLowerCase();
if(SetupUtil.areFileEqual(f, curJar)) {
SetupUI.INSTANCE.abortSelfDestruct();
JOptionPane.showMessageDialog(parent, "MCEF was successfully installed!\nIn fact, it was already installed here.\nAlso make sure Forge is installed!", "Well... there was nothing to do!", JOptionPane.INFORMATION_MESSAGE);
return true;
} else if(f.isFile() && fname.startsWith("mcef") && fname.endsWith(".jar")) {
while(!SetupUtil.tryDelete(f)) {
if(JOptionPane.showConfirmDialog(parent, "An older version of MCEF has been found and cannot be deleted.\nPlease close Minecraft or remove the following file manually:\n" + f.getAbsolutePath(), "WARNING", JOptionPane.OK_CANCEL_OPTION) == JOptionPane.CANCEL_OPTION)
return false;
}
}
}
} else if(!mods.mkdir()) {
JOptionPane.showMessageDialog(parent, "Could not create mods directory. Make sure you have the rights to do that.\nCannot continue.", "Error", JOptionPane.ERROR_MESSAGE);
return false;
}
if(SetupUtil.copyFile(curJar, new File(mods, curJar.getName()))) {
JOptionPane.showMessageDialog(parent, "MCEF was successfully installed!\nDon't forget to install Forge!", "All done!", JOptionPane.INFORMATION_MESSAGE);
return true;
} else {
JOptionPane.showMessageDialog(parent, "Installation failed\nCould not copy JAR to mods folder.", "Critical error", JOptionPane.ERROR_MESSAGE);
return false;
}
}
private static boolean recursiveDelete(File dir) {
if(!dir.exists())
return true;
File[] files = dir.listFiles();
boolean allOk = true;
for(File f: files) {
if(f.isDirectory()) {
if(!recursiveDelete(f))
allOk = false;
} else if(!SetupUtil.tryDelete(f))
allOk = false;
}
return SetupUtil.tryDelete(dir) && allOk;
}
public static boolean uninstall(JFrame parent, File dst) {
File configDir = new File(dst, "config");
FileListing fl = new FileListing(configDir);
if(!fl.load()) {
JOptionPane.showMessageDialog(parent, "Could not locate MCEF file listing. It is either missing,\nor you selected the wrong Minecraft location.\nCannot continue.", "Error", JOptionPane.ERROR_MESSAGE);
return false;
}
//Destroy resources and cache
boolean allDeleted = true;
Iterator<String> files = fl.iterator();
while(files.hasNext()) {
if(!SetupUtil.tryDelete(new File(dst, files.next())))
allDeleted = false;
}
if(!recursiveDelete(new File(dst, "MCEFCache")))
allDeleted = false;
//Destroy file listing and configs
if(!fl.selfDestruct())
allDeleted = false;
if(!SetupUtil.tryDelete(new File(configDir, "MCEF.cfg")))
allDeleted = false;
if(!SetupUtil.tryDelete(new File(dst, "mcef.json")))
allDeleted = false;
//Destroy mod file
File curJar = SetupUtil.getSelfJarLocation();
File mods = new File(dst, "mods");
if(mods.exists()) {
File[] modList = mods.listFiles();
for(File f: modList) {
String fname = f.getName().toLowerCase();
if(SetupUtil.areFileEqual(f, curJar)) {
//Can't self-destruct JAR; add to delete-at-exit file list
SetupUI.INSTANCE.initiateSelfDestruct(f);
} else if(f.isFile() && fname.startsWith("mcef") && fname.endsWith(".jar")) {
if(!SetupUtil.tryDelete(f))
allDeleted = false;
}
}
}
//Show results
if(allDeleted)
JOptionPane.showMessageDialog(parent, "MCEF was successfully uninstalled!\nThanks for using it!", "All done!", JOptionPane.INFORMATION_MESSAGE);
else
JOptionPane.showMessageDialog(parent, "MCEF was uninstalled, but some files couldn't be removed; sorry about that...", "Almost everything done!", JOptionPane.WARNING_MESSAGE);
return true;
}
}

View File

@@ -0,0 +1,199 @@
package net.montoyo.mcef.setup;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.URI;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class SetupUI implements ActionListener, WindowListener, MouseListener {
public static SetupUI INSTANCE = null;
public static void main(String[] args) {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch(Throwable t) {
t.printStackTrace();
}
INSTANCE = new SetupUI();
}
private File selfDestruct = null;
private JFrame frame;
private GridLayout layout;
private JPanel mainPane;
private JButton btnInstall;
private JButton btnConfigure;
private JButton btnUninstall;
private JButton btnExit;
private JLabel aboutLabel;
public SetupUI() {
//Setup
frame = new JFrame("MCEF Setup");
frame.setMinimumSize(new Dimension(300, 100));
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(this);
//Layout & content
btnInstall = new JButton("Install");
btnConfigure = new JButton("Configure");
btnUninstall = new JButton("Uninstall");
btnExit = new JButton("Exit");
btnInstall.addActionListener(this);
btnConfigure.addActionListener(this);
btnUninstall.addActionListener(this);
btnExit.addActionListener(this);
JPanel labelPane = new JPanel();
labelPane.setLayout(new BoxLayout(labelPane, BoxLayout.PAGE_AXIS));
labelPane.add(new JLabel("Welcome to the MCEF Setup Wizard."));
labelPane.add(new JLabel("What do you like to do?"));
mainPane = new JPanel();
layout = new GridLayout(6, 1, 3, 3);
mainPane.setBorder(new EmptyBorder(3, 3, 3, 3));
mainPane.setLayout(layout);
mainPane.add(labelPane);
mainPane.add(btnInstall);
mainPane.add(btnConfigure);
mainPane.add(btnUninstall);
mainPane.add(btnExit);
aboutLabel = new JLabel("<html><i>MCEF was written by <u><font color=\"#000099\">montoyo</font></u></i>&nbsp;&nbsp;</html>");
aboutLabel.setHorizontalAlignment(JLabel.RIGHT);
aboutLabel.addMouseListener(this);
mainPane.add(aboutLabel);
//Display
frame.setContentPane(mainPane);
frame.pack();
frame.setVisible(true);
}
@Override
public void actionPerformed(ActionEvent e) {
if(e.getSource() == btnExit)
windowClosing(null);
else if(e.getSource() == btnInstall)
new McLocationPrompt(frame, "install");
else if(e.getSource() == btnConfigure)
new McLocationPrompt(frame, "configure");
else if(e.getSource() == btnUninstall)
new McLocationPrompt(frame, "uninstall");
}
void initiateSelfDestruct(File f) {
selfDestruct = f;
}
void abortSelfDestruct() {
selfDestruct = null;
}
private void runSelfDestructionUnsafe() throws Throwable {
File tmp = File.createTempFile("mcef-deleter", ".jar");
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(tmp));
InputStream is = SetupUI.class.getResourceAsStream("/net/montoyo/mcef/setup/Deleter.class");
byte[] buf = new byte[8192];
int read;
zos.putNextEntry(new ZipEntry("net/montoyo/mcef/setup/Deleter.class"));
while((read = is.read(buf)) > 0)
zos.write(buf, 0, read);
try {
zos.closeEntry();
} catch(Throwable t) {}
SetupUtil.silentClose(zos);
SetupUtil.silentClose(is);
String java = "\"" + System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";
if(System.getProperty("os.name").toLowerCase().contains("win"))
java += "w.exe";
java += "\" -classpath \"";
java += tmp.getAbsolutePath();
java += "\" net.montoyo.mcef.setup.Deleter \"";
java += selfDestruct.getAbsolutePath();
java += "\"";
System.out.println("Running auto-deleter:");
System.out.println(java);
Runtime.getRuntime().exec(java);
}
@Override
public void windowOpened(WindowEvent e) {
}
@Override
public void windowClosing(WindowEvent e) {
frame.dispose();
if(selfDestruct != null) {
try {
runSelfDestructionUnsafe();
} catch(Throwable t) {
System.err.println("Failed to destruct myself:");
t.printStackTrace();
}
}
}
@Override
public void windowClosed(WindowEvent e) {
}
@Override
public void windowIconified(WindowEvent e) {
}
@Override
public void windowDeiconified(WindowEvent e) {
}
@Override
public void windowActivated(WindowEvent e) {
}
@Override
public void windowDeactivated(WindowEvent e) {
}
@Override
public void mouseClicked(MouseEvent e) {
try {
Desktop.getDesktop().browse(new URI("https://montoyo.net"));
} catch(Throwable t) {
t.printStackTrace();
}
}
@Override
public void mousePressed(MouseEvent e) {
}
@Override
public void mouseReleased(MouseEvent e) {
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
}
}

View File

@@ -0,0 +1,94 @@
package net.montoyo.mcef.setup;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class SetupUtil {
static boolean tryDelete(File f) {
if(!f.exists())
return true;
if(f.delete())
return true;
else {
File dst = new File(f.getParentFile(), f.getName() + "_" + System.currentTimeMillis() + "_" + ((int) (Math.random() * 10000)) + ".tmp");
if(f.renameTo(dst)) {
if(!dst.delete())
dst.deleteOnExit();
return true;
} else
return false;
}
}
static File getSelfJarLocation() {
try {
File ret = new File(SetupUtil.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath());
if(ret.exists() && ret.isFile())
return ret;
} catch(Throwable t) {
System.err.println("Could not locate own JAR (try #1):");
t.printStackTrace();
}
try {
File ret = new File(ClassLoader.getSystemClassLoader().getResource(".").getPath());
if(ret.exists() && ret.isFile())
return ret;
} catch(Throwable t) {
System.err.println("Could not locate own JAR (try #2):");
t.printStackTrace();
}
return null;
}
static void silentClose(Object o) {
try {
o.getClass().getMethod("close").invoke(o);
} catch(Throwable t) {}
}
static boolean copyFile(File src, File dst) {
byte[] buf = new byte[65536];
int read;
try {
FileInputStream fis = new FileInputStream(src);
FileOutputStream fos = new FileOutputStream(dst);
while((read = fis.read(buf)) > 0)
fos.write(buf, 0, read);
silentClose(fos);
silentClose(fis);
return true;
} catch(Throwable t) {
System.err.println("Could NOT copy \"" + src.getAbsolutePath() + "\" to \"" + dst.getAbsolutePath() + "\":");
t.printStackTrace();
return false;
}
}
static boolean areFileEqual(File a, File b) {
if(a == null || b == null)
return false;
try {
String ap = a.getCanonicalPath();
String bp = b.getCanonicalPath();
return System.getProperty("os.name").toLowerCase().contains("win") ? ap.equalsIgnoreCase(bp) : ap.equals(bp); //Windows paths are case-insensitive
} catch(IOException e) {
System.err.println("Could not compare file path, returning non-equal:");
e.printStackTrace();
return false;
}
}
}

View File

@@ -0,0 +1,50 @@
package net.montoyo.mcef.setup;
import java.util.Comparator;
final class SlashComparator implements Comparator<String> {
private Comparator<String> fallback;
SlashComparator(Comparator<String> fb) {
fallback = fb;
}
@Override
public int compare(String a, String b) {
int slashA = 0;
int slashB = 0;
for(int i = 0; i < a.length(); i++) { //WARNING: Sub-optimized code here!
if(a.charAt(i) == '/' || a.charAt(i) == '\\')
slashA++;
}
for(int i = 0; i < b.length(); i++) {
if(b.charAt(i) == '/' || b.charAt(i) == '\\')
slashB++;
}
int ret = slashB - slashA;
if(ret == 0 && fallback != null)
return fallback.compare(a, b);
else
return ret;
}
@Override
public boolean equals(Object obj) {
if(obj == null)
return false;
else if(obj instanceof SlashComparator) {
SlashComparator other = (SlashComparator) obj;
if(fallback == null)
return other.fallback == null;
else
return other.fallback != null && fallback.equals(other.fallback);
} else
return false;
}
}

View File

@@ -0,0 +1,23 @@
package net.montoyo.mcef.utilities;
/**
* Dummy progress listener. Does nothing.
* @author montoyo
* @see Util#secure(IProgressListener)
*
*/
public class DummyProgressListener implements IProgressListener {
@Override
public void onProgressed(double d) {
}
@Override
public void onTaskChanged(String name) {
}
@Override
public void onProgressEnd() {
}
}

View File

@@ -0,0 +1,44 @@
package net.montoyo.mcef.utilities;
import net.minecraftforge.fml.common.ProgressManager;
public class ForgeProgressListener implements IProgressListener {
private ProgressManager.ProgressBar progressBar = null;
private int lastVal = 0;
private void stepUntil(int val) {
// Ensures progress is properly updated before popping
while (lastVal < val && progressBar != null) {
progressBar.step(lastVal + "%");
lastVal++;
}
}
@Override
public void onProgressed(double d) {
stepUntil((int) Util.clamp(d, 0.d, 100.d));
}
@Override
public void onTaskChanged(String name) {
if (progressBar != null) {
stepUntil(100); // Ensure the last task is fully completed before popping
ProgressManager.pop(progressBar);
progressBar = null; // Reset progressBar after popping
}
progressBar = ProgressManager.push(name, 100, false);
lastVal = 0;
}
@Override
public void onProgressEnd() {
if (progressBar != null) {
stepUntil(100); // Ensure progress reaches 100% before popping
ProgressManager.pop(progressBar);
progressBar = null;
}
}
}

View File

@@ -0,0 +1,42 @@
package net.montoyo.mcef.utilities;
import java.util.HashMap;
import java.util.Map;
import net.montoyo.mcef.MCEF;
import net.montoyo.mcef.utilities.Log;
public class ForgeVersionParser {
// Predefined mapping of Forge versions to Minecraft versions
private static final Map<String, String> forgeVersionMapping = new HashMap<>();
static {
// Mapping Forge versions to Minecraft versions
forgeVersionMapping.put("forge-14.23.5.2859", "1.12.2");
forgeVersionMapping.put("forge-14.23.5.2838", "1.12.2");
forgeVersionMapping.put("forge-14.23.5.2768", "1.12.2");
// Add more mappings here as needed
}
// Method to parse the version
public String parse(String version) {
if (version == null) {
return null;
}
// Check if the version contains "forge-"
if (version.startsWith("forge-")) {
// Look up the Forge version in the mapping table
String realVersion = forgeVersionMapping.get(version);
if (realVersion != null) {
return realVersion;
} else {
// Handle unknown Forge version, maybe log a warning or use a default
return "Unknown Minecraft Version (Forge)";
}
} else {
// If it doesn't contain "forge-", it's likely a real Minecraft version
return version;
}
}
}

View File

@@ -0,0 +1,27 @@
package net.montoyo.mcef.utilities;
/**
* This interface is used by classes that wants to keep track of the progress of some tasks.
* @author montoyo
*
*/
public interface IProgressListener {
/**
* Call this when the current task progressed.
* @param d The current task progress in percent.
*/
public void onProgressed(double d);
/**
* Call this if the current task changed.
* @param name The name of the new task.
*/
public void onTaskChanged(String name);
/**
* Call this when everything is finished.
*/
public void onProgressEnd();
}

View File

@@ -0,0 +1,29 @@
package net.montoyo.mcef.utilities;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
/**
* A set of functions to log messages into the MCEF log channel.
* @author montoyo
*
*/
public class Log {
public static void info(String what, Object... data) {
LogManager.getLogger("MCEF").log(Level.INFO, String.format("[MCEF] " + what, data));
}
public static void warning(String what, Object... data) {
LogManager.getLogger("MCEF").log(Level.WARN, String.format("[MCEF] " + what, data));
}
public static void error(String what, Object... data) {
LogManager.getLogger("MCEF").log(Level.ERROR, String.format("[MCEF] " + what, data));
}
public static void errorEx(String what, Throwable t, Object... data) {
LogManager.getLogger("MCEF").log(Level.ERROR, String.format("[MCEF] " + what, data), t);
}
}

View File

@@ -0,0 +1,97 @@
package net.montoyo.mcef.utilities;
import net.montoyo.mcef.MCEF;
import net.montoyo.mcef.utilities.Log;
import org.cef.OS;
import java.util.Locale;
public class Platform {
public static String id;
public static String idFull;
public static String arch;
public static String archFull;
public static String PLATFORM;
public static String PLATFORMFULL;
static { // Static block for initialization
detectPlatform();
}
private static void detectPlatform() {
try {
id = System.getProperty("os.name", "unknown").toLowerCase(Locale.US);
arch = System.getProperty("os.arch", "unknown").toLowerCase(Locale.US);
} catch (Exception e) {
Log.error("Couldn't get OS name or architecture.");
id = "unknown";
arch = "unknown";
}
// Normalize OS name
if (OS.isWindows()) {
id = "win";
idFull = "windows";
} else if (OS.isMacintosh()) {
id = "mac";
idFull = "macos";
} else if (OS.isLinux()) {
id = "linux";
idFull = "linux";
} else {
Log.error("Your OS isn't supported by MCEF.");
id = "unknown";
idFull = "unknown";
return;
}
// Normalize CPU architecture
if (arch.equals("x86_64") || arch.equals("amd64") || arch.equals("64")) {
arch = "64";
archFull = "amd64";
} else if (arch.equals("aarch64") || arch.equals("arm64")) {
if (!id.equals("mac") || !id.equals("darwin")) { // Only allow ARM64 for Macs
Log.error("ARM-based platforms other than Mac are not supported by MCEF.");
id = "unknown";
idFull = "unknown";
return;
}
arch = "arm64";
archFull = "arm64";
} else {
Log.error("Your CPU architecture isn't supported by MCEF.");
id = "unknown";
idFull = "unknown";
return;
}
// Set platform strings
PLATFORM = id + arch;
PLATFORMFULL = idFull + "_" + archFull;
}
public static String getOS() {
return id;
}
public static String getPlatform() {
return PLATFORM;
}
public static String getPlatformFull() {
return PLATFORMFULL;
}
public static boolean isWindows() {
return "win".equals(id);
}
public static boolean isMacOS() {
return "mac".equals(id) || "darwin".equals(id);
}
public static boolean isLinux() {
return "linux".equals(id);
}
}

View File

@@ -0,0 +1,347 @@
package net.montoyo.mcef.utilities;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.zip.GZIPInputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import net.montoyo.mcef.MCEF;
import net.montoyo.mcef.remote.Mirror;
import net.montoyo.mcef.remote.MirrorManager;
import org.cef.OS;
import javax.net.ssl.HttpsURLConnection;
import java.net.URL;
public class Util {
private static final DummyProgressListener DPH = new DummyProgressListener();
/**
* Clamps d between min and max.
*
* @param d The value to clamp.
* @param min The minimum.
* @param max The maximum.
* @return The clamped value.
*/
public static double clamp(double d, double min, double max) {
if(d < min)
return min;
else if(d > max)
return max;
else
return d;
}
/**
* Extracts a ZIP archive into a folder.
*
* @param zip The ZIP archive file to extract.
* @param out The output directory for the ZIP content.
* @return true if the extraction was successful.
*/
public static boolean extractZip(File zip, File out) {
// For macOS, the "unzip" utility seems to be reliable at setting certain flags on executables when extracting
// Otherwise, extracting a .app is a pain. It refuses to run without setting executable flags on the contents, etc
if (OS.isMacintosh()) {
try {
Process unzip = Runtime.getRuntime().exec(new String[]{"/usr/bin/unzip", zip.getAbsolutePath(), "-d", out.getAbsolutePath()});
unzip.waitFor();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
ZipInputStream zis;
try {
zis = new ZipInputStream(new FileInputStream(zip));
} catch(FileNotFoundException e) {
Log.error("Couldn't extract %s: File not found.", zip.getName());
e.printStackTrace();
return false;
}
try {
ZipEntry ze;
while((ze = zis.getNextEntry()) != null) {
if(ze.isDirectory())
continue;
File dst = new File(out, ze.getName());
delete(dst);
mkdirs(dst);
FileOutputStream fos = new FileOutputStream(dst);
byte[] data = new byte[65536];
int read;
while((read = zis.read(data)) > 0)
fos.write(data, 0, read);
close(fos);
}
return true;
} catch(FileNotFoundException e) {
Log.error("Couldn't extract a file from %s. Maybe you're missing some permissions?", zip.getName());
e.printStackTrace();
return false;
} catch(IOException e) {
Log.error("IOException while extracting %s.", zip.getName());
e.printStackTrace();
return false;
} finally {
close(zis);
}
}
/**
* Downloads a file from a URL.
*
* @param urlString The URL to download from.
* @param outputFile The file to save the downloaded content to.
* @param ph A progress handler.
* @throws IOException If the download fails.
*/
public static void downloadFile(String urlString, File outputFile, IProgressListener ph) throws IOException {
try {
Log.info("Downloading: " + urlString + " -> " + outputFile.getCanonicalPath());
ph = secure(ph);
ph.onTaskChanged("Downloading " + outputFile.getName());
URL url = new URL(urlString);
HttpURLConnection urlConnection;
int statusCode;
urlConnection = (HttpURLConnection) url.openConnection();
// Set custom User-Agent
String userAgent = "MCEF/3.0 (Java/" + System.getProperty("java.version") + "; " + System.getProperty("os.name") + " " + System.getProperty("os.arch") + ")";
urlConnection.setRequestProperty("User-Agent", userAgent);
urlConnection.connect();
statusCode = urlConnection.getResponseCode();
if (statusCode != HttpURLConnection.HTTP_OK) {
Log.info("Error: " + urlString + " returned HTTP " + statusCode);
throw new IOException("Server returned HTTP response code: " + statusCode);
}
int fileSize = urlConnection.getContentLength();
BufferedInputStream inputStream = new BufferedInputStream(urlConnection.getInputStream());
FileOutputStream outputStream = new FileOutputStream(outputFile);
byte[] buffer = new byte[2048];
int count;
int readBytes = 0;
while ((count = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, count);
readBytes += count;
float percentComplete = (float) readBytes / fileSize;
ph.onProgressed(percentComplete * 100);
}
inputStream.close();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
Log.info(e.getMessage());
throw new IOException("Failed to download " + urlString, e);
}
}
/**
* Same as {@link #downloadFile(String, File, IProgressListener)} but returns true if the download was successful.
*
* @param urlString The URL to download from.
* @param outputFile The file to save the downloaded content to.
* @param ph A progress handler.
* @throws IOException If the download fails.
*/
public static boolean downloadFileSafe(String urlString, File outputFile, IProgressListener ph) {
try {
// Check if the file path (dir) exists, if not create it
mkdirs(outputFile);
// Download the file
downloadFile(urlString, outputFile, ph);
return true;
} catch (IOException e) {
Log.error("Failed to download %s", urlString);
e.printStackTrace();
return false;
}
}
/**
* Extracts a tar.gz file.
*
* @param tarGzFile The tar.gz file to extract.
* @param outputDirectory The directory to extract files to.
*/
public static void extractTarGz(File tarGzFile, File outputDirectory, IProgressListener ph) {
ph = secure(ph);
ph.onTaskChanged("Extracting " + tarGzFile.getName());
outputDirectory.mkdirs();
long fileSize = tarGzFile.length();
long totalBytesRead = 0;
try (TarArchiveInputStream tarInput = new TarArchiveInputStream(new GzipCompressorInputStream(new FileInputStream(tarGzFile)))) {
TarArchiveEntry entry;
while ((entry = tarInput.getNextTarEntry()) != null) {
if (entry.isDirectory()) {
continue;
}
File outputFile = new File(outputDirectory, entry.getName());
outputFile.getParentFile().mkdirs();
try (OutputStream outputStream = new FileOutputStream(outputFile)) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = tarInput.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
float percentComplete = ((float) totalBytesRead / fileSize) / 2.6158204f; // Rough compression ratio
ph.onProgressed(percentComplete * 100);
}
}
}
} catch (IOException e) {
Log.error("Failed to extract gzip file to " + outputDirectory, e);
}
ph.onProgressed(100);
}
/**
* Convenience function. Secures a progress listener.
* If pl is null, then a dummy empty progress listener will be returned.
*
* @param pl The progress handler to secure.
* @return A progress handler that is never null.
* @see IProgressListener
*/
public static IProgressListener secure(IProgressListener pl) {
return (pl == null) ? DPH : pl;
}
/**
* Renames a file using a string.
*
* @param src The file to rename.
* @param name The new name of the file.
* @return the new file or null if it failed.
*/
public static File rename(File src, String name) {
File ret = new File(src.getParentFile(), name);
if(src.renameTo(ret))
return ret;
else
return null;
}
/**
* Makes sure that the directory in which the file is exists.
* If this one doesn't exist, i'll be created.
*
* @param f The file.
*/
public static void mkdirs(File f) {
File p = f.getParentFile();
if(!p.exists())
p.mkdirs();
}
/**
* Tries to delete a file in an advanced way.
* Does a warning in log if it couldn't delete it neither rename it.
*
* @param f The file to be deleted.
* @see #delete(File)
*/
public static void delete(String f) {
delete(new File(f));
}
/**
* Tries to delete a file in an advanced way.
* Does a warning in log if it couldn't delete it neither rename it.
*
* @param f The file to be deleted.
* @see #delete(String)
*/
public static void delete(File f) {
if(!f.exists() || f.delete())
return;
File mv = new File(f.getParentFile(), "deleteme" + ((int) (Math.random() * 100000.d)));
if(f.renameTo(mv)) {
if(!mv.delete())
mv.deleteOnExit();
return;
}
Log.warning("Couldn't delete file! If there's any problems, please try to remove it yourself. Path: %s", f.getAbsolutePath());
}
/**
* Calls "close" on the specified object without throwing any exceptions.
* This is usefull with input and output streams.
*
* @param o The object to call close on.
*/
public static void close(Object o) {
try {
o.getClass().getMethod("close").invoke(o);
} catch(Throwable t) {}
}
/**
* Same as {@link Files#isSameFile(Path, Path)} but if an {@link IOException} is thrown,
* return false.
*
* @param p1 Path 1
* @param p2 Path 2
* @return true if the paths are the same, false if they are not or if an exception is thrown during the comparison
*/
public static boolean isSameFile(Path p1, Path p2) {
try {
return Files.isSameFile(p1, p2);
} catch(IOException e) {
return false;
}
}
/**
* Same as {@link System#getenv(String)}, but if no such environment variable is
* defined, will return an empty string instead of null.
*
* @param name Name of the environment variable to get
* @return The value of this environment variable (may be empty but never null)
*/
public static String getenv(String name) {
String ret = System.getenv(name);
return ret == null ? "" : ret;
}
}

View File

@@ -0,0 +1,107 @@
package net.montoyo.mcef.utilities;
/**
* A utility class used for parsing decimal dot-separated versions from strings and comparing them.
* Example: 5.4.9
*
* @author montoyo
*
*/
public class Version {
private int[] numbers;
/**
* Constructs a version from a decimal dot-separated version string.
* @param v A version string, such as 5.4.9
*/
public Version(String v) {
String[] ray = v.trim().split("\\.");
numbers = new int[ray.length];
for(int i = 0; i < ray.length; i++) {
try {
numbers[i] = Integer.parseInt(ray[i]);
} catch(NumberFormatException e) {
Log.error("Couldn't parse %s. Number %d will be zero.", v, i);
e.printStackTrace();
numbers[i] = 0;
}
}
//Look for useless ending zeroes
int end;
for(end = numbers.length - 1; end >= 0; end--) {
if(numbers[end] != 0)
break;
}
end++;
if(end != numbers.length) { //Is there any? Remove them!
int[] na = new int[end];
System.arraycopy(numbers, 0, na, 0, end);
numbers = na;
}
}
/**
* Compares two versions. Return true if this instance is bigger than v.
* If both describes the same version, false is returned.
*
* @param v The version to compare with.
* @return true if this instance is bigger than v.
*/
public boolean isBiggerThan(Version v) {
int len = Math.min(numbers.length, v.numbers.length);
for(int i = 0; i < len; i++) {
if(numbers[i] > v.numbers[i])
return true;
}
return numbers.length > v.numbers.length;
}
/**
* Compares two versions. Returns true if this instance is equal to o.
* @param o The version to compare with.
* @return true if both objects describes the same version.
*/
@Override
public boolean equals(Object o) {
if(!(o instanceof Version))
return false;
Version v = (Version) o;
if(v == this)
return true;
if(numbers.length != v.numbers.length)
return false;
for(int i = 0; i < numbers.length; i++) {
if(numbers[i] != v.numbers[i])
return false;
}
return true;
}
/**
* Turns this version class into a decimal dot-separated version string.
* @return a version string, such as 5.4.9
*/
@Override
public String toString() {
String ret = "";
for(int i = 0; i < numbers.length; i++) {
if(i > 0)
ret += '.';
ret += numbers[i];
}
return ret;
}
}

View File

@@ -0,0 +1,80 @@
package net.montoyo.mcef.virtual;
import net.montoyo.mcef.api.IBrowser;
import net.montoyo.mcef.api.IStringVisitor;
public class VirtualBrowser implements IBrowser {
@Override
public void close() {
}
@Override
public void resize(int width, int height) {
}
@Override
public void draw(double x1, double y1, double x2, double y2) {
}
@Override
public int getTextureID() {
return 0;
}
@Override
public void injectMouseMove(int x, int y, int mods, boolean left) {
}
@Override
public void injectMouseButton(int x, int y, int mods, int btn, boolean pressed, int ccnt) {
}
@Override
public void injectKeyTyped(char c, int mods) {
}
@Override
public void injectKeyPressedByKeyCode(int keyCode, char c, int mods) {
}
@Override
public void injectKeyReleasedByKeyCode(int keyCode, char c, int mods) {
}
@Override
public void injectMouseWheel(int x, int y, int mods, int amount, int rot) {
}
@Override
public void runJS(String script, String frame) {
}
@Override
public void loadURL(String url) {
}
@Override
public void goBack() {
}
@Override
public void goForward() {
}
@Override
public String getURL() {
return "about:blank";
}
@Override
public void visitSource(IStringVisitor isv) {
isv.visit("https://www.youtube.com/watch?v=VX5gXHcbJAk");
}
@Override
public boolean isPageLoading() {
return true;
}
}

View File

@@ -0,0 +1,546 @@
// Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef;
import org.cef.callback.CefSchemeHandlerFactory;
import org.cef.handler.CefAppHandler;
import org.cef.handler.CefAppHandlerAdapter;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashSet;
/**
* Exposes static methods for managing the global CEF context.
*/
public class CefApp extends CefAppHandlerAdapter {
public final class CefVersion {
public final int JCEF_COMMIT_NUMBER;
public final int CEF_VERSION_MAJOR;
public final int CEF_VERSION_MINOR;
public final int CEF_VERSION_PATCH;
public final int CEF_COMMIT_NUMBER;
public final int CHROME_VERSION_MAJOR;
public final int CHROME_VERSION_MINOR;
public final int CHROME_VERSION_BUILD;
public final int CHROME_VERSION_PATCH;
private CefVersion(int jcefCommitNo, int cefMajor, int cefMinor, int cefPatch,
int cefCommitNo, int chrMajor, int chrMin, int chrBuild, int chrPatch) {
JCEF_COMMIT_NUMBER = jcefCommitNo;
CEF_VERSION_MAJOR = cefMajor;
CEF_VERSION_MINOR = cefMinor;
CEF_VERSION_PATCH = cefPatch;
CEF_COMMIT_NUMBER = cefCommitNo;
CHROME_VERSION_MAJOR = chrMajor;
CHROME_VERSION_MINOR = chrMin;
CHROME_VERSION_BUILD = chrBuild;
CHROME_VERSION_PATCH = chrPatch;
}
public String getJcefVersion() {
return CEF_VERSION_MAJOR + "." + CEF_VERSION_MINOR + "." + CEF_VERSION_PATCH + "."
+ JCEF_COMMIT_NUMBER;
}
public String getCefVersion() {
return CEF_VERSION_MAJOR + "." + CEF_VERSION_MINOR + "." + CEF_VERSION_PATCH;
}
public String getChromeVersion() {
return CHROME_VERSION_MAJOR + "." + CHROME_VERSION_MINOR + "." + CHROME_VERSION_BUILD
+ "." + CHROME_VERSION_PATCH;
}
@Override
public String toString() {
return "JCEF Version = " + getJcefVersion() + "\n"
+ "CEF Version = " + getCefVersion() + "\n"
+ "Chromium Version = " + getChromeVersion();
}
}
/**
* The CefAppState gives you a hint if the CefApp is already usable or not
* usable any more. See values for details.
*/
public enum CefAppState {
/**
* No CefApp instance was created yet. Call getInstance() to create a new
* one.
*/
NONE,
/**
* CefApp is new created but not initialized yet. No CefClient and no
* CefBrowser was created until now.
*/
NEW,
/**
* CefApp is in its initializing process. Please wait until initializing is
* finished.
*/
INITIALIZING,
/**
* CefApp is up and running. At least one CefClient was created and the
* message loop is running. You can use all classes and methods of JCEF now.
*/
INITIALIZED,
/**
* CefApp is in its shutdown process. All CefClients and CefBrowser
* instances will be disposed. No new CefClient or CefBrowser is allowed to
* be created. The message loop will be performed until all CefClients and
* all CefBrowsers are disposed completely.
*/
SHUTTING_DOWN,
/**
* CefApp is terminated and can't be used any more. You can shutdown the
* application safely now.
*/
TERMINATED
}
/**
* According the singleton pattern, this attribute keeps
* one single object of this class.
*/
private static CefApp self = null;
private static CefAppHandler appHandler_ = null;
private static CefAppState state_ = CefAppState.NONE;
private Timer workTimer_ = null;
private HashSet<CefClient> clients_ = new HashSet<CefClient>();
private CefSettings settings_ = null;
/**
* To get an instance of this class, use the method
* getInstance() instead of this CTOR.
* <p>
* The CTOR is called by getInstance() as needed and
* loads all required JCEF libraries.
*
* @throws UnsatisfiedLinkError
*/
private CefApp(String[] args, CefSettings settings) throws UnsatisfiedLinkError {
super(args);
if (settings != null) settings_ = settings.clone();
if (appHandler_ == null) {
appHandler_ = this;
}
if (!N_PreInitialize())
throw new IllegalStateException("Failed to pre-initialize native code");
}
/**
* Assign an AppHandler to CefApp. The AppHandler can be used to evaluate
* application arguments, to register your own schemes and to hook into the
* shutdown sequence. See CefAppHandler for more details.
* <p>
* This method must be called before CefApp is initialized. CefApp will be
* initialized automatically if you call createClient() the first time.
*
* @param appHandler An instance of CefAppHandler.
* @throws IllegalStateException in case of CefApp is already initialized
*/
public static void addAppHandler(CefAppHandler appHandler) throws IllegalStateException {
if (getState().compareTo(CefAppState.NEW) > 0)
throw new IllegalStateException("Must be called before CefApp is initialized");
appHandler_ = appHandler;
}
/**
* Get an instance of this class.
*
* @return an instance of this class
* @throws UnsatisfiedLinkError
*/
public static synchronized CefApp getInstance() throws UnsatisfiedLinkError {
return getInstance(null, null);
}
public static synchronized CefApp getInstance(String[] args) throws UnsatisfiedLinkError {
return getInstance(args, null);
}
public static synchronized CefApp getInstance(CefSettings settings)
throws UnsatisfiedLinkError {
return getInstance(null, settings);
}
public static synchronized CefApp getInstance(String[] args, CefSettings settings)
throws UnsatisfiedLinkError {
if (settings != null) {
if (getState() != CefAppState.NONE && getState() != CefAppState.NEW)
throw new IllegalStateException("Settings can only be passed to CEF"
+ " before createClient is called the first time.");
}
if (self == null) {
if (getState() == CefAppState.TERMINATED)
throw new IllegalStateException("CefApp was terminated");
self = new CefApp(args, settings);
setState(CefAppState.NEW);
}
return self;
}
public final void setSettings(CefSettings settings) throws IllegalStateException {
if (getState() != CefAppState.NONE && getState() != CefAppState.NEW)
throw new IllegalStateException("Settings can only be passed to CEF"
+ " before createClient is called the first time.");
settings_ = settings.clone();
}
public final CefVersion getVersion() {
try {
return N_GetVersion();
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return null;
}
/**
* Returns the current state of CefApp.
*
* @return current state.
*/
public final static CefAppState getState() {
synchronized (state_) {
return state_;
}
}
private static final void setState(final CefAppState state) {
synchronized (state_) {
state_ = state;
}
if (appHandler_ != null) appHandler_.stateHasChanged(state);
}
//montoyo: Added for MCEF
public static final void forceShutdownState() {
synchronized (state_) {
state_ = CefAppState.SHUTTING_DOWN;
}
}
/**
* To shutdown the system, it's important to call the dispose
* method. Calling this method closes all client instances with
* and all browser instances each client owns. After that the
* message loop is terminated and CEF is shutdown.
*/
public synchronized final void dispose() {
switch (getState()) {
case NEW:
// Nothing to do inspite of invalidating the state
setState(CefAppState.TERMINATED);
break;
case INITIALIZING:
case INITIALIZED:
// (3) Shutdown sequence. Close all clients and continue.
setState(CefAppState.SHUTTING_DOWN);
if (clients_.isEmpty()) {
shutdown();
} else {
// shutdown() will be called from clientWasDisposed() when the last
// client is gone.
// Use a copy of the HashSet to avoid iterating during modification.
HashSet<CefClient> clients = new HashSet<CefClient>(clients_);
for (CefClient c : clients) {
c.dispose();
}
}
break;
case NONE:
case SHUTTING_DOWN:
case TERMINATED:
// Ignore shutdown, CefApp is already terminated, in shutdown progress
// or was never created (shouldn't be possible)
break;
}
}
/**
* Creates a new client instance and returns it to the caller.
* One client instance is responsible for one to many browser
* instances
*
* @return a new client instance
*/
public synchronized CefClient createClient() {
switch (getState()) {
case NEW:
setState(CefAppState.INITIALIZING);
initialize();
// FALL THRU
case INITIALIZING:
case INITIALIZED:
CefClient client = new CefClient();
clients_.add(client);
return client;
default:
throw new IllegalStateException("Can't crate client in state " + state_);
}
}
/**
* Register a scheme handler factory for the specified |scheme_name| and
* optional |domain_name|. An empty |domain_name| value for a standard scheme
* will cause the factory to match all domain names. The |domain_name| value
* will be ignored for non-standard schemes. If |scheme_name| is a built-in
* scheme and no handler is returned by |factory| then the built-in scheme
* handler factory will be called. If |scheme_name| is a custom scheme then
* also implement the CefApp::OnRegisterCustomSchemes() method in all
* processes. This function may be called multiple times to change or remove
* the factory that matches the specified |scheme_name| and optional
* |domain_name|. Returns false if an error occurs. This function may be
* called on any thread in the browser process.
*/
public boolean registerSchemeHandlerFactory(
String schemeName, String domainName, CefSchemeHandlerFactory factory) {
try {
return N_RegisterSchemeHandlerFactory(schemeName, domainName, factory);
} catch (Exception err) {
err.printStackTrace();
}
return false;
}
/**
* Clear all registered scheme handler factories. Returns false on error. This
* function may be called on any thread in the browser process.
*/
public boolean clearSchemeHandlerFactories() {
try {
return N_ClearSchemeHandlerFactories();
} catch (Exception err) {
err.printStackTrace();
}
return false;
}
/**
* This method is called by a CefClient if it was disposed. This causes
* CefApp to clean up its list of available client instances. If all clients
* are disposed, CefApp will be shutdown.
*
* @param client the disposed client.
*/
protected final synchronized void clientWasDisposed(CefClient client) {
clients_.remove(client);
if (clients_.isEmpty() && getState().compareTo(CefAppState.SHUTTING_DOWN) >= 0) {
// Shutdown native system.
shutdown();
}
}
/**
* Initialize the context.
*
* @return true on success.
*/
private final void initialize() {
String jcefPath = getJcefLibPath();
System.out.println("initialize on " + Thread.currentThread() + " with library path " + jcefPath);
CefSettings settings = settings_ != null ? settings_ : new CefSettings();
if (OS.isWindows()) {
Path jcefHelperPath = Paths.get(jcefPath, "jcef_helper.exe");
settings.browser_subprocess_path = jcefHelperPath.normalize().toAbsolutePath().toString();
} else if (OS.isMacintosh()) {
String basePath = Paths.get(jcefPath).getParent().getParent().toString();
settings.main_bundle_path = basePath;
settings.framework_dir_path = basePath
+ "/Contents/Frameworks/Chromium Embedded Framework.framework";
settings.locales_dir_path = basePath
+ "/Contents/Frameworks/Chromium Embedded Framework.framework/Resources";
settings.resources_dir_path = basePath
+ "/Contents/Frameworks/Chromium Embedded Framework.framework/Resources";
settings.browser_subprocess_path = basePath
+ "/Contents/Frameworks/jcef Helper.app/Contents/MacOS/jcef Helper";
} else if (OS.isLinux()) {
settings.resources_dir_path = jcefPath;
Path jcefHelperPath = Paths.get(jcefPath, "jcef_helper");
settings.browser_subprocess_path = jcefHelperPath.normalize().toAbsolutePath().toString();
Path localesPath = Paths.get(jcefPath, "locales");
settings.locales_dir_path = localesPath.normalize().toAbsolutePath().toString();
}
if (N_Initialize(appHandler_, settings)) setState(CefAppState.INITIALIZED);
}
/**
* This method is invoked by the native code (currently on Mac only) in case
* of a termination event (e.g. someone pressed CMD+Q).
*/
protected final void handleBeforeTerminate() {
System.out.println("Cmd+Q termination request.");
CefAppHandler handler =
(CefAppHandler) ((appHandler_ == null) ? this : appHandler_);
if (!handler.onBeforeTerminate()) dispose();
}
/**
* Shut down the context.
*/
private final void shutdown() {
System.out.println("shutdown on " + Thread.currentThread());
// Shutdown native CEF.
N_Shutdown();
setState(CefAppState.TERMINATED);
CefApp.self = null;
}
/**
* Perform a single message loop iteration. Used on all platforms except
* Windows with windowed rendering.
*/
public final void doMessageLoopWork(final long delay_ms) {
// Execute on the AWT event dispatching thread.
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (getState() == CefAppState.TERMINATED) return;
// The maximum number of milliseconds we're willing to wait between
// calls to DoMessageLoopWork().
final long kMaxTimerDelay = 1000 / 30; // 30fps
if (workTimer_ != null) {
workTimer_.stop();
workTimer_ = null;
}
if (delay_ms <= 0) {
// Execute the work immediately.
N_DoMessageLoopWork();
// Schedule more work later.
doMessageLoopWork(kMaxTimerDelay);
} else {
long timer_delay_ms = delay_ms;
// Never wait longer than the maximum allowed time.
if (timer_delay_ms > kMaxTimerDelay) timer_delay_ms = kMaxTimerDelay;
workTimer_ = new Timer((int) timer_delay_ms, new ActionListener() {
@Override
public void actionPerformed(ActionEvent evt) {
// Timer has timed out.
workTimer_.stop();
workTimer_ = null;
N_DoMessageLoopWork();
// Schedule more work later.
doMessageLoopWork(kMaxTimerDelay);
}
});
workTimer_.start();
}
}
});
}
/**
* This method must be called at the beginning of the main() method to perform platform-
* specific startup initialization. On Linux this initializes Xlib multithreading and on
* macOS this dynamically loads the CEF framework.
*
* @param args Command-line arguments massed to main().
* @return True on successful startup.
*/
public static final boolean startup(String[] args) {
String jcefPath = getJcefLibPath();
if (OS.isWindows()) {
System.load(jcefPath + "\\d3dcompiler_47.dll");
System.load(jcefPath + "\\libGLESv2.dll");
System.load(jcefPath + "\\libEGL.dll");
System.load(jcefPath + "\\chrome_elf.dll");
System.load(jcefPath + "\\libcef.dll");
System.load(jcefPath + "\\jcef.dll");
return true;
} else if (OS.isMacintosh()) {
System.load(jcefPath + "/libjcef.dylib");
return N_Startup(getCefFrameworkPath(args));
} else if (OS.isLinux()) {
System.load(jcefPath + "/libcef.so");
System.load(jcefPath + "/libjcef.so");
return N_Startup(null);
}
return false;
}
private static final String getJcefLibPath() {
Path runtimeDir = Paths.get("");
final Path jcefPath;
if (OS.isWindows() || OS.isLinux()) {
jcefPath = runtimeDir.resolve("jcef/cef_runtime");
} else if (OS.isMacintosh()) {
jcefPath = runtimeDir.resolve("jcef/jcef_app.app/Contents/Java");
} else {
jcefPath = null;
}
return jcefPath != null ? jcefPath.toAbsolutePath().toString() : null;
}
/**
* Get the path that contains the CEF Framework on macOS.
*
* @return The path to the CEF Framework.
*/
private static final String getCefFrameworkPath(String[] args) {
// Check for the path on the command-line.
String switchPrefix = "--framework-dir-path=";
for (String arg : args) {
if (arg.startsWith(switchPrefix)) {
return new File(arg.substring(switchPrefix.length())).getAbsolutePath();
}
}
// Determine the path relative to the JCEF lib location in the app bundle.
return new File(getJcefLibPath() + "/../Frameworks/Chromium Embedded Framework.framework")
.getAbsolutePath();
}
private final static native boolean N_Startup(String pathToCefFramework);
private final native boolean N_PreInitialize();
private final native boolean N_Initialize(CefAppHandler appHandler, CefSettings settings);
public final native void N_Shutdown();
public final native void N_DoMessageLoopWork();
private final native CefVersion N_GetVersion();
private final native boolean N_RegisterSchemeHandlerFactory(
String schemeName, String domainName, CefSchemeHandlerFactory factory);
private final native boolean N_ClearSchemeHandlerFactories();
}

View File

@@ -0,0 +1,869 @@
// Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef;
import org.cef.browser.CefBrowser;
import org.cef.browser.CefBrowserFactory;
import org.cef.browser.CefFrame;
import org.cef.browser.CefMessageRouter;
import org.cef.browser.CefRequestContext;
import org.cef.callback.CefAuthCallback;
import org.cef.callback.CefBeforeDownloadCallback;
import org.cef.callback.CefCallback;
import org.cef.callback.CefContextMenuParams;
import org.cef.callback.CefDownloadItem;
import org.cef.callback.CefDownloadItemCallback;
import org.cef.callback.CefDragData;
import org.cef.callback.CefFileDialogCallback;
import org.cef.callback.CefJSDialogCallback;
import org.cef.callback.CefMenuModel;
import org.cef.callback.CefPrintDialogCallback;
import org.cef.callback.CefPrintJobCallback;
import org.cef.handler.CefClientHandler;
import org.cef.handler.CefContextMenuHandler;
import org.cef.handler.CefDialogHandler;
import org.cef.handler.CefDisplayHandler;
import org.cef.handler.CefDownloadHandler;
import org.cef.handler.CefDragHandler;
import org.cef.handler.CefFocusHandler;
import org.cef.handler.CefJSDialogHandler;
import org.cef.handler.CefKeyboardHandler;
import org.cef.handler.CefLifeSpanHandler;
import org.cef.handler.CefLoadHandler;
import org.cef.handler.CefPrintHandler;
import org.cef.handler.CefRenderHandler;
import org.cef.handler.CefRequestHandler;
import org.cef.handler.CefResourceHandler;
import org.cef.handler.CefResourceRequestHandler;
import org.cef.handler.CefScreenInfo;
import org.cef.handler.CefWindowHandler;
import org.cef.misc.BoolRef;
import org.cef.misc.CefPrintSettings;
import org.cef.misc.StringRef;
import org.cef.network.CefRequest;
import org.cef.network.CefRequest.TransitionType;
import org.cef.network.CefResponse;
import org.cef.network.CefURLRequest;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FocusTraversalPolicy;
import java.awt.KeyboardFocusManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.HashMap;
import java.util.Vector;
import javax.swing.SwingUtilities;
/**
* Client that owns a browser and renderer.
*/
public class CefClient extends CefClientHandler
implements CefContextMenuHandler, CefDialogHandler, CefDisplayHandler, CefDownloadHandler,
CefDragHandler, CefFocusHandler, CefJSDialogHandler, CefKeyboardHandler,
CefLifeSpanHandler, CefLoadHandler, CefPrintHandler, CefRenderHandler,
CefRequestHandler, CefWindowHandler {
private HashMap<Integer, CefBrowser> browser_ = new HashMap<Integer, CefBrowser>();
private CefContextMenuHandler contextMenuHandler_ = null;
private CefDialogHandler dialogHandler_ = null;
private CefDisplayHandler displayHandler_ = null;
private CefDownloadHandler downloadHandler_ = null;
private CefDragHandler dragHandler_ = null;
private CefFocusHandler focusHandler_ = null;
private CefJSDialogHandler jsDialogHandler_ = null;
private CefKeyboardHandler keyboardHandler_ = null;
private CefLifeSpanHandler lifeSpanHandler_ = null;
private CefLoadHandler loadHandler_ = null;
private CefPrintHandler printHandler_ = null;
private CefRequestHandler requestHandler_ = null;
private boolean isDisposed_ = false;
private volatile CefBrowser focusedBrowser_ = null;
private final PropertyChangeListener propertyChangeListener = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (focusedBrowser_ != null) {
Component browserUI = focusedBrowser_.getUIComponent();
Object oldUI = evt.getOldValue();
if (isPartOf(oldUI, browserUI)) {
focusedBrowser_.setFocus(false);
focusedBrowser_ = null;
}
}
}
};
/**
* The CTOR is only accessible within this package.
* Use CefApp.createClient() to create an instance of
* this class.
* @see org.cef.CefApp.createClient()
*/
CefClient() throws UnsatisfiedLinkError {
super();
KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener(
propertyChangeListener);
}
private boolean isPartOf(Object obj, Component browserUI) {
if (obj == browserUI) return true;
if (obj instanceof Container) {
Component childs[] = ((Container) obj).getComponents();
for (Component child : childs) {
return isPartOf(child, browserUI);
}
}
return false;
}
@Override
public void dispose() {
isDisposed_ = true;
cleanupBrowser(-1);
}
// CefClientHandler
public CefBrowser createBrowser(
String url, boolean isOffscreenRendered, boolean isTransparent) {
return createBrowser(url, isOffscreenRendered, isTransparent, null);
}
public CefBrowser createBrowser(String url, boolean isOffscreenRendered, boolean isTransparent,
CefRequestContext context) {
if (isDisposed_)
throw new IllegalStateException("Can't create browser. CefClient is disposed");
return CefBrowserFactory.create(this, url, isOffscreenRendered, isTransparent, context);
}
@Override
protected CefBrowser getBrowser(int identifier) {
synchronized (browser_) {
return browser_.get(new Integer(identifier));
}
}
@Override
protected Object[] getAllBrowser() {
synchronized (browser_) {
return browser_.values().toArray();
}
}
@Override
protected CefContextMenuHandler getContextMenuHandler() {
return this;
}
@Override
protected CefDialogHandler getDialogHandler() {
return this;
};
@Override
protected CefDisplayHandler getDisplayHandler() {
return this;
}
@Override
protected CefDownloadHandler getDownloadHandler() {
return this;
}
@Override
protected CefDragHandler getDragHandler() {
return this;
}
@Override
protected CefFocusHandler getFocusHandler() {
return this;
}
@Override
protected CefJSDialogHandler getJSDialogHandler() {
return this;
}
@Override
protected CefKeyboardHandler getKeyboardHandler() {
return this;
}
@Override
protected CefLifeSpanHandler getLifeSpanHandler() {
return this;
}
@Override
protected CefLoadHandler getLoadHandler() {
return this;
}
@Override
protected CefPrintHandler getPrintHandler() {
return this;
}
@Override
protected CefRenderHandler getRenderHandler() {
return this;
}
@Override
protected CefRequestHandler getRequestHandler() {
return this;
}
@Override
protected CefWindowHandler getWindowHandler() {
return this;
}
// CefContextMenuHandler
public CefClient addContextMenuHandler(CefContextMenuHandler handler) {
if (contextMenuHandler_ == null) contextMenuHandler_ = handler;
return this;
}
public void removeContextMenuHandler() {
contextMenuHandler_ = null;
}
@Override
public void onBeforeContextMenu(
CefBrowser browser, CefFrame frame, CefContextMenuParams params, CefMenuModel model) {
if (contextMenuHandler_ != null && browser != null)
contextMenuHandler_.onBeforeContextMenu(browser, frame, params, model);
}
@Override
public boolean onContextMenuCommand(CefBrowser browser, CefFrame frame,
CefContextMenuParams params, int commandId, int eventFlags) {
if (contextMenuHandler_ != null && browser != null)
return contextMenuHandler_.onContextMenuCommand(
browser, frame, params, commandId, eventFlags);
return false;
}
@Override
public void onContextMenuDismissed(CefBrowser browser, CefFrame frame) {
if (contextMenuHandler_ != null && browser != null)
contextMenuHandler_.onContextMenuDismissed(browser, frame);
}
// CefDialogHandler
public CefClient addDialogHandler(CefDialogHandler handler) {
if (dialogHandler_ == null) dialogHandler_ = handler;
return this;
}
public void removeDialogHandler() {
dialogHandler_ = null;
}
@Override
public boolean onFileDialog(CefBrowser browser, FileDialogMode mode, String title,
String defaultFilePath, Vector<String> acceptFilters, int selectedAcceptFilter,
CefFileDialogCallback callback) {
if (dialogHandler_ != null && browser != null) {
return dialogHandler_.onFileDialog(browser, mode, title, defaultFilePath, acceptFilters,
selectedAcceptFilter, callback);
}
return false;
}
// CefDisplayHandler
public CefClient addDisplayHandler(CefDisplayHandler handler) {
if (displayHandler_ == null) displayHandler_ = handler;
return this;
}
public void removeDisplayHandler() {
displayHandler_ = null;
}
@Override
public void onAddressChange(CefBrowser browser, CefFrame frame, String url) {
if (displayHandler_ != null && browser != null)
displayHandler_.onAddressChange(browser, frame, url);
}
@Override
public void onTitleChange(CefBrowser browser, String title) {
if (displayHandler_ != null && browser != null)
displayHandler_.onTitleChange(browser, title);
}
@Override
public boolean onTooltip(CefBrowser browser, String text) {
if (displayHandler_ != null && browser != null) {
return displayHandler_.onTooltip(browser, text);
}
return false;
}
@Override
public void onStatusMessage(CefBrowser browser, String value) {
if (displayHandler_ != null && browser != null) {
displayHandler_.onStatusMessage(browser, value);
}
}
@Override
public boolean onConsoleMessage(CefBrowser browser, CefSettings.LogSeverity level,
String message, String source, int line) {
if (displayHandler_ != null && browser != null) {
return displayHandler_.onConsoleMessage(browser, level, message, source, line);
}
return false;
}
@Override
public boolean onCursorChange(CefBrowser browser, int cursorType) {
if (browser == null) {
return false;
}
if (displayHandler_ != null && displayHandler_.onCursorChange(browser, cursorType)) {
return true;
}
CefRenderHandler realHandler = browser.getRenderHandler();
if (realHandler != null) {
return realHandler.onCursorChange(browser, cursorType);
}
return false;
}
// CefDownloadHandler
public CefClient addDownloadHandler(CefDownloadHandler handler) {
if (downloadHandler_ == null) downloadHandler_ = handler;
return this;
}
public void removeDownloadHandler() {
downloadHandler_ = null;
}
@Override
public void onBeforeDownload(CefBrowser browser, CefDownloadItem downloadItem,
String suggestedName, CefBeforeDownloadCallback callback) {
if (downloadHandler_ != null && browser != null)
downloadHandler_.onBeforeDownload(browser, downloadItem, suggestedName, callback);
}
@Override
public void onDownloadUpdated(
CefBrowser browser, CefDownloadItem downloadItem, CefDownloadItemCallback callback) {
if (downloadHandler_ != null && browser != null)
downloadHandler_.onDownloadUpdated(browser, downloadItem, callback);
}
// CefDragHandler
public CefClient addDragHandler(CefDragHandler handler) {
if (dragHandler_ == null) dragHandler_ = handler;
return this;
}
public void removeDragHandler() {
dragHandler_ = null;
}
@Override
public boolean onDragEnter(CefBrowser browser, CefDragData dragData, int mask) {
if (dragHandler_ != null && browser != null)
return dragHandler_.onDragEnter(browser, dragData, mask);
return false;
}
// CefFocusHandler
public CefClient addFocusHandler(CefFocusHandler handler) {
if (focusHandler_ == null) focusHandler_ = handler;
return this;
}
public void removeFocusHandler() {
focusHandler_ = null;
}
@Override
public void onTakeFocus(CefBrowser browser, boolean next) {
if (browser == null) return;
browser.setFocus(false);
Container parent = browser.getUIComponent().getParent();
if (parent != null) {
FocusTraversalPolicy policy = null;
while (parent != null) {
policy = parent.getFocusTraversalPolicy();
if (policy != null) break;
parent = parent.getParent();
}
if (policy != null) {
Component nextComp = next
? policy.getComponentAfter(parent, browser.getUIComponent())
: policy.getComponentBefore(parent, browser.getUIComponent());
if (nextComp == null) {
policy.getDefaultComponent(parent).requestFocus();
} else {
nextComp.requestFocus();
}
}
}
focusedBrowser_ = null;
if (focusHandler_ != null) focusHandler_.onTakeFocus(browser, next);
}
@Override
public boolean onSetFocus(final CefBrowser browser, FocusSource source) {
if (browser == null) return false;
boolean alreadyHandled = false;
if (focusHandler_ != null) alreadyHandled = focusHandler_.onSetFocus(browser, source);
return alreadyHandled;
}
@Override
public void onGotFocus(CefBrowser browser) {
if (browser == null) return;
focusedBrowser_ = browser;
browser.setFocus(true);
if (focusHandler_ != null) focusHandler_.onGotFocus(browser);
}
// CefJSDialogHandler
public CefClient addJSDialogHandler(CefJSDialogHandler handler) {
if (jsDialogHandler_ == null) jsDialogHandler_ = handler;
return this;
}
public void removeJSDialogHandler() {
jsDialogHandler_ = null;
}
@Override
public boolean onJSDialog(CefBrowser browser, String origin_url, JSDialogType dialog_type,
String message_text, String default_prompt_text, CefJSDialogCallback callback,
BoolRef suppress_message) {
if (jsDialogHandler_ != null && browser != null)
return jsDialogHandler_.onJSDialog(browser, origin_url, dialog_type, message_text,
default_prompt_text, callback, suppress_message);
return false;
}
@Override
public boolean onBeforeUnloadDialog(CefBrowser browser, String message_text, boolean is_reload,
CefJSDialogCallback callback) {
if (jsDialogHandler_ != null && browser != null)
return jsDialogHandler_.onBeforeUnloadDialog(
browser, message_text, is_reload, callback);
return false;
}
@Override
public void onResetDialogState(CefBrowser browser) {
if (jsDialogHandler_ != null && browser != null)
jsDialogHandler_.onResetDialogState(browser);
}
@Override
public void onDialogClosed(CefBrowser browser) {
if (jsDialogHandler_ != null && browser != null) jsDialogHandler_.onDialogClosed(browser);
}
// CefKeyboardHandler
public CefClient addKeyboardHandler(CefKeyboardHandler handler) {
if (keyboardHandler_ == null) keyboardHandler_ = handler;
return this;
}
public void removeKeyboardHandler() {
keyboardHandler_ = null;
}
@Override
public boolean onPreKeyEvent(
CefBrowser browser, CefKeyEvent event, BoolRef is_keyboard_shortcut) {
if (keyboardHandler_ != null && browser != null)
return keyboardHandler_.onPreKeyEvent(browser, event, is_keyboard_shortcut);
return false;
}
@Override
public boolean onKeyEvent(CefBrowser browser, CefKeyEvent event) {
if (keyboardHandler_ != null && browser != null)
return keyboardHandler_.onKeyEvent(browser, event);
return false;
}
// CefLifeSpanHandler
public CefClient addLifeSpanHandler(CefLifeSpanHandler handler) {
if (lifeSpanHandler_ == null) lifeSpanHandler_ = handler;
return this;
}
public void removeLifeSpanHandler() {
lifeSpanHandler_ = null;
}
@Override
public boolean onBeforePopup(
CefBrowser browser, CefFrame frame, String target_url, String target_frame_name) {
if (isDisposed_) return true;
if (lifeSpanHandler_ != null && browser != null)
return lifeSpanHandler_.onBeforePopup(browser, frame, target_url, target_frame_name);
return false;
}
@Override
public void onAfterCreated(CefBrowser browser) {
if (browser == null) return;
// keep browser reference
Integer identifier = browser.getIdentifier();
synchronized (browser_) {
browser_.put(identifier, browser);
}
if (lifeSpanHandler_ != null) lifeSpanHandler_.onAfterCreated(browser);
}
@Override
public void onAfterParentChanged(CefBrowser browser) {
if (browser == null) return;
if (lifeSpanHandler_ != null) lifeSpanHandler_.onAfterParentChanged(browser);
}
@Override
public boolean doClose(CefBrowser browser) {
if (browser == null) return false;
if (lifeSpanHandler_ != null) return lifeSpanHandler_.doClose(browser);
return browser.doClose();
}
@Override
public void onBeforeClose(CefBrowser browser) {
if (browser == null) return;
if (lifeSpanHandler_ != null) lifeSpanHandler_.onBeforeClose(browser);
browser.onBeforeClose();
// remove browser reference
cleanupBrowser(browser.getIdentifier());
}
private void cleanupBrowser(int identifier) {
synchronized (browser_) {
if (identifier >= 0) {
// Remove the specific browser that closed.
browser_.remove(identifier);
} else if (!browser_.isEmpty()) {
// Close all browsers.
Collection<CefBrowser> browserList = browser_.values();
for (CefBrowser browser : browserList) {
browser.close(true);
}
return;
}
if (browser_.isEmpty() && isDisposed_) {
KeyboardFocusManager.getCurrentKeyboardFocusManager().removePropertyChangeListener(
propertyChangeListener);
removeContextMenuHandler(this);
removeDialogHandler(this);
removeDisplayHandler(this);
removeDownloadHandler(this);
removeDragHandler(this);
removeFocusHandler(this);
removeJSDialogHandler(this);
removeKeyboardHandler(this);
removeLifeSpanHandler(this);
removeLoadHandler(this);
removePrintHandler(this);
removeRenderHandler(this);
removeRequestHandler(this);
removeWindowHandler(this);
super.dispose();
CefApp.getInstance().clientWasDisposed(this);
}
}
}
// CefLoadHandler
public CefClient addLoadHandler(CefLoadHandler handler) {
if (loadHandler_ == null) loadHandler_ = handler;
return this;
}
public void removeLoadHandler() {
loadHandler_ = null;
}
@Override
public void onLoadingStateChange(
CefBrowser browser, boolean isLoading, boolean canGoBack, boolean canGoForward) {
if (loadHandler_ != null && browser != null)
loadHandler_.onLoadingStateChange(browser, isLoading, canGoBack, canGoForward);
}
@Override
public void onLoadStart(CefBrowser browser, CefFrame frame, TransitionType transitionType) {
if (loadHandler_ != null && browser != null)
loadHandler_.onLoadStart(browser, frame, transitionType);
}
@Override
public void onLoadEnd(CefBrowser browser, CefFrame frame, int httpStatusCode) {
if (loadHandler_ != null && browser != null)
loadHandler_.onLoadEnd(browser, frame, httpStatusCode);
}
@Override
public void onLoadError(CefBrowser browser, CefFrame frame, ErrorCode errorCode,
String errorText, String failedUrl) {
if (loadHandler_ != null && browser != null)
loadHandler_.onLoadError(browser, frame, errorCode, errorText, failedUrl);
}
// CefPrintHandler
public CefClient addPrintHandler(CefPrintHandler handler) {
if (printHandler_ == null) printHandler_ = handler;
return this;
}
public void removePrintHandler() {
printHandler_ = null;
}
@Override
public void onPrintStart(CefBrowser browser) {
if (printHandler_ != null && browser != null) printHandler_.onPrintStart(browser);
}
@Override
public void onPrintSettings(
CefBrowser browser, CefPrintSettings settings, boolean getDefaults) {
if (printHandler_ != null && browser != null)
printHandler_.onPrintSettings(browser, settings, getDefaults);
}
@Override
public boolean onPrintDialog(
CefBrowser browser, boolean hasSelection, CefPrintDialogCallback callback) {
if (printHandler_ != null && browser != null)
return printHandler_.onPrintDialog(browser, hasSelection, callback);
return false;
}
@Override
public boolean onPrintJob(CefBrowser browser, String documentName, String pdfFilePath,
CefPrintJobCallback callback) {
if (printHandler_ != null && browser != null)
return printHandler_.onPrintJob(browser, documentName, pdfFilePath, callback);
return false;
}
@Override
public void onPrintReset(CefBrowser browser) {
if (printHandler_ != null && browser != null) printHandler_.onPrintReset(browser);
}
@Override
public Dimension getPdfPaperSize(CefBrowser browser, int deviceUnitsPerInch) {
if (printHandler_ != null && browser != null)
return printHandler_.getPdfPaperSize(browser, deviceUnitsPerInch);
return null;
}
// CefMessageRouter
@Override
public synchronized void addMessageRouter(CefMessageRouter messageRouter) {
super.addMessageRouter(messageRouter);
}
@Override
public synchronized void removeMessageRouter(CefMessageRouter messageRouter) {
super.removeMessageRouter(messageRouter);
}
// CefRenderHandler
@Override
public Rectangle getViewRect(CefBrowser browser) {
if (browser == null) return new Rectangle(0, 0, 0, 0);
CefRenderHandler realHandler = browser.getRenderHandler();
if (realHandler != null) return realHandler.getViewRect(browser);
return new Rectangle(0, 0, 0, 0);
}
@Override
public Point getScreenPoint(CefBrowser browser, Point viewPoint) {
if (browser == null) return new Point(0, 0);
CefRenderHandler realHandler = browser.getRenderHandler();
if (realHandler != null) return realHandler.getScreenPoint(browser, viewPoint);
return new Point(0, 0);
}
@Override
public void onPopupShow(CefBrowser browser, boolean show) {
if (browser == null) return;
CefRenderHandler realHandler = browser.getRenderHandler();
if (realHandler != null) realHandler.onPopupShow(browser, show);
}
@Override
public void onPopupSize(CefBrowser browser, Rectangle size) {
if (browser == null) return;
CefRenderHandler realHandler = browser.getRenderHandler();
if (realHandler != null) realHandler.onPopupSize(browser, size);
}
@Override
public void onPaint(CefBrowser browser, boolean popup, Rectangle[] dirtyRects,
ByteBuffer buffer, int width, int height) {
if (browser == null) return;
CefRenderHandler realHandler = browser.getRenderHandler();
if (realHandler != null)
realHandler.onPaint(browser, popup, dirtyRects, buffer, width, height);
}
@Override
public boolean startDragging(CefBrowser browser, CefDragData dragData, int mask, int x, int y) {
if (browser == null) return false;
CefRenderHandler realHandler = browser.getRenderHandler();
if (realHandler != null) return realHandler.startDragging(browser, dragData, mask, x, y);
return false;
}
@Override
public void updateDragCursor(CefBrowser browser, int operation) {
if (browser == null) return;
CefRenderHandler realHandler = browser.getRenderHandler();
if (realHandler != null) realHandler.updateDragCursor(browser, operation);
}
// CefRequestHandler
public CefClient addRequestHandler(CefRequestHandler handler) {
if (requestHandler_ == null) requestHandler_ = handler;
return this;
}
public void removeRequestHandler() {
requestHandler_ = null;
}
@Override
public boolean onBeforeBrowse(CefBrowser browser, CefFrame frame, CefRequest request,
boolean user_gesture, boolean is_redirect) {
if (requestHandler_ != null && browser != null)
return requestHandler_.onBeforeBrowse(
browser, frame, request, user_gesture, is_redirect);
return false;
}
@Override
public boolean onOpenURLFromTab(
CefBrowser browser, CefFrame frame, String target_url, boolean user_gesture) {
if (isDisposed_) return true;
if (requestHandler_ != null && browser != null)
return requestHandler_.onOpenURLFromTab(browser, frame, target_url, user_gesture);
return false;
}
@Override
public CefResourceRequestHandler getResourceRequestHandler(CefBrowser browser, CefFrame frame,
CefRequest request, boolean isNavigation, boolean isDownload, String requestInitiator,
BoolRef disableDefaultHandling) {
if (requestHandler_ != null && browser != null) {
return requestHandler_.getResourceRequestHandler(browser, frame, request, isNavigation,
isDownload, requestInitiator, disableDefaultHandling);
}
return null;
}
@Override
public boolean getAuthCredentials(CefBrowser browser, String origin_url, boolean isProxy,
String host, int port, String realm, String scheme, CefAuthCallback callback) {
if (requestHandler_ != null && browser != null)
return requestHandler_.getAuthCredentials(
browser, origin_url, isProxy, host, port, realm, scheme, callback);
return false;
}
@Override
public boolean onQuotaRequest(
CefBrowser browser, String origin_url, long new_size, CefCallback callback) {
if (requestHandler_ != null && browser != null)
return requestHandler_.onQuotaRequest(browser, origin_url, new_size, callback);
return false;
}
@Override
public boolean onCertificateError(
CefBrowser browser, ErrorCode cert_error, String request_url, CefCallback callback) {
if (requestHandler_ != null)
return requestHandler_.onCertificateError(browser, cert_error, request_url, callback);
return false;
}
@Override
public void onRenderProcessTerminated(CefBrowser browser, TerminationStatus status) {
if (requestHandler_ != null) requestHandler_.onRenderProcessTerminated(browser, status);
}
// CefWindowHandler
@Override
public Rectangle getRect(CefBrowser browser) {
if (browser == null) return new Rectangle(0, 0, 0, 0);
CefWindowHandler realHandler = browser.getWindowHandler();
if (realHandler != null) return realHandler.getRect(browser);
return new Rectangle(0, 0, 0, 0);
}
@Override
public void onMouseEvent(
CefBrowser browser, int event, int screenX, int screenY, int modifier, int button) {
if (browser == null) return;
CefWindowHandler realHandler = browser.getWindowHandler();
if (realHandler != null)
realHandler.onMouseEvent(browser, event, screenX, screenY, modifier, button);
}
@Override
public boolean getScreenInfo(CefBrowser arg0, CefScreenInfo arg1) {
return false;
}
}

View File

@@ -0,0 +1,259 @@
// Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef;
/**
* Initialization settings. Specify NULL or 0 to get the recommended default
* values. Many of these and other settings can also configured using command-
* line switches.
*/
public class CefSettings {
/**
* Log severity levels.
*/
public enum LogSeverity {
/**
* Default logging (currently INFO logging).
*/
LOGSEVERITY_DEFAULT,
/**
* Verbose logging.
*/
LOGSEVERITY_VERBOSE,
/**
* INFO logging.
*/
LOGSEVERITY_INFO,
/**
* WARNING logging.
*/
LOGSEVERITY_WARNING,
/**
* ERROR logging.
*/
LOGSEVERITY_ERROR,
/**
* FATAL logging.
*/
LOGSEVERITY_FATAL,
/**
* Completely disable logging.
*/
LOGSEVERITY_DISABLE
}
/**
* 32-bit ARGB color value, not premultiplied. The color components are always
* in a known order. Equivalent to the SkColor type.
*/
public class ColorType {
private long color_value = 0;
private ColorType() {}
public ColorType(int alpha, int red, int green, int blue) {
color_value = (alpha << 24) | (red << 16) | (green << 8) | (blue << 0);
}
public long getColor() {
return color_value;
}
@Override
public ColorType clone() {
ColorType res = new ColorType();
res.color_value = this.color_value;
return res;
}
}
// MacOS specific settings
public String framework_dir_path = null;
public String main_bundle_path = null;
/**
* The path to a separate executable that will be launched for sub-processes.
* By default the browser process executable is used. See the comments on
* CefExecuteProcess() for details. Also configurable using the
* "browser-subprocess-path" command-line switch.
*/
public String browser_subprocess_path = null;
/**
* Set to true to enable windowless (off-screen) rendering support. Do not
* enable this value if the application does not use windowless rendering as
* it may reduce rendering performance on some systems.
*/
public boolean windowless_rendering_enabled = true;
/**
* Set to true to disable configuration of browser process features using
* standard CEF and Chromium command-line arguments. Configuration can still
* be specified using CEF data structures or via the
* CefApp::OnBeforeCommandLineProcessing() method.
*/
public boolean command_line_args_disabled = false;
/**
* The location where cache data will be stored on disk. If empty an in-memory
* cache will be used for some features and a temporary disk cache for others.
* HTML5 databases such as localStorage will only persist across sessions if a
* cache path is specified.
*/
public String cache_path = null;
/**
* To persist session cookies (cookies without an expiry date or validity
* interval) by default when using the global cookie manager set this value to
* true. Session cookies are generally intended to be transient and most Web
* browsers do not persist them. A |cache_path| value must also be specified to
* enable this feature. Also configurable using the "persist-session-cookies"
* command-line switch.
*/
public boolean persist_session_cookies = false;
/**
* Value that will be returned as the User-Agent HTTP header. If empty the
* default User-Agent string will be used. Also configurable using the
* "user-agent" command-line switch.
*/
public String user_agent = null;
/**
* Value that will be inserted as the product portion of the default
* User-Agent string. If empty the Chromium product version will be used. If
* |userAgent| is specified this value will be ignored. Also configurable
* using the "product-version" command-line switch.
*/
public String product_version = null;
/**
* The locale string that will be passed to Blink. If empty the default
* locale of "en-US" will be used. This value is ignored on Linux where locale
* is determined using environment variable parsing with the precedence order:
* LANGUAGE, LC_ALL, LC_MESSAGES and LANG. Also configurable using the "lang"
* command-line switch.
*/
public String locale = null;
/**
* The directory and file name to use for the debug log. If empty, the
* default name of "debug.log" will be used and the file will be written
* to the application directory. Also configurable using the "log-file"
* command-line switch.
*/
public String log_file = null;
/**
* The log severity. Only messages of this severity level or higher will be
* logged. Also configurable using the "log-severity" command-line switch with
* a value of "verbose", "info", "warning", "error", "error-report" or
* "disable".
*/
public LogSeverity log_severity = LogSeverity.LOGSEVERITY_DEFAULT;
/**
* Custom flags that will be used when initializing the V8 JavaScript engine.
* The consequences of using custom flags may not be well tested. Also
* configurable using the "js-flags" command-line switch.
*/
public String javascript_flags = null;
/**
* The fully qualified path for the resources directory. If this value is
* empty the cef.pak and/or devtools_resources.pak files must be located in
* the module directory on Windows/Linux or the app bundle Resources directory
* on Mac OS X. Also configurable using the "resources-dir-path" command-line
* switch.
*/
public String resources_dir_path = null;
/**
* The fully qualified path for the locales directory. If this value is empty
* the locales directory must be located in the module directory. This value
* is ignored on Mac OS X where pack files are always loaded from the app
* bundle Resources directory. Also configurable using the "locales-dir-path"
* command-line switch.
*/
public String locales_dir_path = null;
/**
* Set to true to disable loading of pack files for resources and locales.
* A resource bundle handler must be provided for the browser and render
* processes via CefApp::GetResourceBundleHandler() if loading of pack files
* is disabled. Also configurable using the "disable-pack-loading" command-
* line switch.
*/
public boolean pack_loading_disabled = false;
/**
* Set to a value between 1024 and 65535 to enable remote debugging on the
* specified port. For example, if 8080 is specified the remote debugging URL
* will be http: *localhost:8080. CEF can be remotely debugged from any CEF or
* Chrome browser window. Also configurable using the "remote-debugging-port"
* command-line switch.
*/
public int remote_debugging_port = 0;
/**
* The number of stack trace frames to capture for uncaught exceptions.
* Specify a positive value to enable the CefV8ContextHandler::
* OnUncaughtException() callback. Specify 0 (default value) and
* OnUncaughtException() will not be called. Also configurable using the
* "uncaught-exception-stack-size" command-line switch.
*/
public int uncaught_exception_stack_size = 0;
/**
* Set to true to ignore errors related to invalid SSL certificates.
* Enabling this setting can lead to potential security vulnerabilities like
* "man in the middle" attacks. Applications that load content from the
* internet should not enable this setting. Also configurable using the
* "ignore-certificate-errors" command-line switch.
*/
public boolean ignore_certificate_errors = false;
/**
* Opaque background color used for accelerated content. By default the
* background color will be white. Only the RGB compontents of the specified
* value will be used. The alpha component must greater than 0 to enable use
* of the background color but will be otherwise ignored.
*/
public ColorType background_color = null;
public CefSettings() {}
@Override
public CefSettings clone() {
CefSettings tmp = new CefSettings();
tmp.framework_dir_path = framework_dir_path; // for MacOS
tmp.main_bundle_path = main_bundle_path; // for MacOS
tmp.browser_subprocess_path = browser_subprocess_path;
tmp.windowless_rendering_enabled = windowless_rendering_enabled;
tmp.command_line_args_disabled = command_line_args_disabled;
tmp.cache_path = cache_path;
tmp.persist_session_cookies = persist_session_cookies;
tmp.user_agent = user_agent;
tmp.product_version = product_version;
tmp.locale = locale;
tmp.log_file = log_file;
tmp.log_severity = log_severity;
tmp.javascript_flags = javascript_flags;
tmp.resources_dir_path = resources_dir_path;
tmp.locales_dir_path = locales_dir_path;
tmp.pack_loading_disabled = pack_loading_disabled;
tmp.remote_debugging_port = remote_debugging_port;
tmp.uncaught_exception_stack_size = uncaught_exception_stack_size;
tmp.ignore_certificate_errors = ignore_certificate_errors;
if (background_color != null) tmp.background_color = background_color.clone();
return tmp;
}
}

View File

@@ -0,0 +1,44 @@
// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef;
public class OS {
private static enum OSType {
OSUndefined,
OSLinux,
OSWindows,
OSMacintosh,
OSUnknown,
}
;
private static OSType osType = OSType.OSUndefined;
public static final boolean isWindows() {
return getOSType() == OSType.OSWindows;
}
public static final boolean isMacintosh() {
return getOSType() == OSType.OSMacintosh;
}
public static final boolean isLinux() {
return getOSType() == OSType.OSLinux;
}
private static final OSType getOSType() {
if (osType == OSType.OSUndefined) {
String os = System.getProperty("os.name").toLowerCase();
if (os.startsWith("windows"))
osType = OSType.OSWindows;
else if (os.startsWith("linux"))
osType = OSType.OSLinux;
else if (os.startsWith("mac"))
osType = OSType.OSMacintosh;
else
osType = OSType.OSUnknown;
}
return osType;
}
}

View File

@@ -0,0 +1,38 @@
// Copyright (c) 2020 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef;
/**
* To allow customization of System.load() calls by supplying a different
* implementation. You'll want to call <code>setLoader</code> with your custom
* implementation before calling into any other CEF classes which then in turn
* will start triggering libraries to be loaded at runtime.
*/
public class SystemBootstrap {
/**
* Simple interface for how a library by name should be loaded.
*/
static public interface Loader { public void loadLibrary(String libname); }
/**
* Default implementation is to call System.loadLibrary
*/
static private Loader loader_ = new Loader() {
@Override
public void loadLibrary(String libname) {
System.loadLibrary(libname);
}
};
static public void setLoader(Loader loader) {
if (loader == null) {
throw new NullPointerException("Loader cannot be null");
}
loader_ = loader;
}
static public void loadLibrary(String libname) {
loader_.loadLibrary(libname);
}
}

View File

@@ -0,0 +1,383 @@
// Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef.browser;
import org.cef.CefClient;
import org.cef.callback.CefPdfPrintCallback;
import org.cef.callback.CefRunFileDialogCallback;
import org.cef.callback.CefStringVisitor;
import org.cef.handler.CefDialogHandler.FileDialogMode;
import org.cef.handler.CefRenderHandler;
import org.cef.handler.CefWindowHandler;
import org.cef.misc.CefPdfPrintSettings;
import org.cef.network.CefRequest;
import java.awt.Component;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.util.Vector;
import java.util.concurrent.CompletableFuture;
/**
* Interface representing a browser.
*/
public interface CefBrowser {
/**
* Call to immediately create the underlying browser object. By default the
* browser object will be created when the parent container is displayed for
* the first time.
*/
public void createImmediately();
/**
* Get the underlying UI component (e.g. java.awt.Canvas).
* @return The underlying UI component.
*/
public Component getUIComponent();
/**
* Get the client associated with this browser.
* @return The browser client.
*/
public CefClient getClient();
/**
* Get an implementation of CefRenderHandler if any.
* @return An instance of CefRenderHandler or null.
*/
public CefRenderHandler getRenderHandler();
/**
* Get an implementation of CefWindowHandler if any.
* @return An instance of CefWindowHandler or null.
*/
public CefWindowHandler getWindowHandler();
//
// The following methods are forwarded to CefBrowser.
//
/**
* Tests if the browser can navigate backwards.
* @return true if the browser can navigate backwards.
*/
public boolean canGoBack();
/**
* Go back.
*/
public void goBack();
/**
* Tests if the browser can navigate forwards.
* @return true if the browser can navigate forwards.
*/
public boolean canGoForward();
/**
* Go forward.
*/
public void goForward();
/**
* Tests if the browser is currently loading.
* @return true if the browser is currently loading.
*/
public boolean isLoading();
/**
* Reload the current page.
*/
public void reload();
/**
* Reload the current page ignoring any cached data.
*/
public void reloadIgnoreCache();
/**
* Stop loading the page.
*/
public void stopLoad();
/**
* Returns the unique browser identifier.
* @return The browser identifier
*/
public int getIdentifier();
/**
* Returns the main (top-level) frame for the browser window.
* @return The main frame
*/
public CefFrame getMainFrame();
/**
* Returns the focused frame for the browser window.
* @return The focused frame
*/
public CefFrame getFocusedFrame();
/**
* Returns the frame with the specified identifier, or NULL if not found.
* @param identifier The unique frame identifier
* @return The frame or NULL if not found
*/
public CefFrame getFrame(long identifier);
/**
* Returns the frame with the specified name, or NULL if not found.
* @param name The specified name
* @return The frame or NULL if not found
*/
public CefFrame getFrame(String name);
/**
* Returns the identifiers of all existing frames.
* @return All identifiers of existing frames.
*/
public Vector<Long> getFrameIdentifiers();
/**
* Returns the names of all existing frames.
* @return The names of all existing frames.
*/
public Vector<String> getFrameNames();
/**
* Returns the number of frames that currently exist.
* @return The number of frames
*/
public int getFrameCount();
/**
* Tests if the window is a popup window.
* @return true if the window is a popup window.
*/
public boolean isPopup();
/**
* Tests if a document has been loaded in the browser.
* @return true if a document has been loaded in the browser.
*/
public boolean hasDocument();
//
// The following methods are forwarded to the mainFrame.
//
/**
* Save this frame's HTML source to a temporary file and open it in the
* default text viewing application. This method can only be called from the
* browser process.
*/
public void viewSource();
/**
* Retrieve this frame's HTML source as a string sent to the specified
* visitor.
*
* @param visitor
*/
public void getSource(CefStringVisitor visitor);
/**
* Retrieve this frame's display text as a string sent to the specified
* visitor.
*
* @param visitor
*/
public void getText(CefStringVisitor visitor);
/**
* Load the request represented by the request object.
*
* @param request The request object.
*/
public void loadRequest(CefRequest request);
/**
* Load the specified URL in the main frame.
* @param url The URL to load.
*/
public void loadURL(String url);
/**
* Execute a string of JavaScript code in this frame. The url
* parameter is the URL where the script in question can be found, if any.
* The renderer may request this URL to show the developer the source of the
* error. The line parameter is the base line number to use for error
* reporting.
*
* @param code The code to be executed.
* @param url The URL where the script in question can be found.
* @param line The base line number to use for error reporting.
*/
public void executeJavaScript(String code, String url, int line);
/**
* Emits the URL currently loaded in this frame.
* @return the URL currently loaded in this frame.
*/
public String getURL();
// The following methods are forwarded to CefBrowserHost.
/**
* Request that the browser close.
* @param force force the close.
*/
public void close(boolean force);
/**
* Allow the browser to close.
*/
public void setCloseAllowed();
/**
* Called from CefClient.doClose.
*/
public boolean doClose();
/**
* Called from CefClient.onBeforeClose.
*/
public void onBeforeClose();
/**
* Set or remove keyboard focus to/from the browser window.
* @param enable set to true to give the focus to the browser
**/
public void setFocus(boolean enable);
/**
* Set whether the window containing the browser is visible
* (minimized/unminimized, app hidden/unhidden, etc). Only used on Mac OS X.
* @param visible
*/
public void setWindowVisibility(boolean visible);
/**
* Get the current zoom level. The default zoom level is 0.0.
* @return The current zoom level.
*/
public double getZoomLevel();
/**
* Change the zoom level to the specified value. Specify 0.0 to reset the
* zoom level.
*
* @param zoomLevel The zoom level to be set.
*/
public void setZoomLevel(double zoomLevel);
/**
* Call to run a file chooser dialog. Only a single file chooser dialog may be
* pending at any given time.The dialog will be initiated asynchronously on
* the UI thread.
*
* @param mode represents the type of dialog to display.
* @param title to be used for the dialog and may be empty to show the
* default title ("Open" or "Save" depending on the mode).
* @param defaultFilePath is the path with optional directory and/or file name
* component that should be initially selected in the dialog.
* @param acceptFilters are used to restrict the selectable file types and may
* any combination of (a) valid lower-cased MIME types (e.g. "text/*" or
* "image/*"), (b) individual file extensions (e.g. ".txt" or ".png"), or (c)
* combined description and file extension delimited using "|" and ";" (e.g.
* "Image Types|.png;.gif;.jpg").
* @param selectedAcceptFilter is the 0-based index of the filter that should
* be selected by default.
* @param callback will be executed after the dialog is dismissed or
* immediately if another dialog is already pending.
*/
public void runFileDialog(FileDialogMode mode, String title, String defaultFilePath,
Vector<String> acceptFilters, int selectedAcceptFilter,
CefRunFileDialogCallback callback);
/**
* Download the file at url using CefDownloadHandler.
*
* @param url URL to download that file.
*/
public void startDownload(String url);
/**
* Print the current browser contents.
*/
public void print();
/**
* Print the current browser contents to a PDF.
*
* @param path The path of the file to write to (will be overwritten if it
* already exists). Cannot be null.
* @param settings The pdf print settings to use. If null then defaults
* will be used.
* @param callback Called when the pdf print job has completed.
*/
public void printToPDF(String path, CefPdfPrintSettings settings, CefPdfPrintCallback callback);
/**
* Search for some kind of text on the page.
*
* @param searchText to be searched for.
* @param forward indicates whether to search forward or backward within the page.
* @param matchCase indicates whether the search should be case-sensitive.
* @param findNext indicates whether this is the first request or a follow-up.
*/
public void find(String searchText, boolean forward, boolean matchCase, boolean findNext);
/**
* Cancel all searches that are currently going on.
* @param clearSelection Set to true to reset selection.
*/
public void stopFinding(boolean clearSelection);
/**
* Get an instance of the dev tools to be displayed in its own window or to be
* embedded within your UI. Only one instance per browser is available.
*/
public CefBrowser getDevTools();
/**
* Get an instance of the dev tools to be displayed in its own window or to be
* embedded within your UI. Only one instance per browser is available.
*
* @param inspectAt a position in the UI which should be inspected.
*/
public CefBrowser getDevTools(Point inspectAt);
/**
* If a misspelled word is currently selected in an editable node calling
* this method will replace it with the specified |word|.
*
* @param word replace selected word with this word.
*/
public void replaceMisspelling(String word);
/**
* Captures a screenshot-like image of the currently displayed content and returns it.
* <p>
* If executed on the AWT Event Thread, this returns an immediately resolved {@link
* java.util.concurrent.CompletableFuture}. If executed from another thread, the {@link
* java.util.concurrent.CompletableFuture} returned is resolved as soon as the screenshot
* has been taken (which must happen on the event thread).
* <p>
* The generated screenshot can either be returned as-is, containing all natively-rendered
* pixels, or it can be scaled to match the logical width and height of the window.
* This distinction is only relevant in case of differing logical and physical resolutions
* (for example with HiDPI/Retina displays, which have a scaling factor of for example 2
* between the logical width of a window (ex. 400px) and the actual number of pixels in
* each row (ex. 800px with a scaling factor of 2)).
*
* @param nativeResolution whether to return an image at full native resolution (true)
* or a scaled-down version whose width and height are equal to the logical size
* of the screenshotted browser window
* @return the screenshot image
* @throws UnsupportedOperationException if not supported
*/
public CompletableFuture<BufferedImage> createScreenshot(boolean nativeResolution);
}

View File

@@ -0,0 +1,18 @@
// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef.browser;
import org.cef.CefClient;
/**
* Creates a new instance of CefBrowser according the passed values
*/
public class CefBrowserFactory {
public static CefBrowser create(CefClient client, String url, boolean isOffscreenRendered,
boolean isTransparent, CefRequestContext context) {
if (isOffscreenRendered) return new CefBrowserOsr(client, url, isTransparent, context);
return new CefBrowserWr(client, url, context);
}
}

View File

@@ -0,0 +1,346 @@
// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef.browser;
import net.montoyo.mcef.MCEF;
import net.montoyo.mcef.api.IBrowser;
import net.montoyo.mcef.api.IStringVisitor;
import net.montoyo.mcef.client.ClientProxy;
import net.montoyo.mcef.client.StringVisitor;
import net.montoyo.mcef.utilities.Log;
import org.apache.commons.lang3.NotImplementedException;
import org.cef.CefClient;
import org.cef.callback.CefDragData;
import org.cef.handler.CefRenderHandler;
import org.cef.handler.CefScreenInfo;
import org.lwjgl.BufferUtils;
import org.lwjgl.input.Keyboard;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.image.BufferedImage;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
/**
* This class represents an off-screen rendered browser.
* The visibility of this class is "package". To create a new
* CefBrowser instance, please use CefBrowserFactory.
*/
public class CefBrowserOsr extends CefBrowser_N implements CefRenderHandler, IBrowser {
private CefRenderer renderer_;
private long window_handle_ = 0;
private boolean justCreated_ = false;
private Rectangle browser_rect_ = new Rectangle(0, 0, 1, 1); // Work around CEF issue #1437.
private Point screenPoint_ = new Point(0, 0);
private double scaleFactor_ = 1.0;
private int depth = 32;
private int depth_per_component = 8;
private boolean isTransparent_;
private final Component dc_ = new Component() {
@Override
public Point getLocationOnScreen() {
return new Point(0, 0);
}
};
private MouseEvent lastMouseEvent = new MouseEvent(dc_, MouseEvent.MOUSE_MOVED, 0, 0, 0, 0, 0, false);
public static boolean CLEANUP = true;
CefBrowserOsr(CefClient client, String url, boolean transparent, CefRequestContext context) {
this(client, url, transparent, context, null, null);
}
private CefBrowserOsr(CefClient client, String url, boolean transparent,
CefRequestContext context, CefBrowserOsr parent, Point inspectAt) {
super(client, url, context, parent, inspectAt);
isTransparent_ = transparent;
renderer_ = new CefRenderer(transparent);
}
@Override
public void createImmediately() {
justCreated_ = true;
// Create the browser immediately.
createBrowserIfRequired(false);
}
@Override
public Component getUIComponent() {
return dc_;
}
@Override
public CefRenderHandler getRenderHandler() {
return this;
}
@Override
protected CefBrowser_N createDevToolsBrowser(CefClient client, String url,
CefRequestContext context, CefBrowser_N parent, Point inspectAt) {
return new CefBrowserOsr(
client, url, isTransparent_, context, (CefBrowserOsr) this, inspectAt);
}
@Override
public Rectangle getViewRect(CefBrowser browser) {
return browser_rect_;
}
@Override
public Point getScreenPoint(CefBrowser browser, Point viewPoint) {
Point screenPoint = new Point(screenPoint_);
screenPoint.translate(viewPoint.x, viewPoint.y);
return screenPoint;
}
@Override
public void onPopupShow(CefBrowser browser, boolean show) {
if (!show) {
renderer_.clearPopupRects();
invalidate();
}
}
@Override
public void onPopupSize(CefBrowser browser, Rectangle size) {
renderer_.onPopupSize(size);
}
@Override
public void close() {
if (CLEANUP) {
((ClientProxy) MCEF.PROXY).removeBrowser(this);
renderer_.cleanup();
}
super.close(true); //true to ignore confirmation popups
}
@Override
public void resize(int width, int height) {
browser_rect_.setBounds(0, 0, width, height);
dc_.setBounds(browser_rect_);
dc_.setVisible(true);
wasResized(width, height);
}
@Override
public void draw(double x1, double y1, double x2, double y2) {
renderer_.render(x1, y1, x2, y2);
}
@Override
public int getTextureID() {
return renderer_.texture_id_[0];
}
@Override
public void injectMouseMove(int x, int y, int mods, boolean left) {
//FIXME: 'left' is not used as it causes bugs since MCEF 1.11
MouseEvent ev = new MouseEvent(dc_, MouseEvent.MOUSE_MOVED, 0, mods, x, y, 0, false);
lastMouseEvent = ev;
sendMouseEvent(ev);
}
@Override
public void injectMouseButton(int x, int y, int mods, int btn, boolean pressed, int ccnt) {
MouseEvent ev = new MouseEvent(dc_, pressed ? MouseEvent.MOUSE_PRESSED : MouseEvent.MOUSE_RELEASED, 0, mods, x, y, ccnt, false, btn);
sendMouseEvent(ev);
}
@Override
public void injectKeyTyped(char c, int mods) {
KeyEvent ev = new KeyEvent(dc_, KeyEvent.KEY_TYPED, 0, mods, 0, c);
sendKeyEvent(ev);
}
public static int remapKeycode(int kc, char c) {
switch (kc) {
case Keyboard.KEY_BACK:
return KeyEvent.VK_BACK_SPACE;
case Keyboard.KEY_DELETE:
return KeyEvent.VK_DELETE;
case Keyboard.KEY_DOWN:
return KeyEvent.VK_DOWN;
case Keyboard.KEY_RETURN:
return KeyEvent.VK_ENTER;
case Keyboard.KEY_ESCAPE:
return KeyEvent.VK_ESCAPE;
case Keyboard.KEY_LEFT:
return KeyEvent.VK_LEFT;
case Keyboard.KEY_RIGHT:
return KeyEvent.VK_RIGHT;
case Keyboard.KEY_TAB:
return KeyEvent.VK_TAB;
case Keyboard.KEY_UP:
return KeyEvent.VK_UP;
case Keyboard.KEY_PRIOR:
return KeyEvent.VK_PAGE_UP;
case Keyboard.KEY_NEXT:
return KeyEvent.VK_PAGE_DOWN;
case Keyboard.KEY_END:
return Keyboard.KEY_END;
case Keyboard.KEY_HOME:
return Keyboard.KEY_HOME;
default:
return c;
}
}
@Override
public void injectKeyPressedByKeyCode(int keyCode, char c, int mods) {
if (c != '\0') {
synchronized (WORST_HACK) {
WORST_HACK.put(keyCode, c);
}
}
KeyEvent ev = new KeyEvent(dc_, KeyEvent.KEY_PRESSED, 0, mods, remapKeycode(keyCode, c), c);
sendKeyEvent(ev);
}
private static final Map<Integer, Character> WORST_HACK = new HashMap<>();
@Override
public void injectKeyReleasedByKeyCode(int keyCode, char c, int mods) {
if (c == '\0') {
synchronized (WORST_HACK) {
c = WORST_HACK.getOrDefault(keyCode, '\0');
}
}
KeyEvent ev = new KeyEvent(dc_, KeyEvent.KEY_RELEASED, 0, mods, remapKeycode(keyCode, c), c);
sendKeyEvent(ev);
}
@Override
public void injectMouseWheel(int x, int y, int mods, int amount, int rot) {
MouseWheelEvent ev = new MouseWheelEvent(dc_, MouseEvent.MOUSE_WHEEL, 0, mods, x, y, 0, false, MouseWheelEvent.WHEEL_UNIT_SCROLL, amount, rot);
sendMouseWheelEvent(ev);
}
@Override
public void runJS(String script, String frame) {
executeJavaScript(script, frame, 0);
}
@Override
public void visitSource(IStringVisitor isv) {
getSource(new StringVisitor(isv));
}
@Override
public boolean isPageLoading() {
return isLoading();
}
private static class PaintData {
private ByteBuffer buffer;
private int width;
private int height;
private Rectangle[] dirtyRects;
private boolean hasFrame;
private boolean fullReRender;
}
private final PaintData paintData = new PaintData();
@Override
public void onPaint(CefBrowser browser, boolean popup, Rectangle[] dirtyRects,
ByteBuffer buffer, int width, int height) {
if (popup) {
return;
}
final int size = (width * height) << 2;
synchronized (paintData) {
if (buffer.limit() > size)
Log.warning("Skipping MCEF browser frame, data is too heavy"); //TODO: Don't spam
else {
if (paintData.hasFrame) //The previous frame was not uploaded to GL texture, so we skip it and render this on instead
paintData.fullReRender = true;
if (paintData.buffer == null || size != paintData.buffer.capacity()) //This only happens when the browser gets resized
paintData.buffer = BufferUtils.createByteBuffer(size);
paintData.buffer.position(0);
paintData.buffer.limit(buffer.limit());
buffer.position(0);
paintData.buffer.put(buffer);
paintData.buffer.position(0);
paintData.width = width;
paintData.height = height;
paintData.dirtyRects = dirtyRects;
paintData.hasFrame = true;
}
}
}
public void mcefUpdate() {
synchronized (paintData) {
if (paintData.hasFrame) {
renderer_.onPaint(false, paintData.dirtyRects, paintData.buffer, paintData.width, paintData.height, paintData.fullReRender);
paintData.hasFrame = false;
paintData.fullReRender = false;
}
}
//So sadly this is the only way I could get around the "youtube not rendering video if the mouse doesn't move bug"
//Even the test browser from the original JCEF library doesn't fix this...
//What I hope, however, is that it doesn't redraw the entire browser... otherwise I could just call "invalidate"
sendMouseEvent(lastMouseEvent);
}
@Override
public boolean onCursorChange(CefBrowser browser, final int cursorType) {
return true;
}
@Override
public boolean startDragging(CefBrowser browser, CefDragData dragData, int mask, int x, int y) {
// TODO(JCEF) Prepared for DnD support using OSR mode.
return false;
}
@Override
public void updateDragCursor(CefBrowser browser, int operation) {
// TODO(JCEF) Prepared for DnD support using OSR mode.
}
private void createBrowserIfRequired(boolean hasParent) {
if (getNativeRef("CefBrowser") == 0) {
if (getParentBrowser() != null) {
createDevTools(getParentBrowser(), getClient(), 0, true, isTransparent_,
null, getInspectAt());
} else {
createBrowser(getClient(), 0, getUrl(), true, isTransparent_, null,
getRequestContext());
}
} else {
// OSR windows cannot be reparented after creation.
setFocus(true);
}
}
@Override
public boolean getScreenInfo(CefBrowser browser, CefScreenInfo screenInfo) {
screenInfo.Set(scaleFactor_, depth, depth_per_component, false, browser_rect_.getBounds(),
browser_rect_.getBounds());
return true;
}
@Override
public CompletableFuture<BufferedImage> createScreenshot(boolean nativeResolution) {
throw new NotImplementedException("createScreenshot not implemented on MCEF");
}
}

View File

@@ -0,0 +1,20 @@
// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef.browser;
import java.awt.Component;
/**
* Interface representing system dependent methods for the browser.
*/
public interface CefBrowserWindow {
/**
* Get the window handle for the given UI object.
*
* @param comp a UI component
* @return a window pointer if any
*/
public long getWindowHandle(Component comp);
}

View File

@@ -0,0 +1,424 @@
// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef.browser;
import org.cef.CefClient;
import org.cef.OS;
import org.cef.handler.CefWindowHandler;
import org.cef.handler.CefWindowHandlerAdapter;
import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.HierarchyBoundsListener;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.image.BufferedImage;
import java.util.Date;
import java.util.concurrent.CompletableFuture;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.MenuSelectionManager;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.ToolTipManager;
/**
* This class represents a windowed rendered browser.
* The visibility of this class is "package". To create a new
* CefBrowser instance, please use CefBrowserFactory.
*/
class CefBrowserWr extends CefBrowser_N {
private Canvas canvas_ = null;
private Component component_ = null;
private Rectangle content_rect_ = new Rectangle(0, 0, 0, 0);
private long window_handle_ = 0;
private boolean justCreated_ = false;
private double scaleFactor_ = 1.0;
private Timer delayedUpdate_ = new Timer(100, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (isClosed()) return;
boolean hasCreatedUI = createBrowserIfRequired(true);
if (hasCreatedUI) {
delayedUpdate_.restart();
} else {
// If on Mac, this is needed due to the quirk described below
// (in org.cef.browser.CefBrowserWr.CefBrowserWr(...).new JPanel()
// {...}.paint(Graphics)). If on Linux, this is needed to invoke an
// XMoveResizeWindow call shortly after the UI was created. That seems to be
// necessary to actually get a windowed renderer to display something.
if (OS.isMacintosh() || OS.isLinux()) doUpdate();
}
}
});
}
});
private CefWindowHandlerAdapter win_handler_ = new CefWindowHandlerAdapter() {
private Point lastPos = new Point(-1, -1);
private long[] nextClick = new long[MouseInfo.getNumberOfButtons()];
private int[] clickCnt = new int[MouseInfo.getNumberOfButtons()];
@Override
public Rectangle getRect(CefBrowser browser) {
synchronized (content_rect_) {
return content_rect_;
}
}
@Override
public void onMouseEvent(CefBrowser browser, int event, final int screenX,
final int screenY, final int modifier, final int button) {
final Point pt = new Point(screenX, screenY);
if (event == MouseEvent.MOUSE_MOVED) {
// Remove mouse-moved events if the position of the cursor hasn't
// changed.
if (pt.equals(lastPos)) return;
lastPos = pt;
// Change mouse-moved event to mouse-dragged event if the left mouse
// button is pressed.
if ((modifier & MouseEvent.BUTTON1_DOWN_MASK) != 0)
event = MouseEvent.MOUSE_DRAGGED;
}
final int finalEvent = event;
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
// Send mouse event to the root UI component instead to the browser UI.
// Otherwise no mouse-entered and no mouse-exited events would be fired.
Component parent = SwingUtilities.getRoot(component_);
if (parent == null) {
return;
}
SwingUtilities.convertPointFromScreen(pt, parent);
int clickCnt = 0;
long now = new Date().getTime();
if (finalEvent == MouseEvent.MOUSE_WHEEL) {
int scrollType = MouseWheelEvent.WHEEL_UNIT_SCROLL;
int rotation = button > 0 ? 1 : -1;
component_.dispatchEvent(new MouseWheelEvent(parent, finalEvent, now,
modifier, pt.x, pt.y, 0, false, scrollType, 3, rotation));
} else {
clickCnt = getClickCount(finalEvent, button);
component_.dispatchEvent(new MouseEvent(parent, finalEvent, now, modifier,
pt.x, pt.y, screenX, screenY, clickCnt, false, button));
}
// Always fire a mouse-clicked event after a mouse-released event.
if (finalEvent == MouseEvent.MOUSE_RELEASED) {
component_.dispatchEvent(
new MouseEvent(parent, MouseEvent.MOUSE_CLICKED, now, modifier,
pt.x, pt.y, screenX, screenY, clickCnt, false, button));
}
}
});
}
public int getClickCount(int event, int button) {
// avoid exceptions by using modulo
int idx = button % nextClick.length;
switch (event) {
case MouseEvent.MOUSE_PRESSED:
long currTime = new Date().getTime();
if (currTime > nextClick[idx]) {
nextClick[idx] = currTime
+ (Integer) Toolkit.getDefaultToolkit().getDesktopProperty(
"awt.multiClickInterval");
clickCnt[idx] = 1;
} else {
clickCnt[idx]++;
}
// FALL THRU
case MouseEvent.MOUSE_RELEASED:
return clickCnt[idx];
default:
return 0;
}
}
};
CefBrowserWr(CefClient client, String url, CefRequestContext context) {
this(client, url, context, null, null);
}
@SuppressWarnings("serial")
private CefBrowserWr(CefClient client, String url, CefRequestContext context,
CefBrowserWr parent, Point inspectAt) {
super(client, url, context, parent, inspectAt);
delayedUpdate_.setRepeats(false);
// Disabling lightweight of popup menu is required because
// otherwise it will be displayed behind the content of component_
JPopupMenu.setDefaultLightWeightPopupEnabled(false);
ToolTipManager.sharedInstance().setLightWeightPopupEnabled(false);
// We're using a JComponent instead of a Canvas now because the
// JComponent has clipping informations, which aren't accessible for Canvas.
component_ = new JPanel(new BorderLayout()) {
private boolean removed_ = true;
@Override
public void setBounds(int x, int y, int width, int height) {
super.setBounds(x, y, width, height);
wasResized((int) (width * scaleFactor_), (int) (height * scaleFactor_));
}
@Override
public void setBounds(Rectangle r) {
setBounds(r.x, r.y, r.width, r.height);
}
@Override
public void setSize(int width, int height) {
super.setSize(width, height);
wasResized((int) (width * scaleFactor_), (int) (height * scaleFactor_));
}
@Override
public void setSize(Dimension d) {
setSize(d.width, d.height);
}
@Override
public void paint(Graphics g) {
// If the user resizes the UI component, the new size and clipping
// informations are forwarded to the native code.
// But on Mac the last resize information doesn't resize the native UI
// accurately (sometimes the native UI is too small). An easy way to
// solve this, is to send the last Update-Information again. Therefore
// we're setting up a delayedUpdate timer which is reset each time
// paint is called. This prevents the us of sending the UI update too
// often.
if (g instanceof Graphics2D) {
scaleFactor_ = ((Graphics2D) g).getTransform().getScaleX();
}
doUpdate();
delayedUpdate_.restart();
}
@Override
public void addNotify() {
super.addNotify();
if (removed_) {
setParent(getWindowHandle(this), canvas_);
removed_ = false;
}
}
@Override
public void removeNotify() {
if (!removed_) {
if (!isClosed()) {
setParent(0, null);
}
removed_ = true;
}
super.removeNotify();
}
};
// On windows we have to use a Canvas because its a heavyweight component
// and we need its native HWND as parent for the browser UI. The same
// technique is used on Linux as well.
if (OS.isWindows() || OS.isLinux()) {
canvas_ = new Canvas();
((JPanel) component_).add(canvas_, BorderLayout.CENTER);
}
// Initial minimal size of the component. Otherwise the UI won't work
// accordingly in panes like JSplitPane.
component_.setMinimumSize(new Dimension(0, 0));
component_.setFocusable(true);
component_.addFocusListener(new FocusListener() {
@Override
public void focusLost(FocusEvent e) {
setFocus(false);
}
@Override
public void focusGained(FocusEvent e) {
// Dismiss any Java menus that are currently displayed.
MenuSelectionManager.defaultManager().clearSelectedPath();
setFocus(true);
}
});
component_.addHierarchyBoundsListener(new HierarchyBoundsListener() {
@Override
public void ancestorResized(HierarchyEvent e) {
doUpdate();
}
@Override
public void ancestorMoved(HierarchyEvent e) {
doUpdate();
notifyMoveOrResizeStarted();
}
});
component_.addHierarchyListener(new HierarchyListener() {
@Override
public void hierarchyChanged(HierarchyEvent e) {
if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) {
setWindowVisibility(e.getChanged().isVisible());
}
}
});
}
@Override
public void createImmediately() {
justCreated_ = true;
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
// Create the browser immediately. It will be parented to the Java
// window once it becomes available.
createBrowserIfRequired(false);
}
});
}
@Override
public Component getUIComponent() {
return component_;
}
@Override
public CefWindowHandler getWindowHandler() {
return win_handler_;
}
@Override
protected CefBrowser_N createDevToolsBrowser(CefClient client, String url,
CefRequestContext context, CefBrowser_N parent, Point inspectAt) {
return new CefBrowserWr(client, url, context, (CefBrowserWr) this, inspectAt);
}
private synchronized long getWindowHandle() {
if (window_handle_ == 0 && OS.isMacintosh()) {
window_handle_ = getWindowHandle(component_);
}
return window_handle_;
}
private static long getWindowHandle(Component component) {
if (OS.isMacintosh()) {
try {
Class<?> cls = Class.forName("org.cef.browser.mac.CefBrowserWindowMac");
CefBrowserWindow browserWindow = (CefBrowserWindow) cls.newInstance();
if (browserWindow != null) {
return browserWindow.getWindowHandle(component);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return 0;
}
private void doUpdate() {
if (isClosed()) return;
Rectangle vr = ((JPanel) component_).getVisibleRect();
Rectangle clipping = new Rectangle((int) (vr.getX() * scaleFactor_),
(int) (vr.getY() * scaleFactor_), (int) (vr.getWidth() * scaleFactor_),
(int) (vr.getHeight() * scaleFactor_));
if (OS.isMacintosh()) {
Container parent = component_.getParent();
Point contentPos = component_.getLocation();
while (parent != null) {
Container next = parent.getParent();
if (next != null && next instanceof Window) break;
Point parentPos = parent.getLocation();
contentPos.translate(parentPos.x, parentPos.y);
parent = next;
}
contentPos.translate(clipping.x, clipping.y);
Point browserPos = clipping.getLocation();
browserPos.x *= -1;
browserPos.y *= -1;
synchronized (content_rect_) {
content_rect_ = new Rectangle(contentPos, clipping.getSize());
Rectangle browserRect = new Rectangle(browserPos, component_.getSize());
updateUI(content_rect_, browserRect);
}
} else {
synchronized (content_rect_) {
Rectangle bounds = component_.getBounds();
content_rect_ = new Rectangle((int) (bounds.getX() * scaleFactor_),
(int) (bounds.getY() * scaleFactor_),
(int) (bounds.getWidth() * scaleFactor_),
(int) (bounds.getHeight() * scaleFactor_));
updateUI(clipping, content_rect_);
}
}
}
private boolean createBrowserIfRequired(boolean hasParent) {
if (isClosed()) return false;
long windowHandle = 0;
Component canvas = null;
if (hasParent) {
windowHandle = getWindowHandle();
canvas = (OS.isWindows() || OS.isLinux()) ? canvas_ : component_;
}
if (getNativeRef("CefBrowser") == 0) {
if (getParentBrowser() != null) {
createDevTools(getParentBrowser(), getClient(), windowHandle, false, false, canvas,
getInspectAt());
return true;
} else {
createBrowser(getClient(), windowHandle, getUrl(), false, false, canvas,
getRequestContext());
return true;
}
} else if (hasParent && justCreated_) {
setParent(windowHandle, canvas);
setFocus(true);
justCreated_ = false;
}
return false;
}
@Override
public CompletableFuture<BufferedImage> createScreenshot(boolean nativeResolution) {
throw new UnsupportedOperationException("Unsupported for windowed rendering");
}
}

View File

@@ -0,0 +1,817 @@
// Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef.browser;
import org.cef.CefClient;
import org.cef.browser.CefRequestContext;
import org.cef.callback.CefDragData;
import org.cef.callback.CefNativeAdapter;
import org.cef.callback.CefPdfPrintCallback;
import org.cef.callback.CefRunFileDialogCallback;
import org.cef.callback.CefStringVisitor;
import org.cef.handler.CefClientHandler;
import org.cef.handler.CefDialogHandler.FileDialogMode;
import org.cef.handler.CefRenderHandler;
import org.cef.handler.CefWindowHandler;
import org.cef.misc.CefPdfPrintSettings;
import org.cef.network.CefRequest;
import java.awt.Canvas;
import java.awt.Component;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.WindowEvent;
import java.util.Vector;
import javax.swing.SwingUtilities;
/**
* This class represents all methods which are connected to the
* native counterpart CEF.
* The visibility of this class is "package". To create a new
* CefBrowser instance, please use CefBrowserFactory.
*/
abstract class CefBrowser_N extends CefNativeAdapter implements CefBrowser {
private volatile boolean isPending_ = false;
private final CefClient client_;
private final String url_;
private final CefRequestContext request_context_;
private volatile CefBrowser_N parent_ = null;
private volatile Point inspectAt_ = null;
private volatile CefBrowser_N devTools_ = null;
private boolean closeAllowed_ = false;
private volatile boolean isClosed_ = false;
private volatile boolean isClosing_ = false;
protected CefBrowser_N(CefClient client, String url, CefRequestContext context,
CefBrowser_N parent, Point inspectAt) {
client_ = client;
url_ = url;
request_context_ = context;
parent_ = parent;
inspectAt_ = inspectAt;
}
protected String getUrl() {
return url_;
}
protected CefRequestContext getRequestContext() {
return request_context_;
}
protected CefBrowser_N getParentBrowser() {
return parent_;
}
protected Point getInspectAt() {
return inspectAt_;
}
protected boolean isClosed() {
return isClosed_;
}
@Override
public CefClient getClient() {
return client_;
}
@Override
public CefRenderHandler getRenderHandler() {
return null;
}
@Override
public CefWindowHandler getWindowHandler() {
return null;
}
@Override
public synchronized void setCloseAllowed() {
closeAllowed_ = true;
}
@Override
public synchronized boolean doClose() {
if (closeAllowed_) {
// Allow the close to proceed.
return false;
}
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
// Trigger close of the parent window.
Component parent = SwingUtilities.getRoot(getUIComponent());
if (parent != null) {
parent.dispatchEvent(
new WindowEvent((Window) parent, WindowEvent.WINDOW_CLOSING));
}
}
});
// Cancel the close.
return true;
}
@Override
public synchronized void onBeforeClose() {
isClosed_ = true;
if (request_context_ != null) request_context_.dispose();
if (parent_ != null) {
parent_.closeDevTools();
parent_.devTools_ = null;
parent_ = null;
}
}
@Override
public CefBrowser getDevTools() {
return getDevTools(null);
}
@Override
public synchronized CefBrowser getDevTools(Point inspectAt) {
if (devTools_ == null) {
devTools_ = createDevToolsBrowser(client_, url_, request_context_, this, inspectAt);
}
return devTools_;
}
protected abstract CefBrowser_N createDevToolsBrowser(CefClient client, String url,
CefRequestContext context, CefBrowser_N parent, Point inspectAt);
/**
* Create a new browser.
*/
protected void createBrowser(CefClientHandler clientHandler, long windowHandle, String url,
boolean osr, boolean transparent, Component canvas, CefRequestContext context) {
if (getNativeRef("CefBrowser") == 0 && !isPending_) {
try {
N_CreateBrowser(
clientHandler, windowHandle, url, osr, transparent, canvas, context);
} catch (UnsatisfiedLinkError err) {
err.printStackTrace();
}
}
}
/**
* Called async from the (native) main UI thread.
*/
private void notifyBrowserCreated() {
isPending_ = true;
}
/**
* Create a new browser as dev tools
*/
protected final void createDevTools(CefBrowser_N parent, CefClientHandler clientHandler,
long windowHandle, boolean osr, boolean transparent, Component canvas,
Point inspectAt) {
if (getNativeRef("CefBrowser") == 0 && !isPending_) {
try {
isPending_ = N_CreateDevTools(
parent, clientHandler, windowHandle, osr, transparent, canvas, inspectAt);
} catch (UnsatisfiedLinkError err) {
err.printStackTrace();
}
}
}
/**
* Returns the native window handle for the specified native surface handle.
*/
protected final long getWindowHandle(long surfaceHandle) {
try {
return N_GetWindowHandle(surfaceHandle);
} catch (UnsatisfiedLinkError err) {
err.printStackTrace();
}
return 0;
}
@Override
protected void finalize() throws Throwable {
close(true);
super.finalize();
}
@Override
public boolean canGoBack() {
try {
return N_CanGoBack();
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return false;
}
@Override
public void goBack() {
try {
N_GoBack();
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
@Override
public boolean canGoForward() {
try {
return N_CanGoForward();
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return false;
}
@Override
public void goForward() {
try {
N_GoForward();
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
@Override
public boolean isLoading() {
try {
return N_IsLoading();
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return false;
}
@Override
public void reload() {
try {
N_Reload();
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
@Override
public void reloadIgnoreCache() {
try {
N_ReloadIgnoreCache();
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
@Override
public void stopLoad() {
try {
N_StopLoad();
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
@Override
public int getIdentifier() {
try {
return N_GetIdentifier();
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
return -1;
}
}
@Override
public CefFrame getMainFrame() {
try {
return N_GetMainFrame();
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
return null;
}
}
@Override
public CefFrame getFocusedFrame() {
try {
return N_GetFocusedFrame();
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
return null;
}
}
@Override
public CefFrame getFrame(long identifier) {
try {
return N_GetFrame(identifier);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
return null;
}
}
@Override
public CefFrame getFrame(String name) {
try {
return N_GetFrame2(name);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
return null;
}
}
@Override
public Vector<Long> getFrameIdentifiers() {
try {
return N_GetFrameIdentifiers();
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
return null;
}
}
@Override
public Vector<String> getFrameNames() {
try {
return N_GetFrameNames();
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
return null;
}
}
@Override
public int getFrameCount() {
try {
return N_GetFrameCount();
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
return -1;
}
}
@Override
public boolean isPopup() {
try {
return N_IsPopup();
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return false;
}
@Override
public boolean hasDocument() {
try {
return N_HasDocument();
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return false;
}
public void viewSource() {
try {
N_ViewSource();
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
public void getSource(CefStringVisitor visitor) {
try {
N_GetSource(visitor);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
public void getText(CefStringVisitor visitor) {
try {
N_GetText(visitor);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
@Override
public void loadRequest(CefRequest request) {
try {
N_LoadRequest(request);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
@Override
public void loadURL(String url) {
try {
N_LoadURL(url);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
@Override
public void executeJavaScript(String code, String url, int line) {
try {
N_ExecuteJavaScript(code, url, line);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
@Override
public String getURL() {
try {
return N_GetURL();
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return "";
}
@Override
public void close(boolean force) {
if (isClosing_ || isClosed_) return;
if (force) isClosing_ = true;
try {
N_Close(force);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
@Override
public void setFocus(boolean enable) {
try {
N_SetFocus(enable);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
@Override
public void setWindowVisibility(boolean visible) {
try {
N_SetWindowVisibility(visible);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
@Override
public double getZoomLevel() {
try {
return N_GetZoomLevel();
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return 0.0;
}
@Override
public void setZoomLevel(double zoomLevel) {
try {
N_SetZoomLevel(zoomLevel);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
@Override
public void runFileDialog(FileDialogMode mode, String title, String defaultFilePath,
Vector<String> acceptFilters, int selectedAcceptFilter,
CefRunFileDialogCallback callback) {
try {
N_RunFileDialog(
mode, title, defaultFilePath, acceptFilters, selectedAcceptFilter, callback);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
@Override
public void startDownload(String url) {
try {
N_StartDownload(url);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
@Override
public void print() {
try {
N_Print();
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
@Override
public void printToPDF(
String path, CefPdfPrintSettings settings, CefPdfPrintCallback callback) {
if (path == null || path.isEmpty()) {
throw new IllegalArgumentException("path was null or empty");
}
try {
N_PrintToPDF(path, settings, callback);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
@Override
public void find(String searchText, boolean forward, boolean matchCase, boolean findNext) {
try {
N_Find(searchText, forward, matchCase, findNext);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
@Override
public void stopFinding(boolean clearSelection) {
try {
N_StopFinding(clearSelection);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
protected final void closeDevTools() {
try {
N_CloseDevTools();
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
@Override
public void replaceMisspelling(String word) {
try {
N_ReplaceMisspelling(word);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
/**
* Notify that the browser was resized.
* @param width The new width of the browser
* @param height The new height of the browser
*/
protected final void wasResized(int width, int height) {
try {
N_WasResized(width, height);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
/**
* Invalidate the UI.
*/
protected final void invalidate() {
try {
N_Invalidate();
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
/**
* Send a key event.
* @param e The event to send.
*/
protected final void sendKeyEvent(KeyEvent e) {
try {
N_SendKeyEvent(e);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
/**
* Send a mouse event.
* @param e The event to send.
*/
protected final void sendMouseEvent(MouseEvent e) {
try {
N_SendMouseEvent(e);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
/**
* Send a mouse wheel event.
* @param e The event to send.
*/
protected final void sendMouseWheelEvent(MouseWheelEvent e) {
try {
N_SendMouseWheelEvent(e);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
/**
* Call this method when the user drags the mouse into the web view (before
* calling DragTargetDragOver/DragTargetLeave/DragTargetDrop).
* |drag_data| should not contain file contents as this type of data is not
* allowed to be dragged into the web view. File contents can be removed using
* CefDragData::ResetFileContents (for example, if |drag_data| comes from
* CefRenderHandler::StartDragging).
* This method is only used when window rendering is disabled.
*/
protected final void dragTargetDragEnter(
CefDragData dragData, Point pos, int modifiers, int allowedOps) {
try {
N_DragTargetDragEnter(dragData, pos, modifiers, allowedOps);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
/**
* Call this method each time the mouse is moved across the web view during
* a drag operation (after calling DragTargetDragEnter and before calling
* DragTargetDragLeave/DragTargetDrop).
* This method is only used when window rendering is disabled.
*/
protected final void dragTargetDragOver(Point pos, int modifiers, int allowedOps) {
try {
N_DragTargetDragOver(pos, modifiers, allowedOps);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
/**
* Call this method when the user drags the mouse out of the web view (after
* calling DragTargetDragEnter).
* This method is only used when window rendering is disabled.
*/
protected final void dragTargetDragLeave() {
try {
N_DragTargetDragLeave();
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
/**
* Call this method when the user completes the drag operation by dropping
* the object onto the web view (after calling DragTargetDragEnter).
* The object being dropped is |drag_data|, given as an argument to
* the previous DragTargetDragEnter call.
* This method is only used when window rendering is disabled.
*/
protected final void dragTargetDrop(Point pos, int modifiers) {
try {
N_DragTargetDrop(pos, modifiers);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
/**
* Call this method when the drag operation started by a
* CefRenderHandler.startDragging call has ended either in a drop or
* by being cancelled. |x| and |y| are mouse coordinates relative to the
* upper-left corner of the view. If the web view is both the drag source
* and the drag target then all DragTarget* methods should be called before
* DragSource* methods.
* This method is only used when window rendering is disabled.
*/
protected final void dragSourceEndedAt(Point pos, int operation) {
try {
N_DragSourceEndedAt(pos, operation);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
/**
* Call this method when the drag operation started by a
* CefRenderHandler.startDragging call has completed. This method may be
* called immediately without first calling DragSourceEndedAt to cancel a
* drag operation. If the web view is both the drag source and the drag
* target then all DragTarget* methods should be called before DragSource*
* methods.
* This method is only used when window rendering is disabled.
*/
protected final void dragSourceSystemDragEnded() {
try {
N_DragSourceSystemDragEnded();
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
protected final void updateUI(Rectangle contentRect, Rectangle browserRect) {
try {
N_UpdateUI(contentRect, browserRect);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
protected final void setParent(long windowHandle, Component canvas) {
if (isClosing_ || isClosed_) return;
try {
N_SetParent(windowHandle, canvas);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
/**
* Call this method if the browser frame was moved.
* This fixes positioning of select popups and dismissal on window move/resize.
*/
protected final void notifyMoveOrResizeStarted() {
try {
N_NotifyMoveOrResizeStarted();
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
private final native boolean N_CreateBrowser(CefClientHandler clientHandler, long windowHandle,
String url, boolean osr, boolean transparent, Component canvas,
CefRequestContext context);
private final native boolean N_CreateDevTools(CefBrowser parent, CefClientHandler clientHandler,
long windowHandle, boolean osr, boolean transparent, Component canvas, Point inspectAt);
private final native long N_GetWindowHandle(long surfaceHandle);
private final native boolean N_CanGoBack();
private final native void N_GoBack();
private final native boolean N_CanGoForward();
private final native void N_GoForward();
private final native boolean N_IsLoading();
private final native void N_Reload();
private final native void N_ReloadIgnoreCache();
private final native void N_StopLoad();
private final native int N_GetIdentifier();
private final native CefFrame N_GetMainFrame();
private final native CefFrame N_GetFocusedFrame();
private final native CefFrame N_GetFrame(long identifier);
private final native CefFrame N_GetFrame2(String name);
private final native Vector<Long> N_GetFrameIdentifiers();
private final native Vector<String> N_GetFrameNames();
private final native int N_GetFrameCount();
private final native boolean N_IsPopup();
private final native boolean N_HasDocument();
private final native void N_ViewSource();
private final native void N_GetSource(CefStringVisitor visitor);
private final native void N_GetText(CefStringVisitor visitor);
private final native void N_LoadRequest(CefRequest request);
private final native void N_LoadURL(String url);
private final native void N_ExecuteJavaScript(String code, String url, int line);
private final native String N_GetURL();
private final native void N_Close(boolean force);
private final native void N_SetFocus(boolean enable);
private final native void N_SetWindowVisibility(boolean visible);
private final native double N_GetZoomLevel();
private final native void N_SetZoomLevel(double zoomLevel);
private final native void N_RunFileDialog(FileDialogMode mode, String title,
String defaultFilePath, Vector<String> acceptFilters, int selectedAcceptFilter,
CefRunFileDialogCallback callback);
private final native void N_StartDownload(String url);
private final native void N_Print();
private final native void N_PrintToPDF(
String path, CefPdfPrintSettings settings, CefPdfPrintCallback callback);
private final native void N_Find(
String searchText, boolean forward, boolean matchCase, boolean findNext);
private final native void N_StopFinding(boolean clearSelection);
private final native void N_CloseDevTools();
private final native void N_ReplaceMisspelling(String word);
private final native void N_WasResized(int width, int height);
private final native void N_Invalidate();
private final native void N_SendKeyEvent(KeyEvent e);
private final native void N_SendMouseEvent(MouseEvent e);
private final native void N_SendMouseWheelEvent(MouseWheelEvent e);
private final native void N_DragTargetDragEnter(
CefDragData dragData, Point pos, int modifiers, int allowed_ops);
private final native void N_DragTargetDragOver(Point pos, int modifiers, int allowed_ops);
private final native void N_DragTargetDragLeave();
private final native void N_DragTargetDrop(Point pos, int modifiers);
private final native void N_DragSourceEndedAt(Point pos, int operation);
private final native void N_DragSourceSystemDragEnded();
private final native void N_UpdateUI(Rectangle contentRect, Rectangle browserRect);
private final native void N_SetParent(long windowHandle, Component canvas);
private final native void N_NotifyMoveOrResizeStarted();
}

View File

@@ -0,0 +1,127 @@
// Copyright (c) 2019 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef.browser;
import org.cef.callback.CefDragData;
import org.cef.misc.EventFlags;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.io.File;
import java.util.List;
class CefDropTargetListener implements DropTargetListener {
private CefBrowser_N browser_;
private CefDragData dragData_ = null;
private int dragOperations_ = CefDragData.DragOperations.DRAG_OPERATION_COPY;
private int dragModifiers_ = EventFlags.EVENTFLAG_NONE;
private int acceptOperations_ = DnDConstants.ACTION_COPY;
CefDropTargetListener(CefBrowser_N browser) {
browser_ = browser;
}
@Override
public void dragEnter(DropTargetDragEvent event) {
CreateDragData(event);
browser_.dragTargetDragEnter(
dragData_, event.getLocation(), dragModifiers_, dragOperations_);
}
@Override
public void dragExit(DropTargetEvent event) {
AssertDragData();
browser_.dragTargetDragLeave();
ClearDragData();
}
@Override
public void dragOver(DropTargetDragEvent event) {
AssertDragData();
browser_.dragTargetDragOver(event.getLocation(), dragModifiers_, dragOperations_);
}
@Override
public void dropActionChanged(DropTargetDragEvent event) {
AssertDragData();
acceptOperations_ = event.getDropAction();
switch (acceptOperations_) {
case DnDConstants.ACTION_LINK:
dragOperations_ = CefDragData.DragOperations.DRAG_OPERATION_LINK;
dragModifiers_ =
EventFlags.EVENTFLAG_CONTROL_DOWN | EventFlags.EVENTFLAG_SHIFT_DOWN;
break;
case DnDConstants.ACTION_COPY:
dragOperations_ = CefDragData.DragOperations.DRAG_OPERATION_COPY;
dragModifiers_ = EventFlags.EVENTFLAG_CONTROL_DOWN;
break;
case DnDConstants.ACTION_MOVE:
dragOperations_ = CefDragData.DragOperations.DRAG_OPERATION_MOVE;
dragModifiers_ = EventFlags.EVENTFLAG_SHIFT_DOWN;
break;
case DnDConstants.ACTION_NONE:
// The user did not select an action, so use COPY as the default.
dragOperations_ = CefDragData.DragOperations.DRAG_OPERATION_COPY;
dragModifiers_ = EventFlags.EVENTFLAG_NONE;
acceptOperations_ = DnDConstants.ACTION_COPY;
break;
}
}
@Override
public void drop(DropTargetDropEvent event) {
AssertDragData();
browser_.dragTargetDrop(event.getLocation(), dragModifiers_);
event.acceptDrop(acceptOperations_);
event.dropComplete(true);
ClearDragData();
}
private void CreateDragData(DropTargetDragEvent event) {
assert dragData_ == null;
dragData_ = createDragData(event);
dropActionChanged(event);
}
private void AssertDragData() {
assert dragData_ != null;
}
private void ClearDragData() {
dragData_ = null;
}
private static CefDragData createDragData(DropTargetDragEvent event) {
CefDragData dragData = CefDragData.create();
Transferable transferable = event.getTransferable();
DataFlavor[] flavors = transferable.getTransferDataFlavors();
for (DataFlavor flavor : flavors) {
try {
if (flavor.isFlavorTextType()) {
Object ob = transferable.getTransferData(flavor);
if (!(ob instanceof String)) continue;
dragData.setFragmentText((String) ob);
} else if (flavor.isFlavorJavaFileListType()) {
List<File> files = (List<File>) transferable.getTransferData(flavor);
for (File file : files) {
dragData.addFile(file.getPath(), file.getName());
}
}
} catch (Exception e) {
// Data is no longer available or of unsupported flavor.
e.printStackTrace();
}
}
return dragData;
}
}

View File

@@ -0,0 +1,101 @@
// Copyright (c) 2017 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef.browser;
/**
* Interface representing a frame.
*/
public interface CefFrame {
/**
* Removes the native reference from an unused object.
*/
void dispose();
/**
* Returns the globally unique identifier for this frame or < 0 if the
* underlying frame does not yet exist.
* @return The frame identifier
*/
long getIdentifier();
/**
* Emits the URL currently loaded in this frame.
* @return the URL currently loaded in this frame.
*/
String getURL();
/**
* Returns the name for this frame. If the frame has an assigned name (for
* example, set via the iframe "name" attribute) then that value will be
* returned. Otherwise a unique name will be constructed based on the frame
* parent hierarchy. The main (top-level) frame will always have an empty name
* value.
* @return The frame name
*/
String getName();
/**
* Returns true if this is the main (top-level) frame.
* @return True if this frame is top-level otherwise false.
*/
boolean isMain();
/**
* True if this object is currently attached to a valid frame.
* @return True if valid otherwise false.
*/
boolean isValid();
/**
* Returns true if this is the focused frame.
* @return True if valid otherwise false.
*/
boolean isFocused();
/**
* Returns the parent of this frame or NULL if this is the main (top-level)
* frame.
* @return The parent frame or NULL if this is the main frame
*/
CefFrame getParent();
/**
* Execute a string of JavaScript code in this frame. The url
* parameter is the URL where the script in question can be found, if any.
* The renderer may request this URL to show the developer the source of the
* error. The line parameter is the base line number to use for error
* reporting.
*
* @param code The code to be executed.
* @param url The URL where the script in question can be found.
* @param line The base line number to use for error reporting.
*/
public void executeJavaScript(String code, String url, int line);
/**
* Execute undo in this frame.
*/
public void undo();
/**
* Execute redo in this frame.
*/
public void redo();
/**
* Execute cut in this frame.
*/
public void cut();
/**
* Execute copy in this frame.
*/
public void copy();
/**
* Execute paste in this frame.
*/
public void paste();
}

View File

@@ -0,0 +1,165 @@
// Copyright (c) 2017 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef.browser;
import org.cef.callback.CefNativeAdapter;
/**
* This class represents all methods which are connected to the
* native counterpart CEF.
* The visibility of this class is "package".
*/
class CefFrame_N extends CefNativeAdapter implements CefFrame {
CefFrame_N() {}
@Override
protected void finalize() throws Throwable {
dispose();
super.finalize();
}
@Override
public void dispose() {
try {
N_Dispose(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
@Override
public long getIdentifier() {
try {
return N_GetIdentifier(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
return -1;
}
}
@Override
public String getURL() {
try {
return N_GetURL(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
return null;
}
}
@Override
public String getName() {
try {
return N_GetName(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
return null;
}
}
@Override
public boolean isMain() {
try {
return N_IsMain(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
return false;
}
}
@Override
public boolean isValid() {
try {
return N_IsValid(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
return false;
}
}
@Override
public boolean isFocused() {
try {
return N_IsFocused(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
return false;
}
}
@Override
public CefFrame getParent() {
try {
return N_GetParent(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
return null;
}
}
@Override
public void executeJavaScript(String code, String url, int line) {
try {
N_ExecuteJavaScript(getNativeRef(null), code, url, line);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
public void undo() {
try {
N_Undo(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
public void redo() {
try {
N_Redo(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
public void cut() {
try {
N_Cut(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
public void copy() {
try {
N_Copy(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
public void paste() {
try {
N_Paste(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
private final native void N_Dispose(long self);
private final native long N_GetIdentifier(long self);
private final native String N_GetURL(long self);
private final native String N_GetName(long self);
private final native boolean N_IsMain(long self);
private final native boolean N_IsValid(long self);
private final native boolean N_IsFocused(long self);
private final native CefFrame N_GetParent(long self);
private final native void N_ExecuteJavaScript(long self, String code, String url, int line);
private final native void N_Undo(long self);
private final native void N_Redo(long self);
private final native void N_Cut(long self);
private final native void N_Copy(long self);
private final native void N_Paste(long self);
}

View File

@@ -0,0 +1,261 @@
// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef.browser;
import org.cef.handler.CefMessageRouterHandler;
/**
* The below classes implement support for routing aynchronous messages between
* JavaScript running in the renderer process and C++ running in the browser
* process. An application interacts with the router by passing it data from
* standard CEF C++ callbacks (OnBeforeBrowse, OnProcessMessageRecieved,
* OnContextCreated, etc). The renderer-side router supports generic JavaScript
* callback registration and execution while the browser-side router supports
* application-specific logic via one or more application-provided Handler
* instances.
*
* The renderer-side router implementation exposes a query function and a cancel
* function via the JavaScript 'window' object:
*
* // Create and send a new query.
* var request_id = window.cefQuery({
* request: 'my_request',
* persistent: false,
* onSuccess: function(response) {},
* onFailure: function(error_code, error_message) {}
* });
*
* // Optionally cancel the query.
* window.cefQueryCancel(request_id);
*
* When |window.cefQuery| is executed the request is sent asynchronously to one
* or more C++ Handler objects registered in the browser process. Each C++
* Handler can choose to either handle or ignore the query in the
* Handler::OnQuery callback. If a Handler chooses to handle the query then it
* should execute Callback::Success when a response is available or
* Callback::Failure if an error occurs. This will result in asynchronous
* execution of the associated JavaScript callback in the renderer process. Any
* queries unhandled by C++ code in the browser process will be automatically
* canceled and the associated JavaScript onFailure callback will be executed
* with an error code of -1.
*
* Queries can be either persistent or non-persistent. If the query is
* persistent than the callbacks will remain registered until one of the
* following conditions are met:
*
* A. The query is canceled in JavaScript using the |window.cefQueryCancel|
* function.
* B. The query is canceled in C++ code using the Callback::Failure function.
* C. The context associated with the query is released due to browser
* destruction, navigation or renderer process termination.
*
* If the query is non-persistent then the registration will be removed after
* the JavaScript callback is executed a single time. If a query is canceled for
* a reason other than Callback::Failure being executed then the associated
* Handler's OnQueryCanceled method will be called.
*
* Some possible usage patterns include:
*
* One-time Request. Use a non-persistent query to send a JavaScript request.
* The Handler evaluates the request and returns the response. The query is
* then discarded.
*
* Broadcast. Use a persistent query to register as a JavaScript broadcast
* receiver. The Handler keeps track of all registered Callbacks and executes
* them sequentially to deliver the broadcast message.
*
* Subscription. Use a persistent query to register as a JavaScript subscription
* receiver. The Handler initiates the subscription feed on the first request
* and delivers responses to all registered subscribers as they become
* available. The Handler cancels the subscription feed when there are no
* longer any registered JavaScript receivers.
*
* Message routing occurs on a per-browser and per-context basis. Consequently,
* additional application logic can be applied by restricting which browser or
* context instances are passed into the router. If you choose to use this
* approach do so cautiously. In order for the router to function correctly any
* browser or context instance passed into a single router callback must then
* be passed into all router callbacks.
*
* There is generally no need to have multiple renderer-side routers unless you
* wish to have multiple bindings with different JavaScript function names. It
* can be useful to have multiple browser-side routers with different client-
* provided Handler instances when implementing different behaviors on a per-
* browser basis.
*
* This implementation places no formatting restrictions on payload content.
* An application may choose to exchange anything from simple formatted
* strings to serialized XML or JSON data.
*
*
* EXAMPLE USAGE
*
* 1. Define the router configuration. You can optionally specify settings
* like the JavaScript function names. The configuration must be the same in
* both the browser and renderer processes. If using multiple routers in the
* same application make sure to specify unique function names for each
* router configuration.
*
* // Example config object showing the default values.
* CefMessageRouterConfig config = new CefMessageRouterConfig();
* config.jsQueryFunction = "cefQuery";
* config.jsCancelFunction = "cefQueryCancel";
*
* 2. Create an instance of CefMessageRouter in the browser process.
*
* messageRouter_ = CefMessageRouter.create(config);
*
* 3. Register one or more Handlers. The Handler instances must either outlive
* the router or be removed from the router before they're deleted.
*
* messageRouter_.addHandler(myHandler);
*
* 4. Add your message router to all CefClient instances you want to get your
* JavaScript code be handled.
*
* myClient.addMessageRouter(messageRouter_);
*
* 4. Execute the query function from JavaScript code.
*
* window.cefQuery({request: 'my_request',
* persistent: false,
* onSuccess: function(response) { print(response); },
* onFailure: function(error_code, error_message) {} });
*
* 5. Handle the query in your CefMessageRouterHandler.onQuery implementation
* and execute the appropriate callback either immediately or asynchronously.
*
* public boolean onQuery(CefBrowser browser,
* long query_id,
* String request,
* boolean persistent,
* CefQueryCallback callback) {
* if (request.indexOf("my_request") == 0) {
* callback.success("my_response");
* return true;
* }
* return false; // Not handled.
* }
*
* 6. Notice that the success callback is executed in JavaScript.
*/
public abstract class CefMessageRouter {
private CefMessageRouterConfig routerConfig_ = null;
/**
* Used to configure the query router. If using multiple router pairs make
* sure to choose values that do not conflict.
*/
public static class CefMessageRouterConfig {
/**
* Name of the JavaScript function that will be added to the 'window' object
* for sending a query. The default value is "cefQuery".
*/
public String jsQueryFunction;
/**
* Name of the JavaScript function that will be added to the 'window' object
* for canceling a pending query. The default value is "cefQueryCancel".
*/
public String jsCancelFunction;
public CefMessageRouterConfig() {
this("cefQuery", "cefQueryCancel");
}
public CefMessageRouterConfig(String queryFunction, String cancelFunction) {
jsQueryFunction = queryFunction;
jsCancelFunction = cancelFunction;
}
}
// This CTOR can't be called directly. Call method create() instead.
CefMessageRouter() {}
@Override
protected void finalize() throws Throwable {
dispose();
super.finalize();
}
/**
* Create a new router with the default configuration. The addHandler() method should be called
* to add a handler.
*/
public static final CefMessageRouter create() {
return CefMessageRouter.create(null, null);
}
/**
* Create a new router with the specified configuration. The addHandler() method should be
* called to add a handler.
*/
public static final CefMessageRouter create(CefMessageRouterConfig config) {
return CefMessageRouter.create(config, null);
}
/**
* Create a new router with the specified handler and default configuration.
*/
public static final CefMessageRouter create(CefMessageRouterHandler handler) {
return CefMessageRouter.create(null, handler);
}
/**
* Create a new router with the specified handler and configuration.
*/
public static final CefMessageRouter create(
CefMessageRouterConfig config, CefMessageRouterHandler handler) {
CefMessageRouter router = CefMessageRouter_N.createNative(config);
if (router != null && handler != null) router.addHandler(handler, true);
return router;
}
/**
* Must be called if the CefMessageRouter instance isn't used any more.
*/
public abstract void dispose();
// Called from native code during handling of createNative().
void setMessageRouterConfig(CefMessageRouterConfig config) {
routerConfig_ = config;
}
// Called from native code during handling of CefClientHandler.[add|remove]MessageRouter().
public final CefMessageRouterConfig getMessageRouterConfig() {
return routerConfig_;
}
/**
* Add a new query handler.
*
* @param handler The handler to be added.
* @param first If true the handler will be added as the first handler, otherwise it will be
* added as the last handler.
* @return True if the handler is added successfully.
*/
public abstract boolean addHandler(CefMessageRouterHandler handler, boolean first);
/**
* Remove an existing query handler. Any pending queries associated with the handler will be
* canceled. onQueryCanceled will be called and the associated JavaScript onFailure callback
* will be executed with an error code of -1.
*
* @param handler The handler to be removed.
* @return True if the handler is removed successfully.
*/
public abstract boolean removeHandler(CefMessageRouterHandler handler);
/**
* Cancel all pending queries associated with either |browser| or |handler|. If both |browser|
* and |handler| are NULL all pending queries will be canceled. onQueryCanceled will be called
* and the associated JavaScript onFailure callback will be executed in all cases with an error
* code of -1.
*
* @param browser The associated browser, or null.
* @param handler The associated handler, or null.
*/
public abstract void cancelPending(CefBrowser browser, CefMessageRouterHandler handler);
}

View File

@@ -0,0 +1,82 @@
// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef.browser;
import org.cef.callback.CefNative;
import org.cef.handler.CefMessageRouterHandler;
class CefMessageRouter_N extends CefMessageRouter implements CefNative {
// Used internally to store a pointer to the CEF object.
private long N_CefHandle = 0;
@Override
public void setNativeRef(String identifer, long nativeRef) {
N_CefHandle = nativeRef;
}
@Override
public long getNativeRef(String identifer) {
return N_CefHandle;
}
private CefMessageRouter_N() {
super();
}
public static final CefMessageRouter createNative(CefMessageRouterConfig config) {
try {
return CefMessageRouter_N.N_Create(config);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
return null;
}
}
@Override
public void dispose() {
try {
N_Dispose(N_CefHandle);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
@Override
public boolean addHandler(CefMessageRouterHandler handler, boolean first) {
try {
return N_AddHandler(N_CefHandle, handler, first);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
return false;
}
}
@Override
public boolean removeHandler(CefMessageRouterHandler handler) {
try {
return N_RemoveHandler(N_CefHandle, handler);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
return false;
}
}
@Override
public void cancelPending(CefBrowser browser, CefMessageRouterHandler handler) {
try {
N_CancelPending(N_CefHandle, browser, handler);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
private final native static CefMessageRouter_N N_Create(CefMessageRouterConfig config);
private final native void N_Dispose(long self);
private final native boolean N_AddHandler(
long self, CefMessageRouterHandler handler, boolean first);
private final native boolean N_RemoveHandler(long self, CefMessageRouterHandler handler);
private final native void N_CancelPending(
long self, CefBrowser browser, CefMessageRouterHandler handler);
}

View File

@@ -0,0 +1,209 @@
// Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef.browser;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.montoyo.mcef.MCEF;
import net.montoyo.mcef.utilities.Log;
import org.lwjgl.opengl.EXTBgra;
import java.awt.*;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import static org.lwjgl.opengl.GL11.*;
public class CefRenderer {
//montoyo: debug tool
//montoyo: debug tool
private static final ArrayList<Integer> GL_TEXTURES = new ArrayList<>();
public static void dumpVRAMLeak() {
Log.info(">>>>> MCEF: Beginning VRAM leak report");
GL_TEXTURES.forEach(tex -> Log.warning(">>>>> MCEF: This texture has not been freed: " + tex));
Log.info(">>>>> MCEF: End of VRAM leak report");
}
private boolean transparent_;
public int[] texture_id_ = new int[1];
private int view_width_ = 0;
private int view_height_ = 0;
private float spin_x_ = 0f;
private float spin_y_ = 0f;
private Rectangle popup_rect_ = new Rectangle(0, 0, 0, 0);
private Rectangle original_popup_rect_ = new Rectangle(0, 0, 0, 0);
private boolean use_draw_pixels_ = false;
protected CefRenderer(boolean transparent) {
transparent_ = transparent;
initialize();
}
protected boolean isTransparent() {
return transparent_;
}
protected int getTextureID() {
return texture_id_[0];
}
protected void initialize() {
GlStateManager.enableTexture2D();
texture_id_[0] = glGenTextures();
if (MCEF.CHECK_VRAM_LEAK)
GL_TEXTURES.add(texture_id_[0]);
GlStateManager.bindTexture(texture_id_[0]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
GlStateManager.bindTexture(0);
}
protected void cleanup() {
if (texture_id_[0] != 0) {
if (MCEF.CHECK_VRAM_LEAK)
GL_TEXTURES.remove((Object) texture_id_[0]);
glDeleteTextures(texture_id_[0]);
}
}
protected void render(double x1, double y1, double x2, double y2) {
if (view_width_ == 0 || view_height_ == 0)
return;
Tessellator t = Tessellator.getInstance();
BufferBuilder vb = t.getBuffer();
GlStateManager.bindTexture(texture_id_[0]);
vb.begin(GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR);
vb.pos(x1, y1, 0.0).tex(0.0, 1.0).color(255, 255, 255, 255).endVertex();
vb.pos(x2, y1, 0.0).tex(1.f, 1.f).color(255, 255, 255, 255).endVertex();
vb.pos(x2, y2, 0.0).tex(1.f, 0.0).color(255, 255, 255, 255).endVertex();
vb.pos(x1, y2, 0.0).tex(0.0, 0.0).color(255, 255, 255, 255).endVertex();
t.draw();
GlStateManager.bindTexture(0);
}
protected void onPopupSize(Rectangle rect) {
if (rect.width <= 0 || rect.height <= 0) return;
original_popup_rect_ = rect;
popup_rect_ = getPopupRectInWebView(original_popup_rect_);
}
protected Rectangle getPopupRect() {
return (Rectangle) popup_rect_.clone();
}
protected Rectangle getPopupRectInWebView(Rectangle original_rect) {
Rectangle rc = original_rect;
// if x or y are negative, move them to 0.
if (rc.x < 0) rc.x = 0;
if (rc.y < 0) rc.y = 0;
// if popup goes outside the view, try to reposition origin
if (rc.x + rc.width > view_width_) rc.x = view_width_ - rc.width;
if (rc.y + rc.height > view_height_) rc.y = view_height_ - rc.height;
// if x or y became negative, move them to 0 again.
if (rc.x < 0) rc.x = 0;
if (rc.y < 0) rc.y = 0;
return rc;
}
protected void clearPopupRects() {
popup_rect_.setBounds(0, 0, 0, 0);
original_popup_rect_.setBounds(0, 0, 0, 0);
}
protected void onPaint(boolean popup, Rectangle[] dirtyRects, ByteBuffer buffer, int width, int height, boolean completeReRender) {
if (transparent_) // Enable alpha blending.
GlStateManager.enableBlend();
final int size = (width * height) << 2;
if (size > buffer.limit()) {
Log.warning("Bad data passed to CefRenderer.onPaint() triggered safe guards... (1)");
return;
}
// Enable 2D textures.
GlStateManager.enableTexture2D();
GlStateManager.bindTexture(texture_id_[0]);
int oldAlignement = glGetInteger(GL_UNPACK_ALIGNMENT);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
if (!popup) {
if (completeReRender || width != view_width_ || height != view_height_) {
// Update/resize the whole texture.
view_width_ = width;
view_height_ = height;
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, view_width_, view_height_, 0, EXTBgra.GL_BGRA_EXT, GL_UNSIGNED_BYTE, buffer);
} else {
glPixelStorei(GL_UNPACK_ROW_LENGTH, view_width_);
// Update just the dirty rectangles.
for (Rectangle rect : dirtyRects) {
if (rect.x < 0 || rect.y < 0 || rect.x + rect.width > view_width_ || rect.y + rect.height > view_height_)
Log.warning("Bad data passed to CefRenderer.onPaint() triggered safe guards... (2)");
else {
glPixelStorei(GL_UNPACK_SKIP_PIXELS, rect.x);
glPixelStorei(GL_UNPACK_SKIP_ROWS, rect.y);
glTexSubImage2D(GL_TEXTURE_2D, 0, rect.x, rect.y, rect.width, rect.height, EXTBgra.GL_BGRA_EXT, GL_UNSIGNED_BYTE, buffer);
}
}
glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
}
} else if (popup_rect_.width > 0 && popup_rect_.height > 0) {
int skip_pixels = 0, x = popup_rect_.x;
int skip_rows = 0, y = popup_rect_.y;
int w = width;
int h = height;
// Adjust the popup to fit inside the view.
if (x < 0) {
skip_pixels = -x;
x = 0;
}
if (y < 0) {
skip_rows = -y;
y = 0;
}
if (x + w > view_width_)
w -= x + w - view_width_;
if (y + h > view_height_)
h -= y + h - view_height_;
// Update the popup rectangle.
glPixelStorei(GL_UNPACK_ROW_LENGTH, width);
glPixelStorei(GL_UNPACK_SKIP_PIXELS, skip_pixels);
glPixelStorei(GL_UNPACK_SKIP_ROWS, skip_rows);
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, EXTBgra.GL_BGRA_EXT, GL_UNSIGNED_BYTE, buffer);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
}
glPixelStorei(GL_UNPACK_ALIGNMENT, oldAlignement);
GlStateManager.bindTexture(0);
}
protected void setSpin(float spinX, float spinY) {
spin_x_ = spinX;
spin_y_ = spinY;
}
protected void incrementSpin(float spinDX, float spinDY) {
spin_x_ -= spinDX;
spin_y_ -= spinDY;
}
}

View File

@@ -0,0 +1,53 @@
// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef.browser;
import org.cef.handler.CefRequestContextHandler;
/**
* A request context provides request handling for a set of related browser
* objects. A request context is specified when creating a new browser object
* via the CefClient.createBrowser method. Browser objects with different
* request contexts will never be hosted in the same render process. Browser
* objects with the same request context may or may not be hosted in the same
* render process depending on the process model. Browser objects created
* indirectly via the JavaScript window.open function or targeted links will
* share the same render process and the same request context as the source
* browser. When running in single-process mode there is only a single render
* process (the main process) and so all browsers created in single-process mode
* will share the same request context. This will be the first request context
* passed into the CefClient.createBrowser method and all other request
* context objects will be ignored.
*/
public abstract class CefRequestContext {
// This CTOR can't be called directly. Call method create() instead.
CefRequestContext() {}
/**
* Returns the global context object.
*/
public static final CefRequestContext getGlobalContext() {
return CefRequestContext_N.getGlobalContextNative();
}
/**
* Creates a new context object with the specified handler.
*/
public static final CefRequestContext createContext(CefRequestContextHandler handler) {
return CefRequestContext_N.createNative(handler);
}
public abstract void dispose();
/**
* Returns true if this object is the global context.
*/
public abstract boolean isGlobal();
/**
* Returns the handler for this context if any.
*/
public abstract CefRequestContextHandler getHandler();
}

View File

@@ -0,0 +1,86 @@
// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef.browser;
import org.cef.callback.CefNative;
import org.cef.handler.CefRequestContextHandler;
class CefRequestContext_N extends CefRequestContext implements CefNative {
// Used internally to store a pointer to the CEF object.
private long N_CefHandle = 0;
private static CefRequestContext_N globalInstance = null;
private CefRequestContextHandler handler = null;
@Override
public void setNativeRef(String identifer, long nativeRef) {
N_CefHandle = nativeRef;
}
@Override
public long getNativeRef(String identifer) {
return N_CefHandle;
}
CefRequestContext_N() {
super();
}
static final CefRequestContext_N getGlobalContextNative() {
CefRequestContext_N result = null;
try {
result = CefRequestContext_N.N_GetGlobalContext();
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
if (globalInstance == null) {
globalInstance = result;
} else if (globalInstance.N_CefHandle == result.N_CefHandle) {
result.N_CefRequestContext_DTOR();
}
return globalInstance;
}
static final CefRequestContext_N createNative(CefRequestContextHandler handler) {
CefRequestContext_N result = null;
try {
result = CefRequestContext_N.N_CreateContext(handler);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
if (result != null) result.handler = handler;
return result;
}
@Override
public void dispose() {
try {
N_CefRequestContext_DTOR();
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
@Override
public boolean isGlobal() {
try {
return N_IsGlobal();
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return false;
}
@Override
public CefRequestContextHandler getHandler() {
return handler;
}
private final static native CefRequestContext_N N_GetGlobalContext();
private final static native CefRequestContext_N N_CreateContext(
CefRequestContextHandler handler);
private final native boolean N_IsGlobal();
private final native void N_CefRequestContext_DTOR();
}

View File

@@ -0,0 +1,16 @@
// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef.browser.mac;
import org.cef.browser.CefBrowserWindow;
import java.awt.Component;
public class CefBrowserWindowMac implements CefBrowserWindow {
@Override
public long getWindowHandle(Component comp) {
return 0L;
}
}

View File

@@ -0,0 +1,21 @@
// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef.callback;
/**
* Callback interface used for asynchronous continuation of authentication
* requests.
*/
public interface CefAuthCallback {
/**
* Continue the authentication request.
*/
public void Continue(String username, String password);
/**
* Cancel the authentication request.
*/
public void cancel();
}

View File

@@ -0,0 +1,36 @@
// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef.callback;
class CefAuthCallback_N extends CefNativeAdapter implements CefAuthCallback {
CefAuthCallback_N() {}
@Override
protected void finalize() throws Throwable {
cancel();
super.finalize();
}
@Override
public void Continue(String username, String password) {
try {
N_Continue(getNativeRef(null), username, password);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
@Override
public void cancel() {
try {
N_Cancel(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
private final native void N_Continue(long self, String username, String password);
private final native void N_Cancel(long self);
}

View File

@@ -0,0 +1,20 @@
// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef.callback;
/**
* Callback interface used to asynchronously continue a download.
*/
public interface CefBeforeDownloadCallback {
/**
* Call to continue the download.
* @param downloadPath Set it to the full file path for the download
* including the file name or leave blank to use the suggested name and
* the default temp directory.
* @param showDialog Set it to true if you do wish to show the default
* "Save As" dialog.
*/
public void Continue(String downloadPath, boolean showDialog);
}

View File

@@ -0,0 +1,26 @@
// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef.callback;
class CefBeforeDownloadCallback_N extends CefNativeAdapter implements CefBeforeDownloadCallback {
CefBeforeDownloadCallback_N() {}
@Override
protected void finalize() throws Throwable {
Continue("", false);
super.finalize();
}
@Override
public void Continue(String downloadPath, boolean showDialog) {
try {
N_Continue(getNativeRef(null), downloadPath, showDialog);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
private final native void N_Continue(long self, String downloadPath, boolean showDialog);
}

View File

@@ -0,0 +1,20 @@
// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef.callback;
/**
* Generic callback interface used for asynchronous continuation.
*/
public interface CefCallback {
/**
* Continue processing.
*/
void Continue();
/**
* Cancel processing.
*/
void cancel();
}

View File

@@ -0,0 +1,36 @@
// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef.callback;
class CefCallback_N extends CefNativeAdapter implements CefCallback {
CefCallback_N() {}
@Override
protected void finalize() throws Throwable {
cancel();
super.finalize();
}
@Override
public void Continue() {
try {
N_Continue(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
@Override
public void cancel() {
try {
N_Cancel(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
private final native void N_Continue(long self);
private final native void N_Cancel(long self);
}

View File

@@ -0,0 +1,95 @@
// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef.callback;
import java.util.Map;
import java.util.Vector;
/**
* Class used to create and/or parse command line arguments. Arguments with
* '--', '-' and, on Windows, '/' prefixes are considered switches. Switches
* will always precede any arguments without switch prefixes. Switches can
* optionally have a value specified using the '=' delimiter (e.g.
* "-switch=value"). An argument of "--" will terminate switch parsing with all
* subsequent tokens, regardless of prefix, being interpreted as non-switch
* arguments. Switch names are considered case-insensitive.
*/
public interface CefCommandLine {
/**
* Reset the command-line switches and arguments but leave the program
* component unchanged.
*/
public void reset();
/**
* Get the program part of the command line string (the first item).
*/
public String getProgram();
/**
* Set the program part of the command line string (the first item).
* @param program Name of the program.
*/
public void setProgram(String program);
/**
* Checks if the command line has switches.
* @return true if the command line has switches.
*/
public boolean hasSwitches();
/**
* Checks if the command line has a specific switches.
* @param name A switch name to test for.
* @return true if the command line contains the given switch.
*/
public boolean hasSwitch(String name);
/**
* Returns the value associated with the given switch. If the switch has no
* value or isn't present this method returns the empty string.
* @param name the name of the switch.
* @return the value of the switch.
*/
public String getSwitchValue(String name);
/**
* Returns the map of switch names and values. If a switch has no value an
* empty string is returned.
* @return Map of switches and each value.
*/
public Map<String, String> getSwitches();
/**
* Add a switch with an empty value to the end of the command line.
* @param name name of the switch.
*/
public void appendSwitch(String name);
/**
* Add a switch with the specified value to the end of the command line.
* @param name name of the switch.
* @param value value for the switch.
*/
public void appendSwitchWithValue(String name, String value);
/**
* Tests if there are remaining command line arguments.
* @return True if there are remaining command line arguments.
*/
public boolean hasArguments();
/**
* Get the remaining command line arguments.
* @return Vector of command line arguments.
*/
public Vector<String> getArguments();
/**
* Add an argument to the end of the command line.
* @param argument name of the argument.
*/
public void appendArgument(String argument);
}

View File

@@ -0,0 +1,154 @@
// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef.callback;
import java.util.Map;
import java.util.Vector;
class CefCommandLine_N extends CefNativeAdapter implements CefCommandLine {
CefCommandLine_N() {}
@Override
public void reset() {
try {
N_Reset(getNativeRef(null));
} catch (UnsatisfiedLinkError err) {
err.printStackTrace();
}
}
@Override
public String getProgram() {
try {
return N_GetProgram(getNativeRef(null));
} catch (UnsatisfiedLinkError err) {
err.printStackTrace();
}
return null;
}
@Override
public void setProgram(String program) {
try {
N_SetProgram(getNativeRef(null), program);
} catch (UnsatisfiedLinkError err) {
err.printStackTrace();
}
}
@Override
public boolean hasSwitches() {
try {
return N_HasSwitches(getNativeRef(null));
} catch (UnsatisfiedLinkError err) {
err.printStackTrace();
}
return false;
}
@Override
public boolean hasSwitch(String name) {
try {
return N_HasSwitch(getNativeRef(null), name);
} catch (UnsatisfiedLinkError err) {
err.printStackTrace();
}
return false;
}
@Override
public String getSwitchValue(String name) {
try {
return N_GetSwitchValue(getNativeRef(null), name);
} catch (UnsatisfiedLinkError err) {
err.printStackTrace();
}
return null;
}
@Override
public Map<String, String> getSwitches() {
try {
return N_GetSwitches(getNativeRef(null));
} catch (UnsatisfiedLinkError err) {
err.printStackTrace();
}
return null;
}
@Override
public void appendSwitch(String name) {
try {
N_AppendSwitch(getNativeRef(null), name);
} catch (UnsatisfiedLinkError err) {
err.printStackTrace();
}
}
@Override
public void appendSwitchWithValue(String name, String value) {
try {
N_AppendSwitchWithValue(getNativeRef(null), name, value);
} catch (UnsatisfiedLinkError err) {
err.printStackTrace();
}
}
@Override
public boolean hasArguments() {
try {
return N_HasArguments(getNativeRef(null));
} catch (UnsatisfiedLinkError err) {
err.printStackTrace();
}
return false;
}
@Override
public Vector<String> getArguments() {
try {
return N_GetArguments(getNativeRef(null));
} catch (UnsatisfiedLinkError err) {
err.printStackTrace();
}
return null;
}
@Override
public void appendArgument(String argument) {
try {
N_AppendArgument(getNativeRef(null), argument);
} catch (UnsatisfiedLinkError err) {
err.printStackTrace();
}
}
@Override
public String toString() {
String result = "CefCommandLine [program=\'" + getProgram() + "\'";
if (hasSwitches()) {
Map<String, String> switches = getSwitches();
result += ", switches=" + switches;
}
if (hasArguments()) {
Vector<String> arguments = getArguments();
result += ", arguments=" + arguments;
}
return result + "]";
}
private final native void N_Reset(long self);
private final native String N_GetProgram(long self);
private final native void N_SetProgram(long self, String program);
private final native boolean N_HasSwitches(long self);
private final native boolean N_HasSwitch(long self, String name);
private final native String N_GetSwitchValue(long self, String name);
private final native Map<String, String> N_GetSwitches(long self);
private final native void N_AppendSwitch(long self, String name);
private final native void N_AppendSwitchWithValue(long self, String name, String value);
private final native boolean N_HasArguments(long self);
private final native Vector<String> N_GetArguments(long self);
private final native void N_AppendArgument(long self, String argument);
}

View File

@@ -0,0 +1,15 @@
// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef.callback;
/**
* Generic callback interface used for asynchronous completion.
*/
public interface CefCompletionCallback {
/**
* Method that will be called once the task is complete.
*/
public abstract void onComplete();
}

View File

@@ -0,0 +1,180 @@
// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef.callback;
import java.util.Vector;
/**
* Provides information about the context menu state. The methods of this class
* can only be accessed on browser process the UI thread.
*/
public interface CefContextMenuParams {
/**
* Supported context menu type flags.
*/
public static final class TypeFlags {
public final static int CM_TYPEFLAG_NONE = 0; //!< No node is selected.
public final static int CM_TYPEFLAG_PAGE = 1 << 0; //!< The top page is selected.
public final static int CM_TYPEFLAG_FRAME = 1 << 1; //!< A subframe page is selected.
public final static int CM_TYPEFLAG_LINK = 1 << 2; //!< A link is selected.
public final static int CM_TYPEFLAG_MEDIA = 1 << 3; //!< A media node is selected.
public final static int CM_TYPEFLAG_SELECTION = 1
<< 4; //!< There is a textual or mixed selection that is selected.
public final static int CM_TYPEFLAG_EDITABLE = 1 << 5; //!< An editable element is selected.
}
/**
* Supported context menu media types.
*/
public enum MediaType {
CM_MEDIATYPE_NONE, //!< No special node is in context.
CM_MEDIATYPE_IMAGE, //!< An image node is selected.
CM_MEDIATYPE_VIDEO, //!< A video node is selected.
CM_MEDIATYPE_AUDIO, //!< An audio node is selected.
CM_MEDIATYPE_FILE, //!< A file node is selected.
CM_MEDIATYPE_PLUGIN, //!< A plugin node is selected.
}
/**
* Supported context menu media state bit flags.
*/
public static final class MediaStateFlags {
public final static int CM_MEDIAFLAG_NONE = 0;
public final static int CM_MEDIAFLAG_ERROR = 1 << 0;
public final static int CM_MEDIAFLAG_PAUSED = 1 << 1;
public final static int CM_MEDIAFLAG_MUTED = 1 << 2;
public final static int CM_MEDIAFLAG_LOOP = 1 << 3;
public final static int CM_MEDIAFLAG_CAN_SAVE = 1 << 4;
public final static int CM_MEDIAFLAG_HAS_AUDIO = 1 << 5;
public final static int CM_MEDIAFLAG_HAS_VIDEO = 1 << 6;
public final static int CM_MEDIAFLAG_CONTROL_ROOT_ELEMENT = 1 << 7;
public final static int CM_MEDIAFLAG_CAN_PRINT = 1 << 8;
public final static int CM_MEDIAFLAG_CAN_ROTATE = 1 << 9;
}
/**
* Supported context menu edit state bit flags.
*/
public static final class EditStateFlags {
public final static int CM_EDITFLAG_NONE = 0;
public final static int CM_EDITFLAG_CAN_UNDO = 1 << 0;
public final static int CM_EDITFLAG_CAN_REDO = 1 << 1;
public final static int CM_EDITFLAG_CAN_CUT = 1 << 2;
public final static int CM_EDITFLAG_CAN_COPY = 1 << 3;
public final static int CM_EDITFLAG_CAN_PASTE = 1 << 4;
public final static int CM_EDITFLAG_CAN_DELETE = 1 << 5;
public final static int CM_EDITFLAG_CAN_SELECT_ALL = 1 << 6;
public final static int CM_EDITFLAG_CAN_TRANSLATE = 1 << 7;
}
/**
* Returns the X coordinate of the mouse where the context menu was invoked.
* Coords are relative to the associated RenderView's origin.
*/
int getXCoord();
/**
* Returns the Y coordinate of the mouse where the context menu was invoked.
* Coords are relative to the associated RenderView's origin.
*/
int getYCoord();
/**
* Returns flags representing the type of node that the context menu was
* invoked on. See TypeFlags for supported values
*/
int getTypeFlags();
/**
* Returns the URL of the link, if any, that encloses the node that the
* context menu was invoked on.
*/
String getLinkUrl();
/**
* Returns the link URL, if any, to be used ONLY for "copy link address". We
* don't validate this field in the frontend process.
*/
String getUnfilteredLinkUrl();
/**
* Returns the source URL, if any, for the element that the context menu was
* invoked on. Example of elements with source URLs are img, audio, and video.
*/
String getSourceUrl();
/**
* Returns true if the context menu was invoked on an image which has
* non-empty contents.
*/
boolean hasImageContents();
/**
* Returns the URL of the top level page that the context menu was invoked on.
*/
String getPageUrl();
/**
* Returns the URL of the subframe that the context menu was invoked on.
*/
String getFrameUrl();
/**
* Returns the character encoding of the subframe that the context menu was
* invoked on.
*/
String getFrameCharset();
/**
* Returns the type of context node that the context menu was invoked on.
*/
MediaType getMediaType();
/**
* Returns flags representing the actions supported by the media element, if
* any, that the context menu was invoked on. See MediaStateFlags for possible
* values.
*/
int getMediaStateFlags();
/**
* Returns the text of the selection, if any, that the context menu was
* invoked on.
*/
String getSelectionText();
/**
* Returns the text of the misspelled word, if any, that the context menu was
* invoked on.
*/
String getMisspelledWord();
/**
* Returns true if suggestions exist, false otherwise. Fills in |suggestions|
* from the spell check service for the misspelled word if there is one.
*/
boolean getDictionarySuggestions(Vector<String> suggestions);
/**
* Returns true if the context menu was invoked on an editable node.
*/
boolean isEditable();
/**
* Returns true if the context menu was invoked on an editable node where
* spell-check is enabled.
*/
boolean isSpellCheckEnabled();
/**
* Returns flags representing the actions supported by the editable node, if
* any, that the context menu was invoked on. See EditStateFlags for possible
* values.
*/
int getEditStateFlags();
}

View File

@@ -0,0 +1,210 @@
// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef.callback;
import java.util.Vector;
class CefContextMenuParams_N extends CefNativeAdapter implements CefContextMenuParams {
CefContextMenuParams_N() {}
@Override
public int getXCoord() {
try {
return N_GetXCoord(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return 0;
}
@Override
public int getYCoord() {
try {
return N_GetYCoord(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return 0;
}
@Override
public int getTypeFlags() {
try {
return N_GetTypeFlags(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return 0;
}
@Override
public String getLinkUrl() {
try {
return N_GetLinkUrl(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return null;
}
@Override
public String getUnfilteredLinkUrl() {
try {
return N_GetUnfilteredLinkUrl(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return null;
}
@Override
public String getSourceUrl() {
try {
return N_GetSourceUrl(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return null;
}
@Override
public boolean hasImageContents() {
try {
return N_HasImageContents(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return false;
}
@Override
public String getPageUrl() {
try {
return N_GetPageUrl(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return null;
}
@Override
public String getFrameUrl() {
try {
return N_GetFrameUrl(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return null;
}
@Override
public String getFrameCharset() {
try {
return N_GetFrameCharset(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return null;
}
@Override
public MediaType getMediaType() {
try {
return N_GetMediaType(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return null;
}
@Override
public int getMediaStateFlags() {
try {
return N_GetMediaStateFlags(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return 0;
}
@Override
public String getSelectionText() {
try {
return N_GetSelectionText(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return null;
}
@Override
public String getMisspelledWord() {
try {
return N_GetMisspelledWord(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return null;
}
@Override
public boolean getDictionarySuggestions(Vector<String> suggestions) {
try {
return N_GetDictionarySuggestions(getNativeRef(null), suggestions);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return false;
}
@Override
public boolean isEditable() {
try {
return N_IsEditable(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return false;
}
@Override
public boolean isSpellCheckEnabled() {
try {
return N_IsSpellCheckEnabled(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return false;
}
@Override
public int getEditStateFlags() {
try {
return N_GetEditStateFlags(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return 0;
}
private final native int N_GetXCoord(long self);
private final native int N_GetYCoord(long self);
private final native int N_GetTypeFlags(long self);
private final native String N_GetLinkUrl(long self);
private final native String N_GetUnfilteredLinkUrl(long self);
private final native String N_GetSourceUrl(long self);
private final native boolean N_HasImageContents(long self);
private final native String N_GetPageUrl(long self);
private final native String N_GetFrameUrl(long self);
private final native String N_GetFrameCharset(long self);
private final native MediaType N_GetMediaType(long self);
private final native int N_GetMediaStateFlags(long self);
private final native String N_GetSelectionText(long self);
private final native String N_GetMisspelledWord(long self);
private final native boolean N_GetDictionarySuggestions(long self, Vector<String> suggestions);
private final native boolean N_IsEditable(long self);
private final native boolean N_IsSpellCheckEnabled(long self);
private final native int N_GetEditStateFlags(long self);
}

View File

@@ -0,0 +1,23 @@
// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef.callback;
import org.cef.misc.BoolRef;
import org.cef.network.CefCookie;
/**
* Interface to implement for visiting cookie values. The methods of this class
* will always be called on the IO thread.
*/
public interface CefCookieVisitor {
/**
* Method that will be called once for each cookie. |count| is the 0-based
* index for the current cookie. |total| is the total number of cookies.
* Set |deleteCookie| to true to delete the cookie currently being visited.
* Return false to stop visiting cookies. This method may never be called if
* no cookies are found.
*/
public abstract boolean visit(CefCookie cookie, int count, int total, BoolRef delete);
}

View File

@@ -0,0 +1,94 @@
// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef.callback;
import java.util.Date;
/**
* Class used to represent a download item.
*/
public interface CefDownloadItem {
/**
* Returns true if this object is valid. Do not call any other methods if this
* function returns false.
*/
boolean isValid();
/**
* Returns true if the download is in progress.
*/
boolean isInProgress();
/**
* Returns true if the download is complete.
*/
boolean isComplete();
/**
* Returns true if the download has been canceled or interrupted.
*/
boolean isCanceled();
/**
* Returns a simple speed estimate in bytes/s.
*/
long getCurrentSpeed();
/**
* Returns the rough percent complete or -1 if the receive total size is
* unknown.
*/
int getPercentComplete();
/**
* Returns the total number of bytes.
*/
long getTotalBytes();
/**
* Returns the number of received bytes.
*/
long getReceivedBytes();
/**
* Returns the time that the download started.
*/
Date getStartTime();
/**
* Returns the time that the download ended.
*/
Date getEndTime();
/**
* Returns the full path to the downloaded or downloading file.
*/
String getFullPath();
/**
* Returns the unique identifier for this download.
*/
int getId();
/**
* Returns the URL.
*/
String getURL();
/**
* Returns the suggested file name.
*/
String getSuggestedFileName();
/**
* Returns the content disposition.
*/
String getContentDisposition();
/**
* Returns the mime type.
*/
String getMimeType();
}

View File

@@ -0,0 +1,25 @@
// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef.callback;
/**
* Callback interface used to asynchronously modify download status.
*/
public interface CefDownloadItemCallback {
/**
* Call to cancel the download.
*/
public void cancel();
/**
* Call to pause the download.
*/
public void pause();
/**
* Call to resume the download.
*/
public void resume();
}

View File

@@ -0,0 +1,51 @@
// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef.callback;
class CefDownloadItemCallback_N extends CefNativeAdapter implements CefDownloadItemCallback {
CefDownloadItemCallback_N() {}
@Override
protected void finalize() throws Throwable {
try {
N_Dispose(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
super.finalize();
}
@Override
public void cancel() {
try {
N_Cancel(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
@Override
public void pause() {
try {
N_Pause(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
@Override
public void resume() {
try {
N_Resume(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
private final native void N_Dispose(long self);
private final native void N_Cancel(long self);
private final native void N_Pause(long self);
private final native void N_Resume(long self);
}

View File

@@ -0,0 +1,188 @@
// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef.callback;
import java.util.Date;
class CefDownloadItem_N extends CefNativeAdapter implements CefDownloadItem {
CefDownloadItem_N() {}
@Override
public boolean isValid() {
try {
return N_IsValid(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return false;
}
@Override
public boolean isInProgress() {
try {
return N_IsInProgress(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return false;
}
@Override
public boolean isComplete() {
try {
return N_IsComplete(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return false;
}
@Override
public boolean isCanceled() {
try {
return N_IsCanceled(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return false;
}
@Override
public long getCurrentSpeed() {
try {
return N_GetCurrentSpeed(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return 0;
}
@Override
public int getPercentComplete() {
try {
return N_GetPercentComplete(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return 0;
}
@Override
public long getTotalBytes() {
try {
return N_GetTotalBytes(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return 0;
}
@Override
public long getReceivedBytes() {
try {
return N_GetReceivedBytes(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return 0;
}
@Override
public Date getStartTime() {
try {
return N_GetStartTime(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return null;
}
@Override
public Date getEndTime() {
try {
return N_GetEndTime(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return null;
}
@Override
public String getFullPath() {
try {
return N_GetFullPath(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return null;
}
@Override
public int getId() {
try {
return N_GetId(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return 0;
}
@Override
public String getURL() {
try {
return N_GetURL(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return null;
}
@Override
public String getSuggestedFileName() {
try {
return N_GetSuggestedFileName(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return null;
}
@Override
public String getContentDisposition() {
try {
return N_GetContentDisposition(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return null;
}
@Override
public String getMimeType() {
try {
return N_GetMimeType(getNativeRef(null));
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return null;
}
private final native boolean N_IsValid(long self);
private final native boolean N_IsInProgress(long self);
private final native boolean N_IsComplete(long self);
private final native boolean N_IsCanceled(long self);
private final native long N_GetCurrentSpeed(long self);
private final native int N_GetPercentComplete(long self);
private final native long N_GetTotalBytes(long self);
private final native long N_GetReceivedBytes(long self);
private final native Date N_GetStartTime(long self);
private final native Date N_GetEndTime(long self);
private final native String N_GetFullPath(long self);
private final native int N_GetId(long self);
private final native String N_GetURL(long self);
private final native String N_GetSuggestedFileName(long self);
private final native String N_GetContentDisposition(long self);
private final native String N_GetMimeType(long self);
}

View File

@@ -0,0 +1,189 @@
// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef.callback;
import java.io.OutputStream;
import java.util.Vector;
/**
* Class used to represent drag data. The methods of this class may be called
* on any thread.
*/
public abstract class CefDragData {
/**
* Supported drag operation bit flags.
*/
public static final class DragOperations {
public final static int DRAG_OPERATION_NONE = 0;
public final static int DRAG_OPERATION_COPY = 1;
public final static int DRAG_OPERATION_LINK = 2;
public final static int DRAG_OPERATION_GENERIC = 4;
public final static int DRAG_OPERATION_PRIVATE = 8;
public final static int DRAG_OPERATION_MOVE = 16;
public final static int DRAG_OPERATION_DELETE = 32;
public final static int DRAG_OPERATION_EVERY = Integer.MAX_VALUE;
}
// This CTOR can't be called directly. Call method create() instead.
CefDragData() {}
@Override
protected void finalize() throws Throwable {
dispose();
super.finalize();
}
/**
* Create a new CefDragData object.
*/
public static final CefDragData create() {
return CefDragData_N.createNative();
}
/**
* Returns a copy of the current object
*/
public abstract CefDragData clone();
/**
* Removes the native reference from an unused object.
*/
public abstract void dispose();
/**
* Test if the object is set to read-only.
* @return true if this object is read-only.
*/
public abstract boolean isReadOnly();
/**
* Returns true if the drag data is a link.
*/
public abstract boolean isLink();
/**
* Returns true if the drag data is a text or html fragment.
*/
public abstract boolean isFragment();
/**
* Returns true if the drag data is a file.
*/
public abstract boolean isFile();
/**
* Return the link URL that is being dragged.
*/
public abstract String getLinkURL();
/**
* Return the title associated with the link being dragged.
*/
public abstract String getLinkTitle();
/**
* Return the metadata, if any, associated with the link being dragged.
*/
public abstract String getLinkMetadata();
/**
* Return the plain text fragment that is being dragged.
*/
public abstract String getFragmentText();
/**
* Return the text/html fragment that is being dragged.
*/
public abstract String getFragmentHtml();
/**
* Return the base URL that the fragment came from. This value is used for
* resolving relative URLs and may be empty.
*/
public abstract String getFragmentBaseURL();
/**
* Write the contents of the file being dragged out of the web view into
* |writer|. Returns the number of bytes sent to |writer|. If |writer| is
* NULL this method will return the size of the file contents in bytes.
* Call getFileName() to get a suggested name for the file.
*
* @param writer Writes the contents into this object.
* @return The number of bytes sent to writer. If writer is NULL the size of
* the file contents in bytes is returned.
*/
public abstract int getFileContents(OutputStream writer);
/**
* Return the name of the file being dragged out of the browser window.
*/
public abstract String getFileName();
/**
* Retrieve the list of file names that are being dragged into the browser
* window.
*/
public abstract boolean getFileNames(Vector<String> names);
/**
* Set the link URL that is being dragged.
* @param url The link URL to be set.
*/
public abstract void setLinkURL(String url);
/**
* Set the title associated with the link being dragged.
* @param title The tile associated with the link.
*/
public abstract void setLinkTitle(String title);
/**
* Set the metadata associated with the link being dragged.
* @param data The metadata associated with the link.
*/
public abstract void setLinkMetadata(String data);
/**
* Set the plain text fragment that is being dragged.
* @param text The plain text fragment to be set.
*/
public abstract void setFragmentText(String text);
/**
* Set the text/html fragment that is being dragged.
* @param html The html fragment to be set.
*/
public abstract void setFragmentHtml(String html);
/**
* Set the base URL that the fragment came from.
* @param baseUrl The base URL to be set.
*/
public abstract void setFragmentBaseURL(String baseUrl);
/**
* Reset the file contents. You should do this before calling
* CefBrowser.dragTargetDragEnter as the web view does not allow us to
* drag in this kind of data.
*/
public abstract void resetFileContents();
/**
* Add a file that is being dragged into the webview.
* @param path The file and path to be set.
* @param displayName The name to be displayed.
*/
public abstract void addFile(String path, String displayName);
@Override
public String toString() {
return "CefDragData [isReadOnly()=" + isReadOnly() + ", isLink()=" + isLink()
+ ", isFragment()=" + isFragment() + ", isFile()=" + isFile() + ", getLinkURL()="
+ getLinkURL() + ", getLinkTitle()=" + getLinkTitle() + ", getLinkMetadata()="
+ getLinkMetadata() + ", getFragmentText()=" + getFragmentText()
+ ", getFragmentHtml()=" + getFragmentHtml() + ", getFragmentBaseURL()="
+ getFragmentBaseURL() + ", getFileName()=" + getFileName() + "]";
}
}

View File

@@ -0,0 +1,287 @@
package org.cef.callback;
import java.io.OutputStream;
import java.util.Vector;
class CefDragData_N extends CefDragData implements CefNative {
// Used internally to store a pointer to the CEF object.
private long N_CefHandle = 0;
@Override
public void setNativeRef(String identifer, long nativeRef) {
N_CefHandle = nativeRef;
}
@Override
public long getNativeRef(String identifer) {
return N_CefHandle;
}
CefDragData_N() {
super();
}
public static CefDragData createNative() {
try {
return CefDragData_N.N_Create();
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
return null;
}
}
@Override
public CefDragData clone() {
try {
return N_Clone(N_CefHandle);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
return null;
}
}
@Override
public void dispose() {
try {
N_Dispose(N_CefHandle);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
@Override
public boolean isReadOnly() {
try {
return N_IsReadOnly(N_CefHandle);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
return true;
}
}
@Override
public boolean isLink() {
try {
return N_IsLink(N_CefHandle);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return false;
}
@Override
public boolean isFragment() {
try {
return N_IsFragment(N_CefHandle);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return false;
}
@Override
public boolean isFile() {
try {
return N_IsFile(N_CefHandle);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return false;
}
@Override
public String getLinkURL() {
try {
return N_GetLinkURL(N_CefHandle);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return null;
}
@Override
public String getLinkTitle() {
try {
return N_GetLinkTitle(N_CefHandle);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return null;
}
@Override
public String getLinkMetadata() {
try {
return N_GetLinkMetadata(N_CefHandle);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return null;
}
@Override
public String getFragmentText() {
try {
return N_GetFragmentText(N_CefHandle);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return null;
}
@Override
public String getFragmentHtml() {
try {
return N_GetFragmentHtml(N_CefHandle);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return null;
}
@Override
public String getFragmentBaseURL() {
try {
return N_GetFragmentBaseURL(N_CefHandle);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return null;
}
@Override
public int getFileContents(OutputStream writer) {
try {
return N_GetFileContents(N_CefHandle, writer);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return 0;
}
@Override
public String getFileName() {
try {
return N_GetFileName(N_CefHandle);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return null;
}
@Override
public boolean getFileNames(Vector<String> names) {
try {
return N_GetFileNames(N_CefHandle, names);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
return false;
}
public void setLinkURL(String url) {
try {
N_SetLinkURL(N_CefHandle, url);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
public void setLinkTitle(String title) {
try {
N_SetLinkTitle(N_CefHandle, title);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
public void setLinkMetadata(String data) {
try {
N_SetLinkMetadata(N_CefHandle, data);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
public void setFragmentText(String text) {
try {
N_SetFragmentText(N_CefHandle, text);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
public void setFragmentHtml(String html) {
try {
N_SetFragmentHtml(N_CefHandle, html);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
public void setFragmentBaseURL(String baseUrl) {
try {
N_SetFragmentBaseURL(N_CefHandle, baseUrl);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
public void resetFileContents() {
try {
N_ResetFileContents(N_CefHandle);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
public void addFile(String path, String displayName) {
try {
N_AddFile(N_CefHandle, path, displayName);
} catch (UnsatisfiedLinkError ule) {
ule.printStackTrace();
}
}
private final native static CefDragData_N N_Create();
private final native CefDragData_N N_Clone(long self);
private final native void N_Dispose(long self);
private final native boolean N_IsReadOnly(long self);
private final native boolean N_IsLink(long self);
private final native boolean N_IsFragment(long self);
private final native boolean N_IsFile(long self);
private final native String N_GetLinkURL(long self);
private final native String N_GetLinkTitle(long self);
private final native String N_GetLinkMetadata(long self);
private final native String N_GetFragmentText(long self);
private final native String N_GetFragmentHtml(long self);
private final native String N_GetFragmentBaseURL(long self);
private final native int N_GetFileContents(long self, OutputStream writer);
private final native String N_GetFileName(long self);
private final native boolean N_GetFileNames(long self, Vector<String> names);
private final native void N_SetLinkURL(long self, String url);
private final native void N_SetLinkTitle(long self, String title);
private final native void N_SetLinkMetadata(long self, String data);
private final native void N_SetFragmentText(long self, String text);
private final native void N_SetFragmentHtml(long self, String html);
private final native void N_SetFragmentBaseURL(long self, String baseUrl);
private final native void N_ResetFileContents(long self);
private final native void N_AddFile(long self, String path, String displayName);
@Override
public String toString() {
Vector<String> names = new Vector<>();
getFileNames(names);
String fileNamesStr = "{";
for (String s : names) fileNamesStr += s + ",";
fileNamesStr += "}";
return "CefDragData_N [isLink()=" + isLink() + ", isFragment()=" + isFragment()
+ ", isFile()=" + isFile() + ", getLinkURL()=" + getLinkURL()
+ ", getLinkTitle()=" + getLinkTitle() + ", getLinkMetadata()=" + getLinkMetadata()
+ ", getFragmentText()=" + getFragmentText() + ", getFragmentHtml()="
+ getFragmentHtml() + ", getFragmentBaseURL()=" + getFragmentBaseURL()
+ ", getFileName()=" + getFileName() + ", getFileNames(vector)=" + fileNamesStr
+ "]";
}
}

View File

@@ -0,0 +1,28 @@
// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package org.cef.callback;
import java.util.Vector;
/**
* Callback interface for asynchronous continuation of file dialog requests.
*/
public interface CefFileDialogCallback {
/**
* Continue the file selection with the specified file_paths. This may be
* a single value or a list of values depending on the dialog mode. An empty
* value is treated the same as calling Cancel().
*
* @param selectedAcceptFilter 0-based index of the value selected from the
* accept filters array passed to CefDialogHandler::OnFileDialog.
* @param filePaths list of selected file paths or an empty list.
*/
public void Continue(int selectedAcceptFilter, Vector<String> filePaths);
/**
* Cancel the file selection.
*/
public void Cancel();
}

Some files were not shown because too many files have changed in this diff Show More