mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-07 08:39:22 +00:00
Android: Keep up with the times
While some of the changes are due to syntax and have no effect on the functionality, others are more important. The major changes are - Use Painter instead of now removed DeprecatedPainter - Remove the need of LibArchive by using Java's native zip support - Have a proper C++23 compiler by updating NDK to r29 beta 2 - Store user data and config in an user accessible way - Update AGP to 8.11 and update compile target to SDK level 35
This commit is contained in:
parent
c99a467cdb
commit
6f350c51fd
Notes:
github-actions[bot]
2025-07-10 21:47:01 +00:00
Author: https://github.com/Olekoop
Commit: 6f350c51fd
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/5053
Reviewed-by: https://github.com/ADKaster ✅
19 changed files with 113 additions and 222 deletions
|
@ -35,6 +35,7 @@ cmake -S "${LADYBIRD_SOURCE_DIR}/Meta/Lagom" -B "$BUILD_DIR/lagom-tools" \
|
||||||
-DCMAKE_C_COMPILER="$CC" \
|
-DCMAKE_C_COMPILER="$CC" \
|
||||||
-DCMAKE_CXX_COMPILER="$CXX" \
|
-DCMAKE_CXX_COMPILER="$CXX" \
|
||||||
-DLADYBIRD_CACHE_DIR="$CACHE_DIR" \
|
-DLADYBIRD_CACHE_DIR="$CACHE_DIR" \
|
||||||
|
-DBUILD_SHARED_LIBS=OFF \
|
||||||
-DLAGOM_TOOLS_ONLY=ON \
|
-DLAGOM_TOOLS_ONLY=ON \
|
||||||
-DINSTALL_LAGOM_TOOLS=ON \
|
-DINSTALL_LAGOM_TOOLS=ON \
|
||||||
-DCMAKE_TOOLCHAIN_FILE="$LADYBIRD_SOURCE_DIR/Build/vcpkg/scripts/buildsystems/vcpkg.cmake" \
|
-DCMAKE_TOOLCHAIN_FILE="$LADYBIRD_SOURCE_DIR/Build/vcpkg/scripts/buildsystems/vcpkg.cmake" \
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import com.android.build.gradle.internal.tasks.factory.dependsOn
|
import com.android.build.gradle.internal.tasks.factory.dependsOn
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application") version "8.4.0"
|
id("com.android.application") version "8.11.0"
|
||||||
id("org.jetbrains.kotlin.android") version "1.9.0"
|
id("org.jetbrains.kotlin.android") version "2.1.20"
|
||||||
}
|
}
|
||||||
|
|
||||||
var buildDir = layout.buildDirectory.get()
|
var buildDir = layout.buildDirectory.get()
|
||||||
|
@ -22,20 +22,21 @@ tasks.named("prepareKotlinBuildScriptModel").dependsOn("buildLagomTools")
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "org.serenityos.ladybird"
|
namespace = "org.serenityos.ladybird"
|
||||||
compileSdk = 34
|
compileSdk = 35
|
||||||
|
// FIXME: Replace the NDK version to a stable one (this is r29 beta 2)
|
||||||
|
ndkVersion = "29.0.13599879"
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "org.serenityos.ladybird"
|
applicationId = "org.serenityos.ladybird"
|
||||||
minSdk = 30
|
minSdk = 30
|
||||||
targetSdk = 34
|
targetSdk = 35
|
||||||
versionCode = 1
|
versionCode = 1
|
||||||
versionName = "1.0"
|
versionName = "1.0"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
// FIXME: Use -std=c++23 once the Android NDK's clang supports that.
|
cppFlags += "-std=c++23"
|
||||||
cppFlags += "-std=c++2b"
|
|
||||||
arguments += listOf(
|
arguments += listOf(
|
||||||
"-DLagomTools_DIR=$buildDir/lagom-tools-install/share/LagomTools",
|
"-DLagomTools_DIR=$buildDir/lagom-tools-install/share/LagomTools",
|
||||||
"-DANDROID_STL=c++_shared",
|
"-DANDROID_STL=c++_shared",
|
||||||
|
@ -91,5 +92,5 @@ dependencies {
|
||||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||||
androidTestImplementation("androidx.test.ext:junit-ktx:1.1.5")
|
androidTestImplementation("androidx.test.ext:junit-ktx:1.1.5")
|
||||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||||
implementation("com.google.oboe:oboe:1.9.0")
|
implementation("com.google.oboe:oboe:1.9.3")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#Fri Sep 01 12:36:55 CEST 2023
|
#Fri Sep 01 12:36:55 CEST 2023
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|
0
UI/Android/src/main/assets/.gitkeep
Normal file
0
UI/Android/src/main/assets/.gitkeep
Normal file
|
@ -72,10 +72,7 @@ public:
|
||||||
virtual void wake() override;
|
virtual void wake() override;
|
||||||
virtual void post_event(Core::EventReceiver& receiver, NonnullOwnPtr<Core::Event>&&) override;
|
virtual void post_event(Core::EventReceiver& receiver, NonnullOwnPtr<Core::Event>&&) override;
|
||||||
|
|
||||||
// FIXME: These APIs only exist for obscure use-cases inside SerenityOS. Try to get rid of them.
|
|
||||||
virtual void unquit() override { }
|
|
||||||
virtual bool was_exit_requested() const override { return false; }
|
virtual bool was_exit_requested() const override { return false; }
|
||||||
virtual void notify_forked_and_in_child() override { }
|
|
||||||
|
|
||||||
EventLoopThreadData& thread_data();
|
EventLoopThreadData& thread_data();
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ ErrorOr<int> service_main(int ipc_socket)
|
||||||
Core::EventLoop event_loop;
|
Core::EventLoop event_loop;
|
||||||
|
|
||||||
auto socket = TRY(Core::LocalSocket::adopt_fd(ipc_socket));
|
auto socket = TRY(Core::LocalSocket::adopt_fd(ipc_socket));
|
||||||
auto client = TRY(ImageDecoder::ConnectionFromClient::try_create(move(socket)));
|
auto client = TRY(ImageDecoder::ConnectionFromClient::try_create(make<IPC::Transport>(move(socket))));
|
||||||
|
|
||||||
return event_loop.exec();
|
return event_loop.exec();
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ namespace Ladybird {
|
||||||
jstring JavaEnvironment::jstring_from_ak_string(String const& str)
|
jstring JavaEnvironment::jstring_from_ak_string(String const& str)
|
||||||
{
|
{
|
||||||
auto as_utf16 = MUST(AK::utf8_to_utf16(str.code_points()));
|
auto as_utf16 = MUST(AK::utf8_to_utf16(str.code_points()));
|
||||||
return m_env->NewString(as_utf16.data(), as_utf16.size());
|
return m_env->NewString(reinterpret_cast<jchar const*>(as_utf16.data.data()), as_utf16.data.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
#include <AK/HashMap.h>
|
#include <AK/HashMap.h>
|
||||||
#include <AK/LexicalPath.h>
|
#include <AK/LexicalPath.h>
|
||||||
#include <AK/OwnPtr.h>
|
#include <AK/OwnPtr.h>
|
||||||
#include <LibArchive/TarStream.h>
|
|
||||||
#include <LibCore/DirIterator.h>
|
#include <LibCore/DirIterator.h>
|
||||||
#include <LibCore/Directory.h>
|
#include <LibCore/Directory.h>
|
||||||
#include <LibCore/EventLoop.h>
|
#include <LibCore/EventLoop.h>
|
||||||
|
@ -19,47 +18,52 @@
|
||||||
#include <LibCore/Timer.h>
|
#include <LibCore/Timer.h>
|
||||||
#include <LibFileSystem/FileSystem.h>
|
#include <LibFileSystem/FileSystem.h>
|
||||||
#include <LibWebView/Application.h>
|
#include <LibWebView/Application.h>
|
||||||
#include <UI/Utilities.h>
|
#include <LibWebView/Utilities.h>
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
|
|
||||||
static ErrorOr<void> extract_tar_archive(String archive_file, ByteString output_directory);
|
|
||||||
|
|
||||||
JavaVM* global_vm;
|
JavaVM* global_vm;
|
||||||
static OwnPtr<WebView::Application> s_application;
|
static OwnPtr<WebView::Application> s_application;
|
||||||
static OwnPtr<Core::EventLoop> s_main_event_loop;
|
static OwnPtr<Core::EventLoop> s_main_event_loop;
|
||||||
static jobject s_java_instance;
|
static jobject s_java_instance;
|
||||||
static jmethodID s_schedule_event_loop_method;
|
static jmethodID s_schedule_event_loop_method;
|
||||||
|
|
||||||
struct Application : public WebView::Application {
|
class Application : public WebView::Application {
|
||||||
WEB_VIEW_APPLICATION(Application);
|
WEB_VIEW_APPLICATION(Application);
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit Application();
|
||||||
};
|
};
|
||||||
|
|
||||||
Application::Application(Badge<WebView::Application>, Main::Arguments&)
|
Application::Application() = default;
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" JNIEXPORT void JNICALL
|
extern "C" JNIEXPORT void JNICALL
|
||||||
Java_org_serenityos_ladybird_LadybirdActivity_initNativeCode(JNIEnv*, jobject, jstring, jstring, jobject);
|
Java_org_serenityos_ladybird_LadybirdActivity_initNativeCode(JNIEnv*, jobject, jstring, jstring, jobject, jstring);
|
||||||
|
|
||||||
extern "C" JNIEXPORT void JNICALL
|
extern "C" JNIEXPORT void JNICALL
|
||||||
Java_org_serenityos_ladybird_LadybirdActivity_initNativeCode(JNIEnv* env, jobject thiz, jstring resource_dir, jstring tag_name, jobject timer_service)
|
Java_org_serenityos_ladybird_LadybirdActivity_initNativeCode(JNIEnv* env, jobject thiz, jstring resource_dir, jstring tag_name, jobject timer_service, jstring user_dir)
|
||||||
{
|
{
|
||||||
char const* raw_resource_dir = env->GetStringUTFChars(resource_dir, nullptr);
|
char const* raw_resource_dir = env->GetStringUTFChars(resource_dir, nullptr);
|
||||||
s_ladybird_resource_root = raw_resource_dir;
|
WebView::s_ladybird_resource_root = raw_resource_dir;
|
||||||
env->ReleaseStringUTFChars(resource_dir, raw_resource_dir);
|
env->ReleaseStringUTFChars(resource_dir, raw_resource_dir);
|
||||||
|
|
||||||
|
// While setting XDG environment variables in order to store user data may seem silly
|
||||||
|
// but in our case it seems to be the most rational idea.
|
||||||
|
char const* raw_user_dir = env->GetStringUTFChars(user_dir, nullptr);
|
||||||
|
setenv("XDG_CONFIG_HOME", ByteString::formatted("{}/config", raw_user_dir).characters(), 1);
|
||||||
|
setenv("XDG_DATA_HOME", ByteString::formatted("{}/userdata", raw_user_dir).characters(), 1);
|
||||||
|
env->ReleaseStringUTFChars(user_dir, raw_user_dir);
|
||||||
|
|
||||||
char const* raw_tag_name = env->GetStringUTFChars(tag_name, nullptr);
|
char const* raw_tag_name = env->GetStringUTFChars(tag_name, nullptr);
|
||||||
AK::set_log_tag_name(raw_tag_name);
|
AK::set_log_tag_name(raw_tag_name);
|
||||||
env->ReleaseStringUTFChars(tag_name, raw_tag_name);
|
env->ReleaseStringUTFChars(tag_name, raw_tag_name);
|
||||||
|
|
||||||
dbgln("Set resource dir to {}", s_ladybird_resource_root);
|
dbgln("Set resource dir to {}", WebView::s_ladybird_resource_root);
|
||||||
|
|
||||||
auto file_or_error = Core::System::open(MUST(String::formatted("{}/res/icons/48x48/app-browser.png", s_ladybird_resource_root)), O_RDONLY);
|
auto file_or_error = Core::System::open(MUST(String::formatted("{}/res/icons/48x48/app-browser.png", WebView::s_ladybird_resource_root)), O_RDONLY);
|
||||||
if (file_or_error.is_error()) {
|
if (file_or_error.is_error()) {
|
||||||
dbgln("No resource files, extracting assets...");
|
dbgln("No resource files, perhaps extracting went wrong?");
|
||||||
MUST(extract_tar_archive(MUST(String::formatted("{}/ladybird-assets.tar", s_ladybird_resource_root)), s_ladybird_resource_root));
|
|
||||||
} else {
|
} else {
|
||||||
dbgln("Found app-browser.png, not re-extracting assets.");
|
dbgln("Found app-browser.png");
|
||||||
dbgln("Hopefully no developer changed the asset files and expected them to be re-extracted!");
|
dbgln("Hopefully no developer changed the asset files and expected them to be re-extracted!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,7 +97,7 @@ Java_org_serenityos_ladybird_LadybirdActivity_initNativeCode(JNIEnv* env, jobjec
|
||||||
// FIXME: We are not making use of this Application object to track our processes.
|
// FIXME: We are not making use of this Application object to track our processes.
|
||||||
// So, right now, the Application's ProcessManager is constantly empty.
|
// So, right now, the Application's ProcessManager is constantly empty.
|
||||||
// (However, LibWebView depends on an Application object existing, so we do have to actually create one.)
|
// (However, LibWebView depends on an Application object existing, so we do have to actually create one.)
|
||||||
s_application = Application::create(arguments, "about:newtab"sv);
|
s_application = Application::create(arguments).release_value_but_fixme_should_propagate_errors();
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT void JNICALL
|
extern "C" JNIEXPORT void JNICALL
|
||||||
|
@ -120,139 +124,3 @@ Java_org_serenityos_ladybird_LadybirdActivity_disposeNativeCode(JNIEnv* env, job
|
||||||
|
|
||||||
delete &Core::EventLoopManager::the();
|
delete &Core::EventLoopManager::the();
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorOr<void> extract_tar_archive(String archive_file, ByteString output_directory)
|
|
||||||
{
|
|
||||||
constexpr size_t buffer_size = 4096;
|
|
||||||
|
|
||||||
auto file = TRY(Core::InputBufferedFile::create(TRY(Core::File::open(archive_file, Core::File::OpenMode::Read))));
|
|
||||||
|
|
||||||
ByteString old_pwd = TRY(Core::System::getcwd());
|
|
||||||
|
|
||||||
TRY(Core::System::chdir(output_directory));
|
|
||||||
ScopeGuard go_back = [&old_pwd] { MUST(Core::System::chdir(old_pwd)); };
|
|
||||||
|
|
||||||
auto tar_stream = TRY(Archive::TarInputStream::construct(move(file)));
|
|
||||||
|
|
||||||
HashMap<ByteString, ByteString> global_overrides;
|
|
||||||
HashMap<ByteString, ByteString> local_overrides;
|
|
||||||
|
|
||||||
auto get_override = [&](StringView key) -> Optional<ByteString> {
|
|
||||||
Optional<ByteString> maybe_local = local_overrides.get(key);
|
|
||||||
|
|
||||||
if (maybe_local.has_value())
|
|
||||||
return maybe_local;
|
|
||||||
|
|
||||||
Optional<ByteString> maybe_global = global_overrides.get(key);
|
|
||||||
|
|
||||||
if (maybe_global.has_value())
|
|
||||||
return maybe_global;
|
|
||||||
|
|
||||||
return {};
|
|
||||||
};
|
|
||||||
|
|
||||||
while (!tar_stream->finished()) {
|
|
||||||
Archive::TarFileHeader const& header = tar_stream->header();
|
|
||||||
|
|
||||||
// Handle meta-entries earlier to avoid consuming the file content stream.
|
|
||||||
if (header.content_is_like_extended_header()) {
|
|
||||||
switch (header.type_flag()) {
|
|
||||||
case Archive::TarFileType::GlobalExtendedHeader: {
|
|
||||||
TRY(tar_stream->for_each_extended_header([&](StringView key, StringView value) {
|
|
||||||
if (value.length() == 0)
|
|
||||||
global_overrides.remove(key);
|
|
||||||
else
|
|
||||||
global_overrides.set(key, value);
|
|
||||||
}));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Archive::TarFileType::ExtendedHeader: {
|
|
||||||
TRY(tar_stream->for_each_extended_header([&](StringView key, StringView value) {
|
|
||||||
local_overrides.set(key, value);
|
|
||||||
}));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
warnln("Unknown extended header type '{}' of {}", (char)header.type_flag(), header.filename());
|
|
||||||
VERIFY_NOT_REACHED();
|
|
||||||
}
|
|
||||||
|
|
||||||
TRY(tar_stream->advance());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Archive::TarFileStream file_stream = tar_stream->file_contents();
|
|
||||||
|
|
||||||
// Handle other header types that don't just have an effect on extraction.
|
|
||||||
switch (header.type_flag()) {
|
|
||||||
case Archive::TarFileType::LongName: {
|
|
||||||
StringBuilder long_name;
|
|
||||||
|
|
||||||
Array<u8, buffer_size> buffer;
|
|
||||||
|
|
||||||
while (!file_stream.is_eof()) {
|
|
||||||
auto slice = TRY(file_stream.read_some(buffer));
|
|
||||||
long_name.append(reinterpret_cast<char*>(slice.data()), slice.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
local_overrides.set("path", long_name.to_byte_string());
|
|
||||||
TRY(tar_stream->advance());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// None of the relevant headers, so continue as normal.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
LexicalPath path = LexicalPath(header.filename());
|
|
||||||
if (!header.prefix().is_empty())
|
|
||||||
path = path.prepend(header.prefix());
|
|
||||||
ByteString filename = get_override("path"sv).value_or(path.string());
|
|
||||||
|
|
||||||
ByteString absolute_path = TRY(FileSystem::absolute_path(filename));
|
|
||||||
auto parent_path = LexicalPath(absolute_path).parent();
|
|
||||||
auto header_mode = TRY(header.mode());
|
|
||||||
|
|
||||||
switch (header.type_flag()) {
|
|
||||||
case Archive::TarFileType::NormalFile:
|
|
||||||
case Archive::TarFileType::AlternateNormalFile: {
|
|
||||||
MUST(Core::Directory::create(parent_path, Core::Directory::CreateDirectories::Yes));
|
|
||||||
|
|
||||||
int fd = TRY(Core::System::open(absolute_path, O_CREAT | O_WRONLY, header_mode));
|
|
||||||
|
|
||||||
Array<u8, buffer_size> buffer;
|
|
||||||
while (!file_stream.is_eof()) {
|
|
||||||
auto slice = TRY(file_stream.read_some(buffer));
|
|
||||||
TRY(Core::System::write(fd, slice));
|
|
||||||
}
|
|
||||||
|
|
||||||
TRY(Core::System::close(fd));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Archive::TarFileType::SymLink: {
|
|
||||||
MUST(Core::Directory::create(parent_path, Core::Directory::CreateDirectories::Yes));
|
|
||||||
|
|
||||||
TRY(Core::System::symlink(header.link_name(), absolute_path));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Archive::TarFileType::Directory: {
|
|
||||||
MUST(Core::Directory::create(parent_path, Core::Directory::CreateDirectories::Yes));
|
|
||||||
|
|
||||||
auto result_or_error = Core::System::mkdir(absolute_path, header_mode);
|
|
||||||
if (result_or_error.is_error() && result_or_error.error().code() != EEXIST)
|
|
||||||
return result_or_error.release_error();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// FIXME: Implement other file types
|
|
||||||
warnln("file type '{}' of {} is not yet supported", (char)header.type_flag(), header.filename());
|
|
||||||
VERIFY_NOT_REACHED();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Non-global headers should be cleared after every file.
|
|
||||||
local_overrides.clear();
|
|
||||||
|
|
||||||
TRY(tar_stream->advance());
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
#include <AK/Atomic.h>
|
#include <AK/Atomic.h>
|
||||||
#include <AK/Format.h>
|
#include <AK/Format.h>
|
||||||
#include <LibCore/ResourceImplementationFile.h>
|
#include <LibCore/ResourceImplementationFile.h>
|
||||||
#include <UI/Utilities.h>
|
#include <LibWebView/Utilities.h>
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
|
|
||||||
JavaVM* global_vm;
|
JavaVM* global_vm;
|
||||||
|
@ -42,10 +42,11 @@ Java_org_serenityos_ladybird_LadybirdServiceBase_initNativeCode(JNIEnv* env, job
|
||||||
env->GetJavaVM(&global_vm);
|
env->GetJavaVM(&global_vm);
|
||||||
|
|
||||||
char const* raw_resource_dir = env->GetStringUTFChars(resource_dir, nullptr);
|
char const* raw_resource_dir = env->GetStringUTFChars(resource_dir, nullptr);
|
||||||
s_ladybird_resource_root = raw_resource_dir;
|
// FIXME: Don't set s_ladybird_resource_root on every service in order not to link with LibWebView.
|
||||||
|
WebView::s_ladybird_resource_root = raw_resource_dir;
|
||||||
env->ReleaseStringUTFChars(resource_dir, raw_resource_dir);
|
env->ReleaseStringUTFChars(resource_dir, raw_resource_dir);
|
||||||
// FIXME: Use a custom Android version that uses AssetManager to load files.
|
// FIXME: Use a custom Android version that uses AssetManager to load files.
|
||||||
Core::ResourceImplementation::install(make<Core::ResourceImplementationFile>(MUST(String::formatted("{}/res", s_ladybird_resource_root))));
|
Core::ResourceImplementation::install(make<Core::ResourceImplementationFile>(MUST(String::formatted("{}/res", WebView::s_ladybird_resource_root))));
|
||||||
|
|
||||||
char const* raw_tag_name = env->GetStringUTFChars(tag_name, nullptr);
|
char const* raw_tag_name = env->GetStringUTFChars(tag_name, nullptr);
|
||||||
AK::set_log_tag_name(raw_tag_name);
|
AK::set_log_tag_name(raw_tag_name);
|
||||||
|
|
|
@ -15,20 +15,24 @@
|
||||||
#include <LibFileSystem/FileSystem.h>
|
#include <LibFileSystem/FileSystem.h>
|
||||||
#include <LibIPC/SingleServer.h>
|
#include <LibIPC/SingleServer.h>
|
||||||
#include <LibTLS/TLSv12.h>
|
#include <LibTLS/TLSv12.h>
|
||||||
|
#include <LibWebView/Utilities.h>
|
||||||
#include <RequestServer/ConnectionFromClient.h>
|
#include <RequestServer/ConnectionFromClient.h>
|
||||||
#include <RequestServer/HttpProtocol.h>
|
|
||||||
#include <RequestServer/HttpsProtocol.h>
|
namespace RequestServer {
|
||||||
#include <UI/Utilities.h>
|
|
||||||
|
extern ByteString g_default_certificate_path;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
ErrorOr<int> service_main(int ipc_socket)
|
ErrorOr<int> service_main(int ipc_socket)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
RequestServer::g_default_certificate_path = ByteString::formatted("{}/res/ladybird/cacert.pem", WebView::s_ladybird_resource_root);
|
||||||
|
|
||||||
Core::EventLoop event_loop;
|
Core::EventLoop event_loop;
|
||||||
|
|
||||||
RequestServer::HttpProtocol::install();
|
|
||||||
RequestServer::HttpsProtocol::install();
|
|
||||||
|
|
||||||
auto socket = TRY(Core::LocalSocket::adopt_fd(ipc_socket));
|
auto socket = TRY(Core::LocalSocket::adopt_fd(ipc_socket));
|
||||||
auto client = TRY(RequestServer::ConnectionFromClient::try_create(move(socket)));
|
auto client = TRY(RequestServer::ConnectionFromClient::try_create(make<IPC::Transport>(move(socket))));
|
||||||
|
|
||||||
return event_loop.exec();
|
return event_loop.exec();
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,11 +24,11 @@
|
||||||
#include <LibWeb/PermissionsPolicy/AutoplayAllowlist.h>
|
#include <LibWeb/PermissionsPolicy/AutoplayAllowlist.h>
|
||||||
#include <LibWeb/Platform/AudioCodecPluginAgnostic.h>
|
#include <LibWeb/Platform/AudioCodecPluginAgnostic.h>
|
||||||
#include <LibWeb/Platform/EventLoopPluginSerenity.h>
|
#include <LibWeb/Platform/EventLoopPluginSerenity.h>
|
||||||
#include <LibWebView/RequestServerAdapter.h>
|
#include <LibWebView/HelperProcess.h>
|
||||||
#include <UI/FontPlugin.h>
|
#include <LibWebView/Plugins/FontPlugin.h>
|
||||||
#include <UI/HelperProcess.h>
|
#include <LibWebView/Plugins/ImageCodecPlugin.h>
|
||||||
#include <UI/ImageCodecPlugin.h>
|
#include <LibWebView/SiteIsolation.h>
|
||||||
#include <UI/Utilities.h>
|
#include <LibWebView/Utilities.h>
|
||||||
#include <WebContent/ConnectionFromClient.h>
|
#include <WebContent/ConnectionFromClient.h>
|
||||||
#include <WebContent/PageHost.h>
|
#include <WebContent/PageHost.h>
|
||||||
|
|
||||||
|
@ -53,21 +53,26 @@ ErrorOr<int> service_main(int ipc_socket)
|
||||||
Web::Platform::EventLoopPlugin::install(*new Web::Platform::EventLoopPluginSerenity);
|
Web::Platform::EventLoopPlugin::install(*new Web::Platform::EventLoopPluginSerenity);
|
||||||
|
|
||||||
auto image_decoder_client = TRY(bind_image_decoder_service());
|
auto image_decoder_client = TRY(bind_image_decoder_service());
|
||||||
Web::Platform::ImageCodecPlugin::install(*new Ladybird::ImageCodecPlugin(move(image_decoder_client)));
|
Web::Platform::ImageCodecPlugin::install(*new WebView::ImageCodecPlugin(move(image_decoder_client)));
|
||||||
|
|
||||||
Web::Platform::AudioCodecPlugin::install_creation_hook([](auto loader) {
|
Web::Platform::AudioCodecPlugin::install_creation_hook([](auto loader) {
|
||||||
return Web::Platform::AudioCodecPluginAgnostic::create(move(loader));
|
return Web::Platform::AudioCodecPluginAgnostic::create(move(loader));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Web::Bindings::initialize_main_thread_vm(Web::Bindings::AgentType::SimilarOriginWindow);
|
||||||
|
|
||||||
auto request_server_client = TRY(bind_request_server_service());
|
auto request_server_client = TRY(bind_request_server_service());
|
||||||
Web::ResourceLoader::initialize(TRY(WebView::RequestServerAdapter::try_create(move(request_server_client))));
|
Web::ResourceLoader::initialize(Web::Bindings::main_thread_vm().heap(), move(request_server_client));
|
||||||
|
|
||||||
bool is_layout_test_mode = false;
|
bool is_layout_test_mode = false;
|
||||||
|
|
||||||
Web::HTML::Window::set_internals_object_exposed(is_layout_test_mode);
|
Web::HTML::Window::set_internals_object_exposed(is_layout_test_mode);
|
||||||
Web::Platform::FontPlugin::install(*new Ladybird::FontPlugin(is_layout_test_mode));
|
Web::Platform::FontPlugin::install(*new WebView::FontPlugin(is_layout_test_mode));
|
||||||
|
|
||||||
TRY(Web::Bindings::initialize_main_thread_vm(Web::HTML::EventLoop::Type::Window));
|
// Currently site isolation doesn't work on Android since everything is running
|
||||||
|
// in the same process. It would require an entire redesign of this port
|
||||||
|
// in order to make it work. For now, it's better to just disable it.
|
||||||
|
WebView::disable_site_isolation();
|
||||||
|
|
||||||
auto maybe_content_filter_error = load_content_filters();
|
auto maybe_content_filter_error = load_content_filters();
|
||||||
if (maybe_content_filter_error.is_error())
|
if (maybe_content_filter_error.is_error())
|
||||||
|
@ -78,7 +83,7 @@ ErrorOr<int> service_main(int ipc_socket)
|
||||||
dbgln("Failed to load autoplay allowlist: {}", maybe_autoplay_allowlist_error.error());
|
dbgln("Failed to load autoplay allowlist: {}", maybe_autoplay_allowlist_error.error());
|
||||||
|
|
||||||
auto webcontent_socket = TRY(Core::LocalSocket::adopt_fd(ipc_socket));
|
auto webcontent_socket = TRY(Core::LocalSocket::adopt_fd(ipc_socket));
|
||||||
auto webcontent_client = TRY(WebContent::ConnectionFromClient::try_create(move(webcontent_socket)));
|
auto webcontent_client = TRY(WebContent::ConnectionFromClient::try_create(make<IPC::Transport>(move(webcontent_socket))));
|
||||||
|
|
||||||
return event_loop.exec();
|
return event_loop.exec();
|
||||||
}
|
}
|
||||||
|
@ -98,14 +103,14 @@ ErrorOr<NonnullRefPtr<Client>> bind_service(void (*bind_method)(int))
|
||||||
auto socket = TRY(Core::LocalSocket::adopt_fd(ui_fd));
|
auto socket = TRY(Core::LocalSocket::adopt_fd(ui_fd));
|
||||||
TRY(socket->set_blocking(true));
|
TRY(socket->set_blocking(true));
|
||||||
|
|
||||||
auto new_client = TRY(try_make_ref_counted<Client>(move(socket)));
|
auto new_client = TRY(try_make_ref_counted<Client>(make<IPC::Transport>(move(socket))));
|
||||||
|
|
||||||
return new_client;
|
return new_client;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ErrorOr<void> load_content_filters()
|
static ErrorOr<void> load_content_filters()
|
||||||
{
|
{
|
||||||
auto file_or_error = Core::File::open(ByteString::formatted("{}/res/ladybird/default-config/BrowserContentFilters.txt", s_ladybird_resource_root), Core::File::OpenMode::Read);
|
auto file_or_error = Core::File::open(ByteString::formatted("{}/res/ladybird/default-config/BrowserContentFilters.txt", WebView::s_ladybird_resource_root), Core::File::OpenMode::Read);
|
||||||
if (file_or_error.is_error())
|
if (file_or_error.is_error())
|
||||||
return file_or_error.release_error();
|
return file_or_error.release_error();
|
||||||
|
|
||||||
|
@ -132,7 +137,7 @@ static ErrorOr<void> load_content_filters()
|
||||||
|
|
||||||
static ErrorOr<void> load_autoplay_allowlist()
|
static ErrorOr<void> load_autoplay_allowlist()
|
||||||
{
|
{
|
||||||
auto file_or_error = Core::File::open(TRY(String::formatted("{}/res/ladybird/default-config/BrowserAutoplayAllowlist.txt", s_ladybird_resource_root)), Core::File::OpenMode::Read);
|
auto file_or_error = Core::File::open(TRY(String::formatted("{}/res/ladybird/default-config/BrowserAutoplayAllowlist.txt", WebView::s_ladybird_resource_root)), Core::File::OpenMode::Read);
|
||||||
if (file_or_error.is_error())
|
if (file_or_error.is_error())
|
||||||
return file_or_error.release_error();
|
return file_or_error.release_error();
|
||||||
|
|
||||||
|
@ -152,7 +157,7 @@ static ErrorOr<void> load_autoplay_allowlist()
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& autoplay_allowlist = Web::PermissionsPolicy::AutoplayAllowlist::the();
|
auto& autoplay_allowlist = Web::PermissionsPolicy::AutoplayAllowlist::the();
|
||||||
TRY(autoplay_allowlist.enable_for_origins(origins));
|
autoplay_allowlist.enable_for_origins(origins);
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
#include "WebViewImplementationNative.h"
|
#include "WebViewImplementationNative.h"
|
||||||
#include "JNIHelpers.h"
|
#include "JNIHelpers.h"
|
||||||
#include <LibGfx/Bitmap.h>
|
#include <LibGfx/Bitmap.h>
|
||||||
#include <LibGfx/DeprecatedPainter.h>
|
#include <LibGfx/ImmutableBitmap.h>
|
||||||
|
#include <LibGfx/Painter.h>
|
||||||
#include <LibWeb/Crypto/Crypto.h>
|
#include <LibWeb/Crypto/Crypto.h>
|
||||||
#include <LibWebView/ViewImplementation.h>
|
#include <LibWebView/ViewImplementation.h>
|
||||||
#include <LibWebView/WebContentClient.h>
|
#include <LibWebView/WebContentClient.h>
|
||||||
|
@ -20,7 +21,7 @@ static Gfx::BitmapFormat to_gfx_bitmap_format(i32 f)
|
||||||
{
|
{
|
||||||
switch (f) {
|
switch (f) {
|
||||||
case ANDROID_BITMAP_FORMAT_RGBA_8888:
|
case ANDROID_BITMAP_FORMAT_RGBA_8888:
|
||||||
return Gfx::BitmapFormat::BGRA8888;
|
return Gfx::BitmapFormat::RGBA8888;
|
||||||
default:
|
default:
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
@ -62,6 +63,8 @@ void WebViewImplementationNative::initialize_client(WebView::ViewImplementation:
|
||||||
|
|
||||||
client().async_set_device_pixels_per_css_pixel(0, m_device_pixel_ratio);
|
client().async_set_device_pixels_per_css_pixel(0, m_device_pixel_ratio);
|
||||||
|
|
||||||
|
set_system_visibility_state(Web::HTML::VisibilityState::Visible);
|
||||||
|
|
||||||
// FIXME: update_palette, update system fonts
|
// FIXME: update_palette, update system fonts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,25 +74,11 @@ void WebViewImplementationNative::paint_into_bitmap(void* android_bitmap_raw, An
|
||||||
VERIFY((info.flags & ANDROID_BITMAP_FLAGS_IS_HARDWARE) == 0);
|
VERIFY((info.flags & ANDROID_BITMAP_FLAGS_IS_HARDWARE) == 0);
|
||||||
|
|
||||||
auto android_bitmap = MUST(Gfx::Bitmap::create_wrapper(to_gfx_bitmap_format(info.format), Gfx::AlphaType::Premultiplied, { info.width, info.height }, info.stride, android_bitmap_raw));
|
auto android_bitmap = MUST(Gfx::Bitmap::create_wrapper(to_gfx_bitmap_format(info.format), Gfx::AlphaType::Premultiplied, { info.width, info.height }, info.stride, android_bitmap_raw));
|
||||||
Gfx::DeprecatedPainter painter(android_bitmap);
|
auto painter = Gfx::Painter::create(android_bitmap);
|
||||||
if (auto* bitmap = m_client_state.has_usable_bitmap ? m_client_state.front_bitmap.bitmap.ptr() : m_backup_bitmap.ptr())
|
if (auto* bitmap = m_client_state.has_usable_bitmap ? m_client_state.front_bitmap.bitmap.ptr() : m_backup_bitmap.ptr())
|
||||||
painter.blit({ 0, 0 }, *bitmap, bitmap->rect());
|
painter->draw_bitmap(android_bitmap->rect().to_type<float>(), Gfx::ImmutableBitmap::create(MUST(bitmap->clone())), bitmap->rect(), Gfx::ScalingMode::NearestNeighbor, {}, 1.0f, Gfx::CompositingAndBlendingOperator::Copy);
|
||||||
else
|
else
|
||||||
painter.clear_rect(painter.clip_rect(), Gfx::Color::Magenta);
|
painter->fill_rect(android_bitmap->rect().to_type<float>(), Gfx::Color::Magenta);
|
||||||
|
|
||||||
// Convert our internal BGRA into RGBA. This will be slowwwwwww
|
|
||||||
// FIXME: Don't do a color format swap here.
|
|
||||||
for (auto y = 0; y < android_bitmap->height(); ++y) {
|
|
||||||
auto* scanline = android_bitmap->scanline(y);
|
|
||||||
for (auto x = 0; x < android_bitmap->width(); ++x) {
|
|
||||||
auto current_pixel = scanline[x];
|
|
||||||
u32 alpha = (current_pixel & 0xFF000000U) >> 24;
|
|
||||||
u32 red = (current_pixel & 0x00FF0000U) >> 16;
|
|
||||||
u32 green = (current_pixel & 0x0000FF00U) >> 8;
|
|
||||||
u32 blue = (current_pixel & 0x000000FFU);
|
|
||||||
scanline[x] = (alpha << 24U) | (blue << 16U) | (green << 8U) | red;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebViewImplementationNative::set_viewport_geometry(int w, int h)
|
void WebViewImplementationNative::set_viewport_geometry(int w, int h)
|
||||||
|
@ -139,7 +128,7 @@ NonnullRefPtr<WebView::WebContentClient> WebViewImplementationNative::bind_web_c
|
||||||
auto socket = MUST(Core::LocalSocket::adopt_fd(ui_fd));
|
auto socket = MUST(Core::LocalSocket::adopt_fd(ui_fd));
|
||||||
MUST(socket->set_blocking(true));
|
MUST(socket->set_blocking(true));
|
||||||
|
|
||||||
auto new_client = make_ref_counted<WebView::WebContentClient>(move(socket), *this);
|
auto new_client = make_ref_counted<WebView::WebContentClient>(make<IPC::Transport>(move(socket)), *this);
|
||||||
|
|
||||||
return new_client;
|
return new_client;
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,7 +102,7 @@ Java_org_serenityos_ladybird_WebViewImplementation_nativeLoadURL(JNIEnv* env, jo
|
||||||
char const* raw_url = env->GetStringUTFChars(url, nullptr);
|
char const* raw_url = env->GetStringUTFChars(url, nullptr);
|
||||||
auto ak_url = URL::create_with_url_or_path(StringView { raw_url, strlen(raw_url) });
|
auto ak_url = URL::create_with_url_or_path(StringView { raw_url, strlen(raw_url) });
|
||||||
env->ReleaseStringUTFChars(url, raw_url);
|
env->ReleaseStringUTFChars(url, raw_url);
|
||||||
impl->load(ak_url);
|
impl->load(ak_url.release_value());
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT void JNICALL
|
extern "C" JNIEXPORT void JNICALL
|
||||||
|
|
|
@ -15,7 +15,7 @@ class ImageDecoderService : LadybirdServiceBase("ImageDecoderService") {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
init {
|
init {
|
||||||
System.loadLibrary("imagedecoder")
|
System.loadLibrary("imagedecoderservice")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,11 @@ import android.view.inputmethod.EditorInfo
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import org.serenityos.ladybird.databinding.ActivityMainBinding
|
import org.serenityos.ladybird.databinding.ActivityMainBinding
|
||||||
|
import java.io.BufferedInputStream
|
||||||
|
import java.io.BufferedOutputStream
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.util.zip.ZipFile
|
||||||
|
|
||||||
class LadybirdActivity : AppCompatActivity() {
|
class LadybirdActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
@ -26,7 +31,29 @@ class LadybirdActivity : AppCompatActivity() {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
resourceDir = TransferAssets.transferAssets(this)
|
resourceDir = TransferAssets.transferAssets(this)
|
||||||
initNativeCode(resourceDir, "Ladybird", timerService)
|
val testFile = File("$resourceDir/res/icons/48x48/app-browser.png")
|
||||||
|
if (!testFile.exists())
|
||||||
|
{
|
||||||
|
ZipFile("$resourceDir/ladybird-assets.zip").use { zip ->
|
||||||
|
zip.entries().asSequence().forEach { entry ->
|
||||||
|
val fileName = entry.name
|
||||||
|
val file = File("$resourceDir/$fileName")
|
||||||
|
if (!entry.isDirectory)
|
||||||
|
{
|
||||||
|
val parentFolder = File(file.parent!!)
|
||||||
|
if (!parentFolder.exists())
|
||||||
|
parentFolder.mkdirs()
|
||||||
|
zip.getInputStream(entry).use { input ->
|
||||||
|
file.outputStream().use { output ->
|
||||||
|
input.copyTo(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val userDir = applicationContext.getExternalFilesDir(null)!!.absolutePath;
|
||||||
|
initNativeCode(resourceDir, "Ladybird", timerService, userDir)
|
||||||
|
|
||||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
@ -63,7 +90,7 @@ class LadybirdActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private external fun initNativeCode(
|
private external fun initNativeCode(
|
||||||
resourceDir: String, tag: String, timerService: TimerExecutorService
|
resourceDir: String, tag: String, timerService: TimerExecutorService, userDir: String
|
||||||
)
|
)
|
||||||
|
|
||||||
private external fun disposeNativeCode()
|
private external fun disposeNativeCode()
|
||||||
|
|
|
@ -15,7 +15,7 @@ class RequestServerService : LadybirdServiceBase("RequestServerService") {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
init {
|
init {
|
||||||
System.loadLibrary("requestserver")
|
System.loadLibrary("requestserverservice")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,11 +27,11 @@ public class TransferAssets {
|
||||||
Context applicationContext = context.getApplicationContext();
|
Context applicationContext = context.getApplicationContext();
|
||||||
File assetDir = applicationContext.getFilesDir();
|
File assetDir = applicationContext.getFilesDir();
|
||||||
AssetManager assetManager = applicationContext.getAssets();
|
AssetManager assetManager = applicationContext.getAssets();
|
||||||
if (!copyAsset(assetManager, "ladybird-assets.tar", assetDir.getAbsolutePath() + "/ladybird-assets.tar")) {
|
if (!copyAsset(assetManager, "ladybird-assets.zip", assetDir.getAbsolutePath() + "/ladybird-assets.zip")) {
|
||||||
Log.e("Ladybird", "Unable to copy assets");
|
Log.e("Ladybird", "Unable to copy assets");
|
||||||
return "Invalid Assets, this won't work";
|
return "Invalid Assets, this won't work";
|
||||||
}
|
}
|
||||||
Log.d("Ladybird", "Copied ladybird-assets.tar to app-specific storage path");
|
Log.d("Ladybird", "Copied ladybird-assets.zip to app-specific storage path");
|
||||||
return assetDir.getAbsolutePath();
|
return assetDir.getAbsolutePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,7 @@ class WebContentService : LadybirdServiceBase("WebContentService") {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
init {
|
init {
|
||||||
System.loadLibrary("webcontent")
|
System.loadLibrary("webcontentservice")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,13 +15,11 @@ macro(copy_res_folder folder)
|
||||||
)
|
)
|
||||||
add_dependencies(archive-assets copy-${folder})
|
add_dependencies(archive-assets copy-${folder})
|
||||||
endmacro()
|
endmacro()
|
||||||
add_custom_target(archive-assets COMMAND ${CMAKE_COMMAND} -E chdir asset-bundle tar cf ../ladybird-assets.tar ./ )
|
add_custom_target(archive-assets COMMAND ${CMAKE_COMMAND} -E chdir asset-bundle zip -r ../ladybird-assets.zip ./ )
|
||||||
copy_res_folder(ladybird)
|
copy_res_folder(ladybird)
|
||||||
copy_res_folder(html)
|
|
||||||
copy_res_folder(fonts)
|
copy_res_folder(fonts)
|
||||||
copy_res_folder(icons)
|
copy_res_folder(icons)
|
||||||
copy_res_folder(emoji)
|
|
||||||
copy_res_folder(themes)
|
copy_res_folder(themes)
|
||||||
add_custom_target(copy-assets COMMAND ${CMAKE_COMMAND} -E copy_if_different ladybird-assets.tar "${CMAKE_SOURCE_DIR}/UI/Android/src/main/assets/")
|
add_custom_target(copy-assets COMMAND ${CMAKE_COMMAND} -E copy_if_different ladybird-assets.zip "${CMAKE_SOURCE_DIR}/UI/Android/src/main/assets/")
|
||||||
add_dependencies(copy-assets archive-assets)
|
add_dependencies(copy-assets archive-assets)
|
||||||
add_dependencies(ladybird copy-assets)
|
add_dependencies(ladybird copy-assets)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue