1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-09 23:34:20 +00:00

CMake: Add embedded Linux subprocess for WebView support

In order to display a WebKit based webview a plugin will deploy a
temporary standalone executable on the system and host the WebKit
instance inside that.
This commit is contained in:
attila 2023-02-03 12:54:58 +01:00 committed by Attila Szarvas
parent e4a86316ca
commit f9ff497978
6 changed files with 204 additions and 31 deletions

View file

@ -160,6 +160,7 @@ install(FILES "${JUCE_BINARY_DIR}/JUCEConfigVersion.cmake"
"${JUCE_CMAKE_UTILS_DIR}/checkBundleSigning.cmake"
"${JUCE_CMAKE_UTILS_DIR}/copyDir.cmake"
"${JUCE_CMAKE_UTILS_DIR}/juce_runtime_arch_detection.cpp"
"${JUCE_CMAKE_UTILS_DIR}/juce_LinuxSubprocessHelper.cpp"
DESTINATION "${JUCE_INSTALL_DESTINATION}")
if("${CMAKE_SOURCE_DIR}" STREQUAL "${JUCE_SOURCE_DIR}")
@ -170,4 +171,3 @@ if("${CMAKE_SOURCE_DIR}" STREQUAL "${JUCE_SOURCE_DIR}")
install(EXPORT LV2_HELPER NAMESPACE juce:: DESTINATION "${JUCE_INSTALL_DESTINATION}")
endif()
endif()

View file

@ -764,6 +764,21 @@ This function sets the `CMAKE_<LANG>_FLAGS_<MODE>` to empty in the current direc
allowing alternative optimisation/debug flags to be supplied without conflicting with the
CMake-supplied defaults.
#### `juce_link_with_embedded_linux_subprocess`
juce_link_with_embedded_linux_subprocess(<target>)
This function links the provided target with an interface library that generates a barebones
standalone executable file and embeds it as a binary resource. This binary resource is only used
by the `juce_gui_extra` module and only when its `JUCE_WEB_BROWSER` capability is enabled. This
executable will then be deployed into a temporary file only when the code is running in a
non-standalone format, and will be used to host a WebKit view. This technique is used by audio
plugins on Linux.
This function is automatically called if necessary for all targets created by one of the JUCE target
creation functions i.e. `juce_add_gui_app`, `juce_add_console_app` and `juce_add_gui_app`. You don't
need to call this function manually in these cases.
### Targets
#### `juce::juce_recommended_warning_flags`

View file

@ -147,6 +147,86 @@ endfunction()
# ==================================================================================================
function(_juce_create_linux_subprocess_helper_target)
if(TARGET juce_linux_subprocess_helper)
return()
endif()
set(source "${JUCE_CMAKE_UTILS_DIR}/juce_LinuxSubprocessHelper.cpp")
add_executable(juce_linux_subprocess_helper ${source})
add_executable(juce::juce_linux_subprocess_helper ALIAS juce_linux_subprocess_helper)
target_compile_features(juce_linux_subprocess_helper PRIVATE cxx_std_17)
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
target_link_libraries(juce_linux_subprocess_helper PRIVATE Threads::Threads ${CMAKE_DL_LIBS})
endfunction()
function(_juce_create_embedded_linux_subprocess_target output_target_name target)
# This library needs to be created in every directory where a target wants to link against it.
# Pre CMake 3.20 the GENERATED property is only visible in the directory of the current target,
# and even post 3.20, CMake will not know how to generate the sources outside this directory.
set(target_directory_key JUCE_EMBEDDED_LINUX_SUBPROCESS_TARGET)
get_directory_property(embedded_linux_subprocess_target ${target_directory_key})
if(embedded_linux_subprocess_target)
set(${output_target_name} juce::${embedded_linux_subprocess_target} PARENT_SCOPE)
return()
endif()
set(prefix "_juce_directory_prefix")
set(embedded_linux_subprocess_target ${prefix}_embedded_linux_subprocess)
while(TARGET ${embedded_linux_subprocess_target})
set(embedded_linux_subprocess_target ${prefix}_${embedded_linux_subprocess_target})
endwhile()
_juce_create_linux_subprocess_helper_target()
get_target_property(generated_sources_directory ${target} JUCE_GENERATED_SOURCES_DIRECTORY)
if(generated_sources_directory)
set(juce_linux_subprocess_helper_binary_dir "${generated_sources_directory}")
else()
set(juce_linux_subprocess_helper_binary_dir "${CMAKE_CURRENT_BINARY_DIR}/juce_LinuxSubprocessHelper")
endif()
set(binary_header_file "${juce_linux_subprocess_helper_binary_dir}/juce_LinuxSubprocessHelperBinaryData.h")
set(binary_source_file "${juce_linux_subprocess_helper_binary_dir}/juce_LinuxSubprocessHelperBinaryData.cpp")
set(juceaide_input_file "${juce_linux_subprocess_helper_binary_dir}/input_file_list")
file(GENERATE OUTPUT ${juceaide_input_file} CONTENT "$<TARGET_FILE:juce_linux_subprocess_helper>")
file(MAKE_DIRECTORY ${juce_linux_subprocess_helper_binary_dir})
add_custom_command(
OUTPUT
${binary_header_file}
${binary_source_file}
COMMAND juce::juceaide binarydata "LinuxSubprocessHelperBinaryData" "${binary_header_file}"
${juce_linux_subprocess_helper_binary_dir} "${juceaide_input_file}"
COMMAND
${CMAKE_COMMAND} -E rename "${juce_linux_subprocess_helper_binary_dir}/BinaryData1.cpp" "${binary_source_file}"
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
DEPENDS juce_linux_subprocess_helper
VERBATIM)
add_library(${embedded_linux_subprocess_target} INTERFACE)
target_sources(${embedded_linux_subprocess_target} INTERFACE ${binary_source_file})
target_include_directories(${embedded_linux_subprocess_target} INTERFACE ${juce_linux_subprocess_helper_binary_dir})
target_compile_definitions(${embedded_linux_subprocess_target} INTERFACE JUCE_USE_EXTERNAL_TEMPORARY_SUBPROCESS=1)
add_library(juce::${embedded_linux_subprocess_target} ALIAS ${embedded_linux_subprocess_target})
set_directory_properties(PROPERTIES ${target_directory_key} ${embedded_linux_subprocess_target})
set(${output_target_name} juce::${embedded_linux_subprocess_target} PARENT_SCOPE)
endfunction()
function(juce_link_with_embedded_linux_subprocess target)
_juce_create_embedded_linux_subprocess_target(embedded_linux_subprocess_target ${target})
target_link_libraries(${target} PRIVATE ${embedded_linux_subprocess_target})
endfunction()
# ==================================================================================================
# Ideally, we'd check the preprocessor defs on the target to see whether
# JUCE_USE_CURL, JUCE_WEB_BROWSER, or JUCE_IN_APP_PURCHASES have been explicitly turned off,
# and then link libraries as appropriate.
@ -167,6 +247,12 @@ function(_juce_link_optional_libraries target)
if(needs_browser)
target_link_libraries(${target} PRIVATE juce::pkgconfig_JUCE_BROWSER_LINUX_DEPS)
get_target_property(is_plugin ${target} JUCE_IS_PLUGIN)
if(is_plugin)
juce_link_with_embedded_linux_subprocess(${target})
endif()
endif()
elseif(APPLE)
get_target_property(needs_storekit ${target} JUCE_NEEDS_STORE_KIT)
@ -846,10 +932,9 @@ function(_juce_add_lv2_manifest_helper_target)
add_executable(juce::juce_lv2_helper ALIAS juce_lv2_helper)
target_compile_features(juce_lv2_helper PRIVATE cxx_std_17)
set_target_properties(juce_lv2_helper PROPERTIES BUILD_WITH_INSTALL_RPATH ON)
target_link_libraries(juce_lv2_helper PRIVATE ${CMAKE_DL_LIBS})
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
target_link_libraries(juce_lv2_helper PRIVATE Threads::Threads)
target_link_libraries(juce_lv2_helper PRIVATE Threads::Threads ${CMAKE_DL_LIBS})
endfunction()
# ==================================================================================================

View file

@ -0,0 +1,36 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include <dlfcn.h>
int main (int argc, const char* const* argv)
{
if (argc >= 3)
if (auto* handle = dlopen (argv[1], RTLD_LAZY))
if (auto* function = reinterpret_cast<int (*) (int, const char* const*)> (dlsym (handle, argv[2])))
return function (argc - 3, argv + 3);
return 1;
}

View file

@ -184,7 +184,7 @@ StringArray JUCE_CALLTYPE JUCEApplicationBase::getCommandLineParameterArray()
#endif
#if (JUCE_LINUX || JUCE_BSD) && JUCE_MODULE_AVAILABLE_juce_gui_extra && (! defined(JUCE_WEB_BROWSER) || JUCE_WEB_BROWSER)
extern int juce_gtkWebkitMain (int argc, const char* argv[]);
extern "C" int juce_gtkWebkitMain (int argc, const char* const* argv);
#endif
#if JUCE_WINDOWS
@ -231,7 +231,7 @@ int JUCEApplicationBase::main (int argc, const char* argv[])
initialiseNSApplication();
#endif
#if (JUCE_LINUX || JUCE_BSD) && JUCE_MODULE_AVAILABLE_juce_gui_extra && (! defined(JUCE_WEB_BROWSER) || JUCE_WEB_BROWSER)
#if (JUCE_LINUX || JUCE_BSD) && JUCE_MODULE_AVAILABLE_juce_gui_extra && (! defined (JUCE_WEB_BROWSER) || JUCE_WEB_BROWSER)
if (argc >= 2 && String (argv[1]) == "--juce-gtkwebkitfork-child")
return juce_gtkWebkitMain (argc, argv);
#endif

View file

@ -23,6 +23,10 @@
==============================================================================
*/
#if JUCE_USE_EXTERNAL_TEMPORARY_SUBPROCESS
#include "juce_LinuxSubprocessHelperBinaryData.h"
#endif
namespace juce
{
@ -215,7 +219,7 @@ private:
JUCE_IMPLEMENT_SINGLETON (WebKitSymbols)
//==============================================================================
extern int juce_gtkWebkitMain (int argc, const char* argv[]);
extern "C" int juce_gtkWebkitMain (int argc, const char* const* argv);
class CommandReceiver
{
@ -776,34 +780,68 @@ private:
ret = pipe (outPipe);
jassert (ret == 0);
std::vector<String> arguments;
#if JUCE_USE_EXTERNAL_TEMPORARY_SUBPROCESS
if (! JUCEApplicationBase::isStandaloneApp())
{
subprocessFile.emplace ("_juce_linux_subprocess");
const auto externalSubprocessAvailable = subprocessFile->getFile().replaceWithData (LinuxSubprocessHelperBinaryData::juce_linux_subprocess_helper,
LinuxSubprocessHelperBinaryData::juce_linux_subprocess_helperSize)
&& subprocessFile->getFile().setExecutePermission (true);
ignoreUnused (externalSubprocessAvailable);
jassert (externalSubprocessAvailable);
/* The external subprocess will load the .so specified as its first argument and execute
the function specified by the second. The remaining arguments will be passed on to
the function.
*/
arguments.emplace_back (subprocessFile->getFile().getFullPathName());
arguments.emplace_back (File::getSpecialLocation (File::currentExecutableFile).getFullPathName());
arguments.emplace_back ("juce_gtkWebkitMain");
}
#endif
arguments.emplace_back (File::getSpecialLocation (File::currentExecutableFile).getFullPathName());
arguments.emplace_back ("--juce-gtkwebkitfork-child");
arguments.emplace_back (outPipe[0]);
arguments.emplace_back (inPipe [1]);
if (userAgent.isNotEmpty())
arguments.emplace_back (userAgent);
std::vector<const char*> argv (arguments.size() + 1, nullptr);
std::transform (arguments.begin(), arguments.end(), argv.begin(), [] (const auto& arg)
{
return arg.toRawUTF8();
});
auto pid = fork();
if (pid == 0)
{
close (inPipe[0]);
close (outPipe[1]);
StringArray arguments;
arguments.add (File::getSpecialLocation (File::currentExecutableFile).getFullPathName());
arguments.add ("--juce-gtkwebkitfork-child");
arguments.add (String (outPipe[0]));
arguments.add (String (inPipe [1]));
if (userAgent.isNotEmpty())
arguments.add (userAgent);
std::vector<const char*> argv;
argv.reserve (static_cast<std::size_t> (arguments.size() + 1));
for (const auto& arg : arguments)
argv.push_back (arg.toRawUTF8());
argv.push_back (nullptr);
if (JUCEApplicationBase::isStandaloneApp())
{
execv (arguments[0].toRawUTF8(), (char**) argv.data());
}
else
juce_gtkWebkitMain (arguments.size(), (const char**) argv.data());
{
#if JUCE_USE_EXTERNAL_TEMPORARY_SUBPROCESS
execv (arguments[0].toRawUTF8(), (char**) argv.data());
#else
// After a fork in a multithreaded program, the child can only safely call
// async-signal-safe functions until it calls execv, but if we reached this point
// then execv won't be called at all! The following call is unsafe, and is very
// likely to lead to unexpected behaviour.
jassertfalse;
juce_gtkWebkitMain ((int) arguments.size(), argv.data());
#endif
}
exit (0);
}
@ -881,12 +919,10 @@ private:
void handleCommand (const String& cmd, const var& params) override
{
MessageManager::callAsync ([liveness = std::weak_ptr<int> (livenessProbe), this, cmd, params]()
MessageManager::callAsync ([liveness = std::weak_ptr (livenessProbe), this, cmd, params]
{
if (liveness.lock() == nullptr)
return;
handleCommandOnMessageThread (cmd, params);
if (liveness.lock() != nullptr)
handleCommandOnMessageThread (cmd, params);
});
}
@ -903,6 +939,7 @@ private:
std::unique_ptr<XEmbedComponent> xembed;
std::shared_ptr<int> livenessProbe = std::make_shared<int> (0);
std::vector<pollfd> pfds;
std::optional<TemporaryFile> subprocessFile;
};
//==============================================================================
@ -1015,7 +1052,7 @@ bool WebBrowserComponent::areOptionsSupported (const Options& options)
return (options.getBackend() == Options::Backend::defaultBackend);
}
int juce_gtkWebkitMain (int argc, const char* argv[])
extern "C" __attribute__ ((visibility ("default"))) int juce_gtkWebkitMain (int argc, const char* const* argv)
{
if (argc < 4)
return -1;