added Linux arm64 SDK
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
project(Tools C CXX)
|
||||
cmake_minimum_required(VERSION 3.15.0)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
list(PREPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake)
|
||||
include(Common)
|
||||
|
||||
set(INSTALL_DIR "Tools")
|
||||
|
||||
add_subdirectory("MiniBrowser")
|
||||
|
||||
set(TOOLS_TARGETS MiniBrowser)
|
||||
|
||||
if (${ALLINONE_BUILD})
|
||||
foreach(target ${TOOLS_TARGETS})
|
||||
add_dependencies(${target} GenerateSDK)
|
||||
endforeach()
|
||||
|
||||
add_custom_target(install_tools ALL "${CMAKE_COMMAND}"
|
||||
-P "${CMAKE_CURRENT_BINARY_DIR}/cmake_install.cmake"
|
||||
DEPENDS ${TOOLS_TARGETS})
|
||||
endif ()
|
||||
@@ -0,0 +1,20 @@
|
||||
# EXPERIMENTAL:
|
||||
# Set this to TRUE to embed assets directly into the executable.
|
||||
set(SHOULD_EMBED_FILES FALSE)
|
||||
|
||||
set(SOURCE_FILES "src/Browser.h"
|
||||
"src/Browser.cpp"
|
||||
"src/Page.h"
|
||||
"src/Page.cpp"
|
||||
"src/UI.h"
|
||||
"src/UI.cpp"
|
||||
"src/main.cpp")
|
||||
|
||||
if (SHOULD_EMBED_FILES)
|
||||
set(SOURCE_FILES ${SOURCE_FILES}
|
||||
"src/platform/EmbeddedFileSystem.h"
|
||||
"src/platform/EmbeddedFileSystem.cpp")
|
||||
add_app(MiniBrowser EMBED_FILES NEEDS_INSPECTOR SOURCES ${SOURCE_FILES})
|
||||
else ()
|
||||
add_app(MiniBrowser NEEDS_INSPECTOR SOURCES ${SOURCE_FILES})
|
||||
endif ()
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,12 @@
|
||||
<svg version="1.1" id="loader-1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="14px" height="14px" viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||
<path fill="#888" d="M43.935,25.145c0-10.318-8.364-18.683-18.683-18.683c-10.318,0-18.683,8.365-18.683,18.683h4.068c0-8.071,6.543-14.615,14.615-14.615c8.072,0,14.615,6.543,14.615,14.615H43.935z">
|
||||
<animateTransform attributeType="xml"
|
||||
attributeName="transform"
|
||||
type="rotate"
|
||||
from="0 25 25"
|
||||
to="360 25 25"
|
||||
dur="0.6s"
|
||||
repeatCount="indefinite"/>
|
||||
</path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 646 B |
@@ -0,0 +1,57 @@
|
||||
* { overflow: hidden; user-select: none; -webkit-user-select: none; }
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: "Segoe UI", -apple-system, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
background: white;
|
||||
}
|
||||
#bar {
|
||||
background: rgba(236, 236, 236, 1.0);
|
||||
height: 32px;
|
||||
padding: 4px 9px;
|
||||
border-bottom: 0.8px solid rgb(200, 200, 200);
|
||||
cursor: default;
|
||||
display: flex;
|
||||
}
|
||||
#address {
|
||||
border-radius: 24px;
|
||||
border: none;
|
||||
height: 32px;
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
color: #667;
|
||||
background-color: #FAFAFA;
|
||||
display: inline-block;
|
||||
padding: 2px 16px 2px 16px;
|
||||
margin-top: 0px;
|
||||
margin-left:9px;
|
||||
user-select: auto !important;
|
||||
-webkit-user-select: text !important;
|
||||
}
|
||||
#address:focus {
|
||||
background-color: #FFFFFF;
|
||||
color: #223;
|
||||
}
|
||||
.icon {
|
||||
cursor: default;
|
||||
fill: rgba(90, 90, 90, 1.0);
|
||||
vertical-align: top;
|
||||
margin-top: 4px;
|
||||
margin-left:9px;
|
||||
padding: 2px;
|
||||
height: 22px;
|
||||
}
|
||||
#back {
|
||||
margin-left:0px;
|
||||
}
|
||||
.icon.disabled {
|
||||
fill: rgba(200, 200, 200, 1.0);
|
||||
}
|
||||
svg#icon_defs path {
|
||||
fill: inherit;
|
||||
}
|
||||
.icon:not(.disabled):hover {
|
||||
background-color: rgba(220, 220, 220, 1.0);
|
||||
border-radius: 2px;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>MiniBrowser Address Bar</title>
|
||||
<link rel="stylesheet" type="text/css" href="ui.css">
|
||||
<script src="ui.js"></script>
|
||||
<script src="anchorme.js"></script>
|
||||
</head>
|
||||
|
||||
<body style="">
|
||||
<svg style="display: none;">
|
||||
<defs>
|
||||
<g id="svg_arrow_back"><path d="M427 277v-42h-260l119 -120l-30 -30l-171 171l171 171l30 -30l-119 -120h260z" /></g>
|
||||
<g id="svg_arrow_forward"><path d="M256 427l171 -171l-171 -171l-30 30l119 120h-260v42h260l-119 120z" /></g>
|
||||
<g id="svg_refresh"><path transform="matrix(1 0 0 -1 0 512)" d="M377 377l50 50v-150h-150l69 69c-23 23 -55 38 -90 38c-71 0 -128 -57 -128 -128s57 -128 128 -128c56 0 104 35 121 85h44c-19 -74 -85 -128 -165 -128c-94 0 -170 77 -170 171s76 171 170 171c47 0 90 -19 121 -50z" /></g>
|
||||
<g id="svg_stop"><path d="M405 375l-119 -119l119 -119l-30 -30l-119 119l-119 -119l-30 30l119 119l-119 119l30 30l119 -119l119 119z" /></g>
|
||||
<g id="svg_tools"><path d="M22.7 19l-9.1-9.1c.9-2.3.4-5-1.5-6.9-2-2-5-2.4-7.4-1.3L9 6 6 9 1.6 4.7C.4 7.1.9 10.1 2.9 12.1c1.9 1.9 4.6 2.4 6.9 1.5l9.1 9.1c.4.4 1 .4 1.4 0l2.3-2.3c.5-.4.5-1.1.1-1.4z"/></g>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
<div id="bar">
|
||||
<span id="back" class="icon disabled">
|
||||
<svg viewBox="0 0 512 512" width="20" height="20"><use xlink:href="#svg_arrow_back"/></svg>
|
||||
</span>
|
||||
<span id="forward" class="icon disabled">
|
||||
<svg viewBox="0 0 512 512" width="20" height="20"><use xlink:href="#svg_arrow_forward"/></svg>
|
||||
</span>
|
||||
<span id="refresh" class="icon">
|
||||
<svg viewBox="0 0 512 512" width="20" height="20"><use xlink:href="#svg_refresh"/></svg>
|
||||
</span>
|
||||
<span id="stop" class="icon" style="display: none;">
|
||||
<svg viewBox="0 0 512 512" width="20" height="20"><use xlink:href="#svg_stop"/></svg>
|
||||
</span>
|
||||
<input onfocus="select_next_mouseup = true;" onmouseup="if (select_next_mouseup) this.select(); select_next_mouseup = false;" type="text" id="address"></input>
|
||||
<span id="toggle-tools" class="icon">
|
||||
<svg viewBox="0 0 24 24" width="18" height="18"><use xlink:href="#svg_tools"/></svg>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
function bindCallbacks() {
|
||||
document.querySelector('#back').addEventListener('click', event => OnBack());
|
||||
document.querySelector('#forward').addEventListener('click', event => OnForward());
|
||||
document.querySelector('#refresh').addEventListener('click', event => OnRefresh());
|
||||
document.querySelector('#stop').addEventListener('click', event => OnStop());
|
||||
document.querySelector('#toggle-tools').addEventListener('click', event => OnToggleTools());
|
||||
var address = document.querySelector('#address');
|
||||
address.onkeypress = (function(e) {
|
||||
if (e.which == '13') {
|
||||
address.blur();
|
||||
let url = address.value;
|
||||
if (anchorme.validate.url(url) || anchorme.validate.ip(url)) {
|
||||
if (url.toLowerCase().startsWith("http://") || url.toLowerCase().startsWith("https://")) {
|
||||
OnRequestChangeURL(url);
|
||||
} else {
|
||||
OnRequestChangeURL("http://" + url);
|
||||
}
|
||||
} else if (url.toLowerCase().startsWith("file:///")) {
|
||||
OnRequestChangeURL(url);
|
||||
} else if (url.toLowerCase().startsWith("inspector://")) {
|
||||
OnRequestChangeURL(url);
|
||||
} else if (url.toLowerCase().startsWith("file://")) {
|
||||
OnRequestChangeURL("file:///" + url.substring(7));
|
||||
} else {
|
||||
// Interpret as search
|
||||
OnRequestChangeURL("https://www.google.com/search?q=" + encodeURIComponent(url));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bindCallbacks();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,27 @@
|
||||
function updateBack(enable) {
|
||||
if (enable)
|
||||
document.getElementById("back").classList.remove("disabled");
|
||||
else
|
||||
document.getElementById("back").classList.add("disabled");
|
||||
}
|
||||
|
||||
function updateForward(enable) {
|
||||
if (enable)
|
||||
document.getElementById("forward").classList.remove("disabled");
|
||||
else
|
||||
document.getElementById("forward").classList.add("disabled");
|
||||
}
|
||||
|
||||
function updateLoading(is_loading) {
|
||||
if (is_loading) {
|
||||
document.getElementById("refresh").style.display = "none";
|
||||
document.getElementById("stop").style.display = "inline-block";
|
||||
} else {
|
||||
document.getElementById("refresh").style.display = "inline-block";
|
||||
document.getElementById("stop").style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
function updateURL(url) {
|
||||
document.getElementById('address').value = url;
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>MiniBrowser Welcome Page</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style type="text/css">
|
||||
/* Basic Resets */
|
||||
body, button, input, select, textarea {
|
||||
font-family: BlinkMacSystemFont, -apple-system, 'Segoe UI', 'Roboto', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Color scheme and layout */
|
||||
body {
|
||||
background-color: #ebf1ff;
|
||||
color: #333;
|
||||
display: flex;
|
||||
justify-content: center; /* Center horizontally */
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
align-items: flex-start; /* Align items at the start inside the container */
|
||||
flex-wrap: wrap; /* Wrap items when necessary */
|
||||
width: 90%; /* Responsive width */
|
||||
max-width: 800px; /* Maximum width */
|
||||
}
|
||||
|
||||
section {
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
margin: 10px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.1); /* Subtle shadow for depth */
|
||||
width: 100%; /* Responsive width */
|
||||
}
|
||||
|
||||
h2.title {
|
||||
color: #0077cc; /* Accent color for title */
|
||||
padding: 0.5em 0.5em;
|
||||
}
|
||||
|
||||
h3, h4 {
|
||||
color: #333; /* Dark gray color for better readability */
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin-top: 2em; /* Spacing before headers */
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none; /* Remove default list style */
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: #f3f7ff; /* Light gray background for <pre> tags */
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
overflow: auto; /* Adds scrollbar if needed */
|
||||
color: #0077cc;
|
||||
}
|
||||
|
||||
code {
|
||||
color: #0077cc;
|
||||
}
|
||||
|
||||
/* Interactive elements */
|
||||
section:hover {
|
||||
box-shadow: 0 8px 16px rgba(0,0,0,0.2); /* Larger shadow on hover */
|
||||
transition: box-shadow 0.3s ease-in-out;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<h2 class="title">MiniBrowser</h2>
|
||||
<section class="usage">
|
||||
<h3>About</h3>
|
||||
<p>MiniBrowser can be used to test, inspect, and profile web-content using the Ultralight renderer.</p>
|
||||
|
||||
<h4>Local HTML Files</h4>
|
||||
<p>
|
||||
Files can be placed inside the <code>/assets/</code> folder and accessed via:
|
||||
<pre> file:///filename.html</pre>
|
||||
</p>
|
||||
<h4>Web Inspector</h4>
|
||||
<p>Press the wrench icon in the upper-right to inspect the current page.</p>
|
||||
</section>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,56 @@
|
||||
#include "Browser.h"
|
||||
#include "UI.h"
|
||||
#include <Ultralight/Renderer.h>
|
||||
#include <Ultralight/platform/Config.h>
|
||||
#include <Ultralight/platform/Platform.h>
|
||||
#include <sstream>
|
||||
|
||||
Browser::Browser(bool use_gpu, bool force_repaint, bool show_stats)
|
||||
{
|
||||
Settings settings;
|
||||
settings.force_cpu_renderer = !use_gpu;
|
||||
|
||||
Config config;
|
||||
config.scroll_timer_delay = 1.0 / 90.0;
|
||||
config.force_repaint = force_repaint;
|
||||
|
||||
#ifdef UL_EMBED_FILES
|
||||
// Use our custom FileSystem
|
||||
file_system_ = std::make_unique<EmbeddedFileSystem>();
|
||||
Platform::instance().set_file_system(file_system_.get());
|
||||
#endif
|
||||
|
||||
app_ = App::Create(settings, config);
|
||||
|
||||
window_ = Window::Create(app_->main_monitor(), 1024, 768, false,
|
||||
kWindowFlags_Resizable | kWindowFlags_Titled | kWindowFlags_Maximizable);
|
||||
if (show_stats)
|
||||
window_->EnableFrameStatistics();
|
||||
SetTitle("Welcome!");
|
||||
|
||||
// Create the UI
|
||||
ui_.reset(new UI(this));
|
||||
window_->set_listener(ui_.get());
|
||||
}
|
||||
|
||||
Browser::~Browser()
|
||||
{
|
||||
window_->set_listener(nullptr);
|
||||
|
||||
ui_.reset();
|
||||
|
||||
window_ = nullptr;
|
||||
app_ = nullptr;
|
||||
}
|
||||
|
||||
void Browser::Run()
|
||||
{
|
||||
app_->Run();
|
||||
}
|
||||
|
||||
void Browser::SetTitle(const String& title)
|
||||
{
|
||||
std::ostringstream str;
|
||||
str << "Ultralight MiniBrowser - " << title.utf8().data();
|
||||
window_->SetTitle(str.str().c_str());
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
#include <AppCore/AppCore.h>
|
||||
#include <memory>
|
||||
#ifdef UL_EMBED_FILES
|
||||
#include "platform/EmbeddedFileSystem.h"
|
||||
#endif
|
||||
|
||||
using namespace ultralight;
|
||||
|
||||
class UI;
|
||||
|
||||
class Browser {
|
||||
public:
|
||||
Browser(bool use_gpu, bool force_repaint, bool show_stats);
|
||||
~Browser();
|
||||
|
||||
void Run();
|
||||
|
||||
void SetTitle(const String& title);
|
||||
|
||||
RefPtr<App> app() { return app_; }
|
||||
RefPtr<Window> window() { return window_; }
|
||||
|
||||
protected:
|
||||
RefPtr<App> app_;
|
||||
RefPtr<Window> window_;
|
||||
std::unique_ptr<UI> ui_;
|
||||
#ifdef UL_EMBED_FILES
|
||||
std::unique_ptr<EmbeddedFileSystem> file_system_;
|
||||
#endif
|
||||
};
|
||||
@@ -0,0 +1,287 @@
|
||||
#include "Page.h"
|
||||
#include "UI.h"
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#define INSPECTOR_DRAG_HANDLE_HEIGHT 10
|
||||
|
||||
Page::Page(UI* ui, uint32_t width, uint32_t height, int x, int y)
|
||||
: ui_(ui)
|
||||
, container_width_(width)
|
||||
, container_height_(height)
|
||||
{
|
||||
overlay_ = Overlay::Create(ui->window(), width, height, x, y);
|
||||
view()->set_view_listener(this);
|
||||
view()->set_load_listener(this);
|
||||
view()->set_download_listener(this);
|
||||
view()->set_network_listener(this);
|
||||
}
|
||||
|
||||
Page::~Page()
|
||||
{
|
||||
view()->set_network_listener(nullptr);
|
||||
view()->set_download_listener(nullptr);
|
||||
view()->set_view_listener(nullptr);
|
||||
view()->set_load_listener(nullptr);
|
||||
}
|
||||
|
||||
void Page::Show()
|
||||
{
|
||||
overlay_->Show();
|
||||
overlay_->Focus();
|
||||
|
||||
if (inspector_overlay_)
|
||||
inspector_overlay_->Show();
|
||||
}
|
||||
|
||||
void Page::Hide()
|
||||
{
|
||||
overlay_->Hide();
|
||||
overlay_->Unfocus();
|
||||
|
||||
if (inspector_overlay_)
|
||||
inspector_overlay_->Hide();
|
||||
}
|
||||
|
||||
void Page::ToggleInspector()
|
||||
{
|
||||
if (!inspector_overlay_) {
|
||||
view()->CreateLocalInspectorView();
|
||||
} else {
|
||||
if (inspector_overlay_->is_hidden()) {
|
||||
inspector_overlay_->Show();
|
||||
} else {
|
||||
inspector_overlay_->Hide();
|
||||
}
|
||||
}
|
||||
|
||||
// Force resize to update layout
|
||||
Resize(container_width_, container_height_);
|
||||
}
|
||||
|
||||
bool Page::IsInspectorShowing() const
|
||||
{
|
||||
if (!inspector_overlay_)
|
||||
return false;
|
||||
|
||||
return !inspector_overlay_->is_hidden();
|
||||
}
|
||||
|
||||
IntRect Page::GetInspectorResizeDragHandle() const
|
||||
{
|
||||
if (!IsInspectorShowing())
|
||||
return IntRect::MakeEmpty();
|
||||
|
||||
int drag_handle_height_px = (uint32_t)std::round(INSPECTOR_DRAG_HANDLE_HEIGHT * ui_->window()->scale());
|
||||
|
||||
// This drag handle should span the width of the UI and be centered vertically at the boundary between
|
||||
// the page overlay and inspector overlay.
|
||||
|
||||
int drag_handle_x = (int)inspector_overlay_->x();
|
||||
int drag_handle_y = (int)inspector_overlay_->y() - drag_handle_height_px / 2;
|
||||
|
||||
return { drag_handle_x, drag_handle_y, drag_handle_x + (int)inspector_overlay_->width(),
|
||||
drag_handle_y + drag_handle_height_px };
|
||||
}
|
||||
|
||||
int Page::GetInspectorHeight() const
|
||||
{
|
||||
if (inspector_overlay_)
|
||||
return inspector_overlay_->height();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Page::SetInspectorHeight(int height)
|
||||
{
|
||||
if (height > 2) {
|
||||
inspector_overlay_->Resize(inspector_overlay_->width(), height);
|
||||
|
||||
// Trigger a resize to perform re-layout / re-size of content overlay
|
||||
Resize(container_width_, container_height_);
|
||||
}
|
||||
}
|
||||
|
||||
void Page::Resize(uint32_t width, uint32_t height)
|
||||
{
|
||||
container_width_ = width;
|
||||
container_height_ = height;
|
||||
|
||||
uint32_t content_height = container_height_;
|
||||
if (inspector_overlay_ && !inspector_overlay_->is_hidden()) {
|
||||
uint32_t inspector_height_px = inspector_overlay_->height();
|
||||
content_height -= inspector_height_px;
|
||||
}
|
||||
|
||||
if (content_height < 1)
|
||||
content_height = 1;
|
||||
|
||||
overlay_->Resize(container_width_, content_height);
|
||||
|
||||
if (inspector_overlay_ && !inspector_overlay_->is_hidden()) {
|
||||
inspector_overlay_->Resize(container_width_, inspector_overlay_->height());
|
||||
inspector_overlay_->MoveTo(0, overlay_->y() + overlay_->height());
|
||||
}
|
||||
}
|
||||
|
||||
void Page::OnChangeTitle(View* caller, const String& title)
|
||||
{
|
||||
ui_->SetTitle(title);
|
||||
}
|
||||
|
||||
void Page::OnChangeURL(View* caller, const String& url)
|
||||
{
|
||||
ui_->SetURL(url);
|
||||
}
|
||||
|
||||
void Page::OnChangeTooltip(View* caller, const String& tooltip) {}
|
||||
|
||||
void Page::OnChangeCursor(View* caller, Cursor cursor)
|
||||
{
|
||||
ui_->SetCursor(cursor);
|
||||
}
|
||||
|
||||
void Page::OnAddConsoleMessage(View* caller, const ConsoleMessage& msg)
|
||||
{
|
||||
std::cout << "[OnAddConsoleMessage]\n\t"
|
||||
<< "\n\tsource:\t" << (uint32_t)msg.source()
|
||||
<< "\n\ttype:\t" << (uint32_t)msg.type()
|
||||
<< "\n\tlevel:\t" << (uint32_t)msg.level()
|
||||
<< "\n\tmessage:\t" << msg.message().utf8().data()
|
||||
<< "\n\tline_number:\t" << msg.line_number()
|
||||
<< "\n\tcolumn_number:\t" << msg.column_number()
|
||||
<< "\n\tsource_id:\t" << msg.source_id().utf8().data()
|
||||
<< "\n\tnum_arguments:\t" << msg.num_arguments() << std::endl;
|
||||
|
||||
uint32_t num_args = msg.num_arguments();
|
||||
if (num_args > 0) {
|
||||
SetJSContext(msg.argument_context());
|
||||
for (uint32_t i = 0; i < num_args; i++) {
|
||||
String arg_str = JSValue(msg.argument_at(i)).ToString();
|
||||
std::cout << "\n\t[" << i << "]:\t" << arg_str.utf8().data();
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
RefPtr<View> Page::OnCreateChildView(ultralight::View* caller,
|
||||
const String& opener_url, const String& target_url,
|
||||
bool is_popup, const IntRect& popup_rect)
|
||||
{
|
||||
|
||||
// TODO: handle child view in new window
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<View> Page::OnCreateInspectorView(ultralight::View* caller, bool is_local,
|
||||
const String& inspected_url)
|
||||
{
|
||||
if (inspector_overlay_)
|
||||
return nullptr;
|
||||
|
||||
inspector_overlay_ = Overlay::Create(ui_->window(), container_width_, container_height_ / 2, 0, 0);
|
||||
|
||||
// Force resize to update layout
|
||||
Resize(container_width_, container_height_);
|
||||
inspector_overlay_->Show();
|
||||
|
||||
return inspector_overlay_->view();
|
||||
}
|
||||
|
||||
void Page::OnBeginLoading(View* caller, uint64_t frame_id, bool is_main_frame, const String& url)
|
||||
{
|
||||
ui_->UpdatePageNavigation(caller->is_loading(), caller->CanGoBack(), caller->CanGoForward());
|
||||
|
||||
std::cout << "[OnBeginLoading]\n\t"
|
||||
<< "\n\tframe_id:\t" << frame_id
|
||||
<< "\n\tis_main_frame:\t" << is_main_frame
|
||||
<< "\n\turl:\t" << url.utf8().data()
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
void Page::OnFinishLoading(View* caller, uint64_t frame_id, bool is_main_frame, const String& url)
|
||||
{
|
||||
ui_->UpdatePageNavigation(caller->is_loading(), caller->CanGoBack(), caller->CanGoForward());
|
||||
|
||||
std::cout << "[OnFinishLoading]\n\t"
|
||||
<< "\n\tframe_id:\t" << frame_id << "\n\tis_main_frame:\t" << is_main_frame
|
||||
<< "\n\turl:\t" << url.utf8().data() << std::endl;
|
||||
}
|
||||
|
||||
void Page::OnFailLoading(View* caller, uint64_t frame_id, bool is_main_frame, const String& url,
|
||||
const String& description, const String& error_domain, int error_code)
|
||||
{
|
||||
if (is_main_frame) {
|
||||
char error_code_str[16];
|
||||
sprintf(error_code_str, "%d", error_code);
|
||||
|
||||
String html_string = "<html><head><style>";
|
||||
html_string += "* { font-family: sans-serif; }";
|
||||
html_string += "body { background-color: #CCC; color: #555; padding: 4em; }";
|
||||
html_string += "dt { font-weight: bold; padding: 1em; }";
|
||||
html_string += "</style></head><body>";
|
||||
html_string += "<h2>A Network Error was Encountered</h2>";
|
||||
html_string += "<dl>";
|
||||
html_string += "<dt>URL</dt><dd>" + url + "</dd>";
|
||||
html_string += "<dt>Description</dt><dd>" + description + "</dd>";
|
||||
html_string += "<dt>Error Domain</dt><dd>" + error_domain + "</dd>";
|
||||
html_string += "<dt>Error Code</dt><dd>" + String(error_code_str) + "</dd>";
|
||||
html_string += "</dl></body></html>";
|
||||
|
||||
view()->LoadHTML(html_string);
|
||||
}
|
||||
|
||||
std::cout << "[OnFailLoading]\n\t"
|
||||
<< "\n\tframe_id:\t" << frame_id << "\n\tis_main_frame:\t" << is_main_frame
|
||||
<< "\n\turl:\t" << url.utf8().data() << "\n\tdescription:\t"
|
||||
<< description.utf8().data() << "\n\terror_domain:\t" << error_domain.utf8().data()
|
||||
<< "\n\terror_code:\t" << error_code << std::endl;
|
||||
}
|
||||
|
||||
void Page::OnWindowObjectReady(ultralight::View* caller, uint64_t frame_id, bool is_main_frame,
|
||||
const String& url)
|
||||
{
|
||||
std::cout << "[OnWindowObjectReady]\n\t"
|
||||
<< "\n\tframe_id:\t" << frame_id << "\n\tis_main_frame:\t" << is_main_frame
|
||||
<< "\n\turl:\t" << url.utf8().data() << std::endl;
|
||||
}
|
||||
|
||||
void Page::OnDOMReady(ultralight::View* caller, uint64_t frame_id, bool is_main_frame,
|
||||
const String& url)
|
||||
{
|
||||
std::cout << "[OnDOMReady]\n\t"
|
||||
<< "\n\tframe_id:\t" << frame_id << "\n\tis_main_frame:\t" << is_main_frame
|
||||
<< "\n\turl:\t" << url.utf8().data() << std::endl;
|
||||
}
|
||||
|
||||
void Page::OnUpdateHistory(View* caller)
|
||||
{
|
||||
ui_->UpdatePageNavigation(caller->is_loading(), caller->CanGoBack(), caller->CanGoForward());
|
||||
}
|
||||
|
||||
bool Page::OnRequestDownload(View* caller, DownloadId id, const String& url)
|
||||
{
|
||||
printf("Page::OnRequestDownload [%d] %s\n", id, url.utf8().data());
|
||||
return true;
|
||||
}
|
||||
|
||||
void Page::OnBeginDownload(View* caller, DownloadId id, const String& url, const String& filename,
|
||||
int64_t expected_content_length)
|
||||
{
|
||||
printf("Page::OnBeginDownload [%d] %s\n", id, url.utf8().data());
|
||||
}
|
||||
|
||||
void Page::OnReceiveDataForDownload(View* caller, DownloadId id, RefPtr<Buffer> data)
|
||||
{
|
||||
printf("Page::OnReceiveDataForDownload [%d]\n", id);
|
||||
}
|
||||
|
||||
void Page::OnFinishDownload(View* caller, DownloadId id)
|
||||
{
|
||||
printf("Page::OnFinishDownload [%d]\n", id);
|
||||
}
|
||||
|
||||
void Page::OnFailDownload(View* caller, DownloadId id)
|
||||
{
|
||||
printf("Page::OnFailDownload [%d]\n", id);
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
#include <AppCore/AppCore.h>
|
||||
#include <Ultralight/Listener.h>
|
||||
|
||||
class UI;
|
||||
using namespace ultralight;
|
||||
|
||||
/**
|
||||
* Browser Page implementation. Renders the actual page content in bottom pane.
|
||||
*/
|
||||
class Page : public ViewListener,
|
||||
public LoadListener,
|
||||
public DownloadListener,
|
||||
public NetworkListener {
|
||||
public:
|
||||
Page(UI* ui, uint32_t width, uint32_t height, int x, int y);
|
||||
~Page();
|
||||
|
||||
void set_ready_to_close(bool ready) { ready_to_close_ = ready; }
|
||||
bool ready_to_close() { return ready_to_close_; }
|
||||
|
||||
RefPtr<View> view() { return overlay_->view(); }
|
||||
|
||||
void Show();
|
||||
|
||||
void Hide();
|
||||
|
||||
void ToggleInspector();
|
||||
|
||||
bool IsInspectorShowing() const;
|
||||
|
||||
IntRect GetInspectorResizeDragHandle() const;
|
||||
|
||||
int GetInspectorHeight() const;
|
||||
|
||||
void SetInspectorHeight(int height);
|
||||
|
||||
void Resize(uint32_t width, uint32_t height);
|
||||
|
||||
// Inherited from ViewListener
|
||||
virtual void OnChangeTitle(View* caller, const String& title) override;
|
||||
virtual void OnChangeURL(View* caller, const String& url) override;
|
||||
virtual void OnChangeTooltip(View* caller, const String& tooltip) override;
|
||||
virtual void OnChangeCursor(View* caller, Cursor cursor) override;
|
||||
virtual void OnAddConsoleMessage(View* caller, const ConsoleMessage& msg) override;
|
||||
virtual RefPtr<View> OnCreateChildView(ultralight::View* caller,
|
||||
const String& opener_url, const String& target_url,
|
||||
bool is_popup, const IntRect& popup_rect) override;
|
||||
virtual RefPtr<View> OnCreateInspectorView(ultralight::View* caller, bool is_local,
|
||||
const String& inspected_url) override;
|
||||
|
||||
// Inherited from LoadListener
|
||||
virtual void OnBeginLoading(View* caller, uint64_t frame_id,
|
||||
bool is_main_frame, const String& url) override;
|
||||
virtual void OnFinishLoading(View* caller, uint64_t frame_id,
|
||||
bool is_main_frame, const String& url) override;
|
||||
virtual void OnFailLoading(View* caller, uint64_t frame_id,
|
||||
bool is_main_frame, const String& url, const String& description,
|
||||
const String& error_domain, int error_code) override;
|
||||
virtual void OnWindowObjectReady(View* caller, uint64_t frame_id, bool is_main_frame,
|
||||
const String& url) override;
|
||||
virtual void OnDOMReady(View* caller, uint64_t frame_id, bool is_main_frame,
|
||||
const String& url) override;
|
||||
virtual void OnUpdateHistory(View* caller) override;
|
||||
|
||||
// Inherited from DownloadListener
|
||||
virtual DownloadId NextDownloadId(View* caller) override { return next_download_id_++; }
|
||||
virtual bool OnRequestDownload(View* caller, DownloadId id,
|
||||
const String& url) override;
|
||||
virtual void OnBeginDownload(View* caller, DownloadId id, const String& url,
|
||||
const String& filename, int64_t expected_content_length) override;
|
||||
|
||||
virtual void OnReceiveDataForDownload(View* caller, DownloadId id,
|
||||
RefPtr<Buffer> data) override;
|
||||
virtual void OnFinishDownload(View* caller, DownloadId id) override;
|
||||
virtual void OnFailDownload(View* caller, DownloadId id) override;
|
||||
|
||||
// Inherited from NetworkListener
|
||||
virtual bool OnNetworkRequest(View* caller, NetworkRequest& request) override { return true; }
|
||||
|
||||
protected:
|
||||
UI* ui_;
|
||||
RefPtr<Overlay> overlay_;
|
||||
RefPtr<Overlay> inspector_overlay_;
|
||||
bool ready_to_close_ = false;
|
||||
uint32_t container_width_, container_height_;
|
||||
DownloadId next_download_id_ = 0;
|
||||
};
|
||||
@@ -0,0 +1,212 @@
|
||||
#include "UI.h"
|
||||
|
||||
static UI* g_ui = 0;
|
||||
|
||||
#define UI_HEIGHT 41
|
||||
|
||||
UI::UI(Browser* browser)
|
||||
: browser_(browser)
|
||||
, cur_cursor_(Cursor::kCursor_Pointer)
|
||||
, is_resizing_inspector_(false)
|
||||
, is_over_inspector_resize_drag_handle_(false)
|
||||
{
|
||||
uint32_t window_width = window()->width();
|
||||
ui_height_ = (uint32_t)std::round(UI_HEIGHT * window()->scale());
|
||||
overlay_ = Overlay::Create(window(), window_width, ui_height_, 0, 0);
|
||||
g_ui = this;
|
||||
|
||||
view()->set_load_listener(this);
|
||||
view()->set_view_listener(this);
|
||||
view()->LoadURL("file:///ui/ui.html");
|
||||
}
|
||||
|
||||
UI::~UI()
|
||||
{
|
||||
view()->set_load_listener(nullptr);
|
||||
view()->set_view_listener(nullptr);
|
||||
g_ui = nullptr;
|
||||
}
|
||||
|
||||
bool UI::OnKeyEvent(const ultralight::KeyEvent& evt)
|
||||
{
|
||||
// Consume all F2 key events
|
||||
if (evt.virtual_key_code == KeyCodes::GK_F2) {
|
||||
if (evt.type == KeyEvent::kType_RawKeyDown) {
|
||||
App::instance()->renderer()->StartRemoteInspectorServer("0.0.0.0", 7676);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UI::OnMouseEvent(const ultralight::MouseEvent& evt)
|
||||
{
|
||||
if (page_ && page_->IsInspectorShowing()) {
|
||||
float x_px = std::round(evt.x * window()->scale());
|
||||
float y_px = std::round(evt.y * window()->scale());
|
||||
|
||||
if (is_resizing_inspector_) {
|
||||
int resize_delta = inspector_resize_begin_mouse_y_ - y_px;
|
||||
int new_inspector_height = inspector_resize_begin_height_ + resize_delta;
|
||||
page_->SetInspectorHeight(new_inspector_height);
|
||||
|
||||
if (evt.type == MouseEvent::kType_MouseUp) {
|
||||
is_resizing_inspector_ = false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
IntRect drag_handle = page_->GetInspectorResizeDragHandle();
|
||||
|
||||
bool over_drag_handle = drag_handle.Contains(Point(x_px, y_px));
|
||||
|
||||
if (over_drag_handle && !is_over_inspector_resize_drag_handle_) {
|
||||
// We entered the drag area
|
||||
window()->SetCursor(Cursor::kCursor_NorthSouthResize);
|
||||
is_over_inspector_resize_drag_handle_ = true;
|
||||
} else if (!over_drag_handle && is_over_inspector_resize_drag_handle_) {
|
||||
// We left the drag area, restore previous cursor
|
||||
window()->SetCursor(cur_cursor_);
|
||||
is_over_inspector_resize_drag_handle_ = false;
|
||||
}
|
||||
|
||||
if (over_drag_handle && evt.type == MouseEvent::kType_MouseDown && !is_resizing_inspector_) {
|
||||
is_resizing_inspector_ = true;
|
||||
inspector_resize_begin_mouse_y_ = y_px;
|
||||
inspector_resize_begin_height_ = page_->GetInspectorHeight();
|
||||
}
|
||||
|
||||
return !over_drag_handle;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void UI::OnClose(ultralight::Window* window) { App::instance()->Quit(); }
|
||||
|
||||
void UI::OnResize(ultralight::Window* window, uint32_t width, uint32_t height)
|
||||
{
|
||||
int page_height = window->height() - ui_height_;
|
||||
|
||||
if (page_height < 1)
|
||||
page_height = 1;
|
||||
|
||||
overlay_->Resize(window->width(), ui_height_);
|
||||
|
||||
if (page())
|
||||
page()->Resize(window->width(), (uint32_t)page_height);
|
||||
}
|
||||
|
||||
void UI::OnDOMReady(View* caller, uint64_t frame_id, bool is_main_frame, const String& url)
|
||||
{
|
||||
// Set the context for all subsequent JS* calls
|
||||
RefPtr<JSContext> locked_context = view()->LockJSContext();
|
||||
SetJSContext(locked_context->ctx());
|
||||
|
||||
JSObject global = JSGlobalObject();
|
||||
updateBack = global["updateBack"];
|
||||
updateForward = global["updateForward"];
|
||||
updateLoading = global["updateLoading"];
|
||||
updateURL = global["updateURL"];
|
||||
|
||||
global["OnBack"] = BindJSCallback(&UI::OnBack);
|
||||
global["OnForward"] = BindJSCallback(&UI::OnForward);
|
||||
global["OnRefresh"] = BindJSCallback(&UI::OnRefresh);
|
||||
global["OnStop"] = BindJSCallback(&UI::OnStop);
|
||||
global["OnToggleTools"] = BindJSCallback(&UI::OnToggleTools);
|
||||
global["OnRequestChangeURL"] = BindJSCallback(&UI::OnRequestChangeURL);
|
||||
|
||||
CreatePage();
|
||||
}
|
||||
|
||||
void UI::OnBack(const JSObject& obj, const JSArgs& args)
|
||||
{
|
||||
if (page())
|
||||
page()->view()->GoBack();
|
||||
}
|
||||
|
||||
void UI::OnForward(const JSObject& obj, const JSArgs& args)
|
||||
{
|
||||
if (page())
|
||||
page()->view()->GoForward();
|
||||
}
|
||||
|
||||
void UI::OnRefresh(const JSObject& obj, const JSArgs& args)
|
||||
{
|
||||
if (page())
|
||||
page()->view()->Reload();
|
||||
}
|
||||
|
||||
void UI::OnStop(const JSObject& obj, const JSArgs& args)
|
||||
{
|
||||
if (page())
|
||||
page()->view()->Stop();
|
||||
}
|
||||
|
||||
void UI::OnToggleTools(const JSObject& obj, const JSArgs& args)
|
||||
{
|
||||
if (page())
|
||||
page()->ToggleInspector();
|
||||
}
|
||||
|
||||
void UI::OnRequestChangeURL(const JSObject& obj, const JSArgs& args)
|
||||
{
|
||||
if (args.size() == 1) {
|
||||
ultralight::String url = args[0];
|
||||
|
||||
if (page())
|
||||
page()->view()->LoadURL(url);
|
||||
}
|
||||
}
|
||||
|
||||
void UI::CreatePage()
|
||||
{
|
||||
int page_height = window()->height() - ui_height_;
|
||||
if (page_height < 1)
|
||||
page_height = 1;
|
||||
page_.reset(new Page(this, window()->width(), (uint32_t)page_height, 0, ui_height_));
|
||||
page_->view()->LoadURL("file:///ui/welcome.html");
|
||||
}
|
||||
|
||||
void UI::UpdatePageNavigation(bool is_loading, bool can_go_back, bool can_go_forward)
|
||||
{
|
||||
SetLoading(is_loading);
|
||||
SetCanGoBack(can_go_back);
|
||||
SetCanGoForward(can_go_forward);
|
||||
}
|
||||
|
||||
void UI::SetLoading(bool is_loading)
|
||||
{
|
||||
RefPtr<JSContext> lock(view()->LockJSContext());
|
||||
updateLoading({ is_loading });
|
||||
}
|
||||
|
||||
void UI::SetCanGoBack(bool can_go_back)
|
||||
{
|
||||
RefPtr<JSContext> lock(view()->LockJSContext());
|
||||
updateBack({ can_go_back });
|
||||
}
|
||||
|
||||
void UI::SetCanGoForward(bool can_go_forward)
|
||||
{
|
||||
RefPtr<JSContext> lock(view()->LockJSContext());
|
||||
updateForward({ can_go_forward });
|
||||
}
|
||||
|
||||
void UI::SetTitle(const String& title) { browser_->SetTitle(title); }
|
||||
|
||||
void UI::SetURL(const ultralight::String& url)
|
||||
{
|
||||
RefPtr<JSContext> lock(view()->LockJSContext());
|
||||
updateURL({ url });
|
||||
}
|
||||
|
||||
void UI::SetCursor(ultralight::Cursor cursor)
|
||||
{
|
||||
cur_cursor_ = cursor;
|
||||
|
||||
if (App::instance())
|
||||
window()->SetCursor(cursor);
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
#include <AppCore/AppCore.h>
|
||||
#include "Page.h"
|
||||
#include "Browser.h"
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
using ultralight::JSObject;
|
||||
using ultralight::JSArgs;
|
||||
using ultralight::JSFunction;
|
||||
using namespace ultralight;
|
||||
|
||||
class Console;
|
||||
|
||||
/**
|
||||
* Browser UI implementation. Renders the toolbar/addressbar/tabs in top pane.
|
||||
*/
|
||||
class UI : public WindowListener,
|
||||
public LoadListener,
|
||||
public ViewListener {
|
||||
public:
|
||||
UI(Browser* browser);
|
||||
~UI();
|
||||
|
||||
// Inherited from WindowListener
|
||||
virtual bool OnKeyEvent(const ultralight::KeyEvent& evt) override;
|
||||
virtual bool OnMouseEvent(const ultralight::MouseEvent& evt) override;
|
||||
virtual void OnClose(ultralight::Window* window) override;
|
||||
virtual void OnResize(ultralight::Window* window, uint32_t width, uint32_t height) override;
|
||||
|
||||
// Inherited from LoadListener
|
||||
virtual void OnDOMReady(View* caller, uint64_t frame_id,
|
||||
bool is_main_frame, const String& url) override;
|
||||
|
||||
// Inherited from ViewListener
|
||||
virtual void OnChangeCursor(ultralight::View* caller, Cursor cursor) override { SetCursor(cursor); }
|
||||
|
||||
// Called by UI JavaScript
|
||||
void OnBack(const JSObject& obj, const JSArgs& args);
|
||||
void OnForward(const JSObject& obj, const JSArgs& args);
|
||||
void OnRefresh(const JSObject& obj, const JSArgs& args);
|
||||
void OnStop(const JSObject& obj, const JSArgs& args);
|
||||
void OnToggleTools(const JSObject& obj, const JSArgs& args);
|
||||
void OnRequestChangeURL(const JSObject& obj, const JSArgs& args);
|
||||
|
||||
RefPtr<Window> window() { return browser_->window(); }
|
||||
|
||||
protected:
|
||||
void CreatePage();
|
||||
void UpdatePageNavigation(bool is_loading, bool can_go_back, bool can_go_forward);
|
||||
|
||||
void SetLoading(bool is_loading);
|
||||
void SetCanGoBack(bool can_go_back);
|
||||
void SetCanGoForward(bool can_go_forward);
|
||||
void SetTitle(const String& title);
|
||||
void SetURL(const String& url);
|
||||
void SetCursor(Cursor cursor);
|
||||
|
||||
Page* page() { return page_.get(); }
|
||||
|
||||
RefPtr<View> view() { return overlay_->view(); }
|
||||
|
||||
Browser* browser_;
|
||||
RefPtr<Overlay> overlay_;
|
||||
int ui_height_;
|
||||
int page_height_;
|
||||
float scale_;
|
||||
|
||||
std::unique_ptr<Page> page_;
|
||||
Cursor cur_cursor_;
|
||||
bool is_resizing_inspector_;
|
||||
bool is_over_inspector_resize_drag_handle_;
|
||||
int inspector_resize_begin_height_;
|
||||
int inspector_resize_begin_mouse_y_;
|
||||
|
||||
JSFunction updateBack;
|
||||
JSFunction updateForward;
|
||||
JSFunction updateLoading;
|
||||
JSFunction updateURL;
|
||||
JSFunction addTab;
|
||||
JSFunction updateTab;
|
||||
JSFunction closeTab;
|
||||
|
||||
friend class Page;
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
#include "Browser.h"
|
||||
#include <AppCore/Dialogs.h>
|
||||
|
||||
static bool use_gpu_renderer = true;
|
||||
static bool force_repaints = false;
|
||||
static bool show_statistics = true;
|
||||
|
||||
void ShowOptionDialog()
|
||||
{
|
||||
// Show a message box to select whether or not to use the GPU renderer
|
||||
ultralight::ButtonResult result = ultralight::ShowMessageBox("MiniBrowser Options",
|
||||
"Use the GPU renderer?\n\n(Yes = GPU, No = CPU)", ultralight::DialogIcon::Question, ultralight::ButtonType::YesNo);
|
||||
|
||||
use_gpu_renderer = result == ultralight::ButtonResult::Yes;
|
||||
|
||||
// Show a message box to select whether or not to force repaints
|
||||
result = ultralight::ShowMessageBox("MiniBrowser Options",
|
||||
"Force repaints to benchmark performance?\n\n(Yes = Force, No = Don't force)", ultralight::DialogIcon::Question, ultralight::ButtonType::YesNo);
|
||||
|
||||
force_repaints = result == ultralight::ButtonResult::Yes;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
ShowOptionDialog();
|
||||
|
||||
Browser browser(use_gpu_renderer, force_repaints, show_statistics);
|
||||
browser.Run();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
#include "EmbeddedFileSystem.h"
|
||||
#include "EmbeddedFiles.h"
|
||||
#include <Ultralight/Buffer.h>
|
||||
#include <Ultralight/String.h>
|
||||
#include <Ultralight/platform/FileSystem.h>
|
||||
#include <Ultralight/platform/Platform.h>
|
||||
|
||||
static const char* FileExtensionToMimeType(const char* ext);
|
||||
|
||||
namespace ultralight {
|
||||
|
||||
inline static std::string ToString(const String& str)
|
||||
{
|
||||
return std::string(str.utf8().data(), str.utf8().length());
|
||||
}
|
||||
|
||||
EmbeddedFileSystem::EmbeddedFileSystem()
|
||||
{
|
||||
}
|
||||
|
||||
bool EmbeddedFileSystem::FileExists(const String& file_path)
|
||||
{
|
||||
auto& embedded_files = GetEmbeddedFiles();
|
||||
return embedded_files.find(ToString(file_path)) != embedded_files.end();
|
||||
}
|
||||
|
||||
String EmbeddedFileSystem::GetFileMimeType(const String& file_path)
|
||||
{
|
||||
std::string file_path_utf8 = ToString(file_path);
|
||||
size_t last_dot_pos = file_path_utf8.rfind('.');
|
||||
std::string ext = (last_dot_pos != std::string::npos) ? file_path_utf8.substr(last_dot_pos + 1) : "";
|
||||
|
||||
return String(FileExtensionToMimeType(ext.c_str()));
|
||||
}
|
||||
|
||||
String EmbeddedFileSystem::GetFileCharset(const String& file_path)
|
||||
{
|
||||
return "utf-8";
|
||||
}
|
||||
|
||||
RefPtr<Buffer> EmbeddedFileSystem::OpenFile(const String& file_path)
|
||||
{
|
||||
auto& embedded_files = GetEmbeddedFiles();
|
||||
auto it = embedded_files.find(ToString(file_path));
|
||||
if (it != embedded_files.end()) {
|
||||
return Buffer::Create((void*)it->second.first, it->second.second, nullptr, nullptr);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace ultralight
|
||||
|
||||
const char* FileExtensionToMimeType(const char* ext)
|
||||
{
|
||||
static const std::unordered_map<std::string, const char*> mime_types = {
|
||||
{ "html", "text/html" },
|
||||
{ "htm", "text/html" },
|
||||
{ "css", "text/css" },
|
||||
{ "js", "application/javascript" },
|
||||
{ "json", "application/json" },
|
||||
{ "jpg", "image/jpeg" },
|
||||
{ "jpeg", "image/jpeg" },
|
||||
{ "png", "image/png" },
|
||||
{ "gif", "image/gif" },
|
||||
{ "webp", "image/webp" },
|
||||
{ "svg", "image/svg+xml" },
|
||||
{ "ico", "image/x-icon" },
|
||||
{ "txt", "text/plain" },
|
||||
{ "csv", "text/csv" },
|
||||
{ "xml", "text/xml" },
|
||||
{ "pdf", "application/pdf" },
|
||||
{ "doc", "application/msword" },
|
||||
{ "docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document" },
|
||||
{ "ppt", "application/vnd.ms-powerpoint" },
|
||||
{ "pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation" },
|
||||
{ "xls", "application/vnd.ms-excel" },
|
||||
{ "xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" },
|
||||
{ "mp3", "audio/mpeg" },
|
||||
{ "wav", "audio/wav" },
|
||||
{ "mp4", "video/mp4" },
|
||||
{ "avi", "video/x-msvideo" },
|
||||
{ "mov", "video/quicktime" },
|
||||
{ "flv", "video/x-flv" },
|
||||
{ "webm", "video/webm" },
|
||||
{ "zip", "application/zip" },
|
||||
{ "rar", "application/x-rar-compressed" },
|
||||
{ "7z", "application/x-7z-compressed" },
|
||||
{ "tar", "application/x-tar" },
|
||||
{ "gz", "application/gzip" },
|
||||
{ "mpg", "video/mpeg" },
|
||||
{ "mpeg", "video/mpeg" },
|
||||
{ "ogg", "application/ogg" },
|
||||
{ "ogv", "video/ogg" },
|
||||
{ "oga", "audio/ogg" },
|
||||
{ "otf", "font/otf" },
|
||||
{ "ttf", "font/ttf" },
|
||||
{ "woff", "font/woff" },
|
||||
{ "woff2", "font/woff2" },
|
||||
{ "eot", "application/vnd.ms-fontobject" },
|
||||
{ "sfnt", "font/sfnt" },
|
||||
{ "bin", "application/octet-stream" },
|
||||
{ "exe", "application/octet-stream" },
|
||||
{ "dll", "application/octet-stream" },
|
||||
{ "psd", "image/vnd.adobe.photoshop" },
|
||||
{ "ai", "application/postscript" },
|
||||
{ "eps", "application/postscript" },
|
||||
{ "ps", "application/postscript" },
|
||||
{ "m4a", "audio/m4a" },
|
||||
{ "m4v", "video/x-m4v" },
|
||||
{ "bmp", "image/bmp" },
|
||||
{ "tiff", "image/tiff" },
|
||||
{ "tif", "image/tiff" },
|
||||
{ "mkv", "video/x-matroska" },
|
||||
{ "mpa", "video/mpeg" },
|
||||
{ "mpe", "video/mpeg" },
|
||||
{ "mid", "audio/midi" },
|
||||
{ "midi", "audio/midi" },
|
||||
{ "3gp", "video/3gpp" },
|
||||
{ "3g2", "video/3gpp2" },
|
||||
{ "aif", "audio/aiff" },
|
||||
{ "aiff", "audio/aiff" },
|
||||
{ "aac", "audio/aac" },
|
||||
{ "au", "audio/basic" },
|
||||
{ "wasm", "application/wasm" },
|
||||
{ "xhtml", "application/xhtml+xml" },
|
||||
{ "qt", "video/quicktime" }
|
||||
};
|
||||
|
||||
auto it = mime_types.find(std::string(ext));
|
||||
if (it != mime_types.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
return "application/octet-stream"; // default MIME type if not found
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
#include <Ultralight/platform/FileSystem.h>
|
||||
|
||||
namespace ultralight {
|
||||
|
||||
///
|
||||
/// Custom file system implementation that loads files from resources embedded in the executable.
|
||||
///
|
||||
class EmbeddedFileSystem : public FileSystem {
|
||||
public:
|
||||
EmbeddedFileSystem();
|
||||
|
||||
virtual ~EmbeddedFileSystem() = default;
|
||||
|
||||
virtual bool FileExists(const String& file_path) override;
|
||||
|
||||
virtual String GetFileMimeType(const String& file_path) override;
|
||||
|
||||
virtual String GetFileCharset(const String& file_path) override;
|
||||
|
||||
virtual RefPtr<Buffer> OpenFile(const String& file_path) override;
|
||||
};
|
||||
|
||||
} // namespace ultralight
|
||||
@@ -0,0 +1,6 @@
|
||||
# Ultralight - Tools
|
||||
|
||||
Various developer tools for the Ultralight SDK:
|
||||
|
||||
* MemProfiler - Memory profiling utility (SDK must be built with memory stats enabled)
|
||||
* MiniBrowser - Simple browser app, can be used to test capabilities of the renderer
|
||||
@@ -0,0 +1,157 @@
|
||||
include(CMakeParseArguments)
|
||||
include(Platform)
|
||||
include(embed_files)
|
||||
|
||||
if (${UL_ENABLE_STATIC_BUILD})
|
||||
include(StaticLibs)
|
||||
endif ()
|
||||
|
||||
set(SDK_ROOT "${UL_SDK_PATH}")
|
||||
|
||||
set(ULTRALIGHT_INCLUDE_DIR "${SDK_ROOT}/include")
|
||||
set(ULTRALIGHT_BINARY_DIR "${SDK_ROOT}/bin")
|
||||
set(ULTRALIGHT_INSPECTOR_DIR "${SDK_ROOT}/inspector")
|
||||
set(ULTRALIGHT_RESOURCES_DIR "${SDK_ROOT}/resources")
|
||||
set(ULTRALIGHT_LIBRARY_DIR "${SDK_ROOT}/bin"
|
||||
"${SDK_ROOT}/lib")
|
||||
|
||||
get_filename_component(INFO_PLIST_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Info.plist.in" REALPATH)
|
||||
get_filename_component(ENTITLEMENTS_PLIST_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Entitlements.plist" REALPATH)
|
||||
|
||||
macro(add_console_app)
|
||||
set(APP_NAME ${ARGV0})
|
||||
set(options "NEEDS_INSPECTOR" "EMBED_FILES")
|
||||
set(oneValueArgs "")
|
||||
set(multiValueArgs "SOURCES")
|
||||
cmake_parse_arguments("ARGS"
|
||||
"${options}"
|
||||
"${oneValueArgs}"
|
||||
"${multiValueArgs}"
|
||||
${ARGN})
|
||||
|
||||
include_directories("${ULTRALIGHT_INCLUDE_DIR}")
|
||||
link_directories("${ULTRALIGHT_LIBRARY_DIR}")
|
||||
link_libraries(UltralightCore Ultralight WebCore AppCore)
|
||||
|
||||
if (${UL_ENABLE_STATIC_BUILD})
|
||||
add_definitions(-DULTRALIGHT_STATIC_BUILD)
|
||||
link_libraries(${UL_STATIC_LIBS})
|
||||
endif ()
|
||||
|
||||
if (UL_PLATFORM MATCHES "macos")
|
||||
set(CMAKE_INSTALL_RPATH ".")
|
||||
endif ()
|
||||
|
||||
add_executable(${APP_NAME} ${ARGS_SOURCES})
|
||||
|
||||
# Always link to the C++ standard library
|
||||
set_target_properties(${APP_NAME} PROPERTIES LINKER_LANGUAGE CXX)
|
||||
|
||||
set(INSTALL_PATH "${INSTALL_DIR}/${APP_NAME}")
|
||||
|
||||
install(TARGETS ${APP_NAME}
|
||||
RUNTIME DESTINATION "${INSTALL_PATH}"
|
||||
BUNDLE DESTINATION "${INSTALL_PATH}")
|
||||
|
||||
install(DIRECTORY "${ULTRALIGHT_BINARY_DIR}/" DESTINATION "${INSTALL_PATH}")
|
||||
install(DIRECTORY "${ULTRALIGHT_RESOURCES_DIR}" DESTINATION "${INSTALL_PATH}/assets")
|
||||
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/assets/" DESTINATION "${INSTALL_PATH}/assets" OPTIONAL)
|
||||
|
||||
# Conditionally embed additional assets based on EMBED_FILES
|
||||
if (ARGS_EMBED_FILES)
|
||||
endif ()
|
||||
|
||||
endmacro ()
|
||||
|
||||
macro(add_app)
|
||||
set(APP_NAME ${ARGV0})
|
||||
set(prefix "ARGS")
|
||||
set(options "NEEDS_INSPECTOR" "EMBED_FILES")
|
||||
set(oneValueArgs "")
|
||||
set(multiValueArgs "SOURCES")
|
||||
cmake_parse_arguments(${prefix}
|
||||
"${options}"
|
||||
"${oneValueArgs}"
|
||||
"${multiValueArgs}"
|
||||
${ARGN})
|
||||
|
||||
include_directories("${ULTRALIGHT_INCLUDE_DIR}")
|
||||
link_directories("${ULTRALIGHT_LIBRARY_DIR}")
|
||||
link_libraries(UltralightCore AppCore Ultralight WebCore)
|
||||
|
||||
if (${UL_ENABLE_STATIC_BUILD})
|
||||
add_definitions(-DULTRALIGHT_STATIC_BUILD)
|
||||
link_libraries(${UL_STATIC_LIBS} ${APPCORE_STATIC_LIBS})
|
||||
endif ()
|
||||
|
||||
add_executable(${APP_NAME} WIN32 MACOSX_BUNDLE ${ARGS_SOURCES})
|
||||
|
||||
# Always link to the C++ standard library
|
||||
set_target_properties(${APP_NAME} PROPERTIES LINKER_LANGUAGE CXX)
|
||||
|
||||
if (ARGS_EMBED_FILES)
|
||||
set(EMBEDDED_FOLDERS "${CMAKE_CURRENT_SOURCE_DIR}/assets/"
|
||||
"${ULTRALIGHT_RESOURCES_DIR}")
|
||||
|
||||
if (ARGS_NEEDS_INSPECTOR)
|
||||
list(APPEND EMBEDDED_FOLDERS "${ULTRALIGHT_INSPECTOR_DIR}")
|
||||
endif ()
|
||||
|
||||
embed_files(${APP_NAME} FOLDERS ${EMBEDDED_FOLDERS})
|
||||
endif ()
|
||||
|
||||
set(INSTALL_PATH "${INSTALL_DIR}/${APP_NAME}")
|
||||
|
||||
if (UL_PLATFORM MATCHES "macos")
|
||||
# Include Entitlements.plist
|
||||
set_source_files_properties(${ENTITLEMENTS_PLIST_PATH} PROPERTIES MACOSX_PACKAGE_LOCATION "Contents")
|
||||
|
||||
# Enable High-DPI on macOS through our custom Info.plist template
|
||||
set_target_properties(${APP_NAME} PROPERTIES
|
||||
BUNDLE True
|
||||
MACOSX_BUNDLE_GUI_IDENTIFIER ultralight.${APP_NAME}
|
||||
MACOSX_BUNDLE_BUNDLE_NAME ${APP_NAME}
|
||||
MACOSX_BUNDLE_EXECUTABLE_NAME ${APP_NAME}
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION "1.0"
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING "1.0"
|
||||
MACOSX_BUNDLE_INFO_PLIST ${INFO_PLIST_PATH}
|
||||
)
|
||||
|
||||
# Set the install destination for the app bundle
|
||||
set(BUNDLE_INSTALL_PATH "${INSTALL_PATH}/${APP_NAME}.app")
|
||||
set(BUNDLE_EXEC_PATH "${BUNDLE_INSTALL_PATH}/Contents/MacOS")
|
||||
set(BUNDLE_RESOURCE_PATH "${BUNDLE_INSTALL_PATH}/Contents/Resources")
|
||||
set(BUNDLE_ASSETS_PATH "${BUNDLE_RESOURCE_PATH}/assets")
|
||||
|
||||
install(TARGETS ${APP_NAME} BUNDLE DESTINATION "${INSTALL_PATH}")
|
||||
install(DIRECTORY "${ULTRALIGHT_BINARY_DIR}/" DESTINATION "${BUNDLE_EXEC_PATH}")
|
||||
|
||||
if (NOT ARGS_EMBED_FILES)
|
||||
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/assets/" DESTINATION "${BUNDLE_ASSETS_PATH}" OPTIONAL)
|
||||
install(DIRECTORY "${ULTRALIGHT_RESOURCES_DIR}" DESTINATION "${BUNDLE_ASSETS_PATH}")
|
||||
if (ARGS_NEEDS_INSPECTOR)
|
||||
install(DIRECTORY "${ULTRALIGHT_INSPECTOR_DIR}" DESTINATION "${BUNDLE_ASSETS_PATH}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
else ()
|
||||
if (UL_PLATFORM MATCHES "windows")
|
||||
# Use main instead of WinMain for Windows subsystem executables
|
||||
set_target_properties(${APP_NAME} PROPERTIES LINK_FLAGS "/ENTRY:mainCRTStartup")
|
||||
endif()
|
||||
|
||||
set(ASSETS_PATH "${INSTALL_PATH}/assets")
|
||||
set(BIN_PATH "${INSTALL_PATH}")
|
||||
|
||||
install(TARGETS ${APP_NAME} RUNTIME DESTINATION "${INSTALL_PATH}")
|
||||
install(DIRECTORY "${ULTRALIGHT_BINARY_DIR}/" DESTINATION "${BIN_PATH}")
|
||||
|
||||
if (NOT ARGS_EMBED_FILES)
|
||||
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/assets/" DESTINATION "${ASSETS_PATH}" OPTIONAL)
|
||||
install(DIRECTORY "${ULTRALIGHT_RESOURCES_DIR}" DESTINATION "${ASSETS_PATH}")
|
||||
if (ARGS_NEEDS_INSPECTOR)
|
||||
install(DIRECTORY "${ULTRALIGHT_INSPECTOR_DIR}" DESTINATION "${ASSETS_PATH}")
|
||||
endif()
|
||||
endif()
|
||||
endif ()
|
||||
endmacro ()
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.downloads.read-write</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,22 @@
|
||||
include(BundleUtilities)
|
||||
|
||||
# Set bundle to the full path name of the executable already
|
||||
# existing in the install tree:
|
||||
set(bundle "@BUNDLE_PATH@")
|
||||
|
||||
# Set other_libs to a list of full path names to additional
|
||||
# libraries that cannot be reached by dependency analysis.
|
||||
# (Dynamically loaded PlugIns, for example.)
|
||||
set(other_libs "")
|
||||
|
||||
# Set dirs to a list of directories where prerequisite libraries
|
||||
# may be found:
|
||||
set(dirs
|
||||
"@CMAKE_RUNTIME_OUTPUT_DIRECTORY@"
|
||||
"@CMAKE_LIBRARY_OUTPUT_DIRECTORY@"
|
||||
"@ULTRALIGHT_BINARY_DIR@"
|
||||
"/usr/local/opt/llvm@11/lib/"
|
||||
)
|
||||
|
||||
fixup_bundle("${bundle}" "${other_libs}" "${dirs}")
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>${MACOSX_BUNDLE_INFO_STRING}</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleLongVersionString</key>
|
||||
<string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
|
||||
<key>CSResourcesFileMapped</key>
|
||||
<true/>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<string>true</string>
|
||||
<key>NSAppSandbox</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,21 @@
|
||||
if (NOT UL_PLATFORM)
|
||||
if(CMAKE_SYSTEM_NAME MATCHES "Windows")
|
||||
set(UL_PLATFORM "windows")
|
||||
elseif (CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
||||
set(UL_PLATFORM "macos")
|
||||
elseif (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
set(UL_PLATFORM "linux")
|
||||
else ()
|
||||
message(FATAL_ERROR "Unable to detect target platform. Please manually specify UL_PLATFORM when running CMake.")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
if (UL_PLATFORM MATCHES "macos")
|
||||
SET(CMAKE_SKIP_BUILD_RPATH FALSE)
|
||||
SET(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
|
||||
SET(CMAKE_INSTALL_RPATH "@executable_path/")
|
||||
elseif (UL_PLATFORM MATCHES "linux")
|
||||
SET(CMAKE_SKIP_BUILD_RPATH FALSE)
|
||||
SET(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
|
||||
SET(CMAKE_INSTALL_RPATH "$\{ORIGIN\}")
|
||||
endif ()
|
||||
@@ -0,0 +1,55 @@
|
||||
function(embed_files TARGET)
|
||||
cmake_parse_arguments(EMBED_FILES "" "" "FOLDERS" ${ARGN})
|
||||
|
||||
set(EMBED_FILES_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/EmbeddedFiles")
|
||||
set(EMBED_FILES_HEADER "${EMBED_FILES_OUTPUT_DIR}/EmbeddedFiles.h")
|
||||
set(EMBED_FILES_RC "${EMBED_FILES_OUTPUT_DIR}/data.rc")
|
||||
|
||||
# Find the Python executable
|
||||
find_package(Python COMPONENTS Interpreter)
|
||||
if(Python_FOUND)
|
||||
set(PYTHON_EXECUTABLE ${Python_EXECUTABLE})
|
||||
else()
|
||||
find_program(PYTHON_EXECUTABLE NAMES python python2 python3)
|
||||
endif()
|
||||
|
||||
if(NOT PYTHON_EXECUTABLE)
|
||||
message(FATAL_ERROR "Python executable not found.")
|
||||
endif()
|
||||
|
||||
# Find the embed_files.py script
|
||||
find_file(EMBED_FILES_SCRIPT NAMES embed_files.py PATHS ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_MODULE_PATH})
|
||||
|
||||
if(NOT EMBED_FILES_SCRIPT)
|
||||
message(FATAL_ERROR "embed_files.py script not found.")
|
||||
endif()
|
||||
|
||||
# Add the output directory to the target's include directories
|
||||
target_include_directories(${TARGET} PRIVATE ${EMBED_FILES_OUTPUT_DIR})
|
||||
|
||||
# Create a custom target to run the embed_files.py script unconditionally
|
||||
add_custom_target(${TARGET}_embed_files
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${EMBED_FILES_OUTPUT_DIR}
|
||||
COMMAND ${PYTHON_EXECUTABLE} ${EMBED_FILES_SCRIPT} ${EMBED_FILES_FOLDERS} ${EMBED_FILES_OUTPUT_DIR}
|
||||
BYPRODUCTS ${EMBED_FILES_HEADER} ${EMBED_FILES_RC}
|
||||
COMMENT "Embedding files for target ${TARGET}"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
if (${ALLINONE_BUILD})
|
||||
add_dependencies(${TARGET}_embed_files GenerateSDK)
|
||||
endif ()
|
||||
|
||||
# Add the custom target as a dependency of the main target
|
||||
add_dependencies(${TARGET} ${TARGET}_embed_files)
|
||||
|
||||
# Add the output header as a source file to the target
|
||||
target_sources(${TARGET} PRIVATE ${EMBED_FILES_HEADER})
|
||||
|
||||
# Add the RC file to the target's source files on Windows
|
||||
if(WIN32)
|
||||
target_sources(${TARGET} PRIVATE ${EMBED_FILES_RC})
|
||||
endif()
|
||||
|
||||
target_compile_definitions(${TARGET} PRIVATE UL_EMBED_FILES=1)
|
||||
endfunction()
|
||||
@@ -0,0 +1,158 @@
|
||||
# embed_files.py
|
||||
|
||||
import os
|
||||
import sys
|
||||
import hashlib
|
||||
import shutil
|
||||
import platform
|
||||
|
||||
def embed_files(folder_paths, out_dir):
|
||||
"""
|
||||
Embeds files from the specified folder paths into a C++ application using the best available method
|
||||
for each platform:
|
||||
- Windows (MSVC/Clang-CL): Resource files (RC)
|
||||
- MacOS / Linux (GCC/Clang): Inline assembly (.incbin)
|
||||
|
||||
Generates a C++ header (EmbeddedFiles.h) and, on Windows, a resource file (data.rc).
|
||||
|
||||
The embedded files can be retrieved via the GetEmbeddedFiles() function.
|
||||
|
||||
Args:
|
||||
folder_paths (list): List of folder paths containing the files to be embedded.
|
||||
out_dir (str): Output directory where the generated files will be stored.
|
||||
"""
|
||||
files_to_embed = []
|
||||
output_hash = hashlib.sha256()
|
||||
symbol_counter = 0
|
||||
|
||||
for folder_path in folder_paths:
|
||||
if folder_path.endswith('/'):
|
||||
base_path = folder_path
|
||||
else:
|
||||
base_path = os.path.dirname(folder_path) + '/'
|
||||
for root, _, files in os.walk(folder_path):
|
||||
for file in files:
|
||||
abs_path = os.path.join(root, file)
|
||||
abs_path = abs_path.replace('\\', '/') # Replace backslashes with forward slashes
|
||||
rel_path = os.path.relpath(abs_path, base_path)
|
||||
rel_path = rel_path.replace('\\', '/') # Replace backslashes with forward slashes
|
||||
symbol_name = f"FILE_{symbol_counter}"
|
||||
files_to_embed.append((abs_path, rel_path, symbol_name))
|
||||
output_hash.update(str((abs_path, os.path.getmtime(abs_path))).encode())
|
||||
symbol_counter += 1
|
||||
|
||||
output_header = os.path.join(out_dir, "EmbeddedFiles.h")
|
||||
|
||||
if not os.path.exists(out_dir):
|
||||
os.makedirs(out_dir)
|
||||
|
||||
output_hash_hex = output_hash.hexdigest()
|
||||
|
||||
# Check if the hash has changed
|
||||
hash_file = os.path.join(out_dir, "output_hash.txt")
|
||||
if os.path.exists(hash_file):
|
||||
with open(hash_file, "r") as f:
|
||||
old_hash = f.read().strip()
|
||||
if old_hash == output_hash_hex:
|
||||
print("No changes detected in files_to_embed. Skipping file generation.")
|
||||
return
|
||||
|
||||
output_header_content = """
|
||||
#pragma once
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
using EmbeddedFilesMap = const std::unordered_map<std::string, std::pair<const uint8_t*, size_t>>;
|
||||
"""
|
||||
|
||||
if platform.system() == "Windows":
|
||||
# Windows: Use resource files (RC)
|
||||
rc_file = os.path.join(out_dir, "data.rc")
|
||||
rc_content = "#include \"winres.h\"\n"
|
||||
|
||||
for abs_path, rel_path, symbol_name in files_to_embed:
|
||||
rc_content += """{symbol_name} RCDATA \"{abs_path}\"\n""".format(symbol_name=symbol_name, abs_path=abs_path)
|
||||
|
||||
with open(rc_file, "w") as f:
|
||||
f.write(rc_content)
|
||||
|
||||
output_header_content += """
|
||||
#include <windows.h>
|
||||
|
||||
inline std::pair<const uint8_t*, size_t> GetEmbeddedFileData(const char* resourceName) {
|
||||
HRSRC hResource = FindResource(NULL, resourceName, RT_RCDATA);
|
||||
if (hResource) {
|
||||
HGLOBAL hData = LoadResource(NULL, hResource);
|
||||
if (hData) {
|
||||
const uint8_t* data = static_cast<const uint8_t*>(LockResource(hData));
|
||||
size_t size = SizeofResource(NULL, hResource);
|
||||
return std::make_pair(data, size);
|
||||
}
|
||||
}
|
||||
return std::make_pair(nullptr, 0);
|
||||
}
|
||||
"""
|
||||
|
||||
output_header_content += """
|
||||
inline EmbeddedFilesMap& GetEmbeddedFiles() {
|
||||
static EmbeddedFilesMap embedded_files = {
|
||||
"""
|
||||
|
||||
for _, rel_path, symbol_name in files_to_embed:
|
||||
output_header_content += """
|
||||
{{ "{rel_path}", GetEmbeddedFileData("{symbol_name}") }},""".format(rel_path=rel_path, symbol_name=symbol_name)
|
||||
|
||||
output_header_content += """
|
||||
};
|
||||
|
||||
return embedded_files;
|
||||
}
|
||||
"""
|
||||
else:
|
||||
# MacOS / Linux: Use inline assembly (.incbin)
|
||||
section = "__TEXT,__const" if platform.system() == "Darwin" else ".rodata,\\\"a\\\",@progbits"
|
||||
for abs_path, rel_path, symbol_name in files_to_embed:
|
||||
output_header_content += """
|
||||
__asm__(".section {section}");
|
||||
__asm__(".balign 16");
|
||||
__asm__(".globl __binary_{symbol_name}_start");
|
||||
__asm__("__binary_{symbol_name}_start:");
|
||||
__asm__(".incbin \\"{abs_path}\\"");
|
||||
__asm__(".globl __binary_{symbol_name}_end");
|
||||
__asm__("__binary_{symbol_name}_end:");
|
||||
extern const uint8_t __binary_{symbol_name}_start[] __asm__("__binary_{symbol_name}_start") __attribute__((aligned(16)));
|
||||
extern const uint8_t __binary_{symbol_name}_end[] __asm__("__binary_{symbol_name}_end") __attribute__((aligned(16)));
|
||||
""".format(section=section, symbol_name=symbol_name, abs_path=abs_path)
|
||||
|
||||
output_header_content += """
|
||||
inline EmbeddedFilesMap& GetEmbeddedFiles() {
|
||||
static EmbeddedFilesMap embedded_files = {
|
||||
"""
|
||||
|
||||
for _, rel_path, symbol_name in files_to_embed:
|
||||
output_header_content += """
|
||||
{{ "{rel_path}", {{ __binary_{symbol_name}_start, static_cast<size_t>(__binary_{symbol_name}_end - __binary_{symbol_name}_start) }} }},""".format(rel_path=rel_path, symbol_name=symbol_name)
|
||||
|
||||
output_header_content += """
|
||||
};
|
||||
return embedded_files;
|
||||
}
|
||||
"""
|
||||
|
||||
with open(output_header, "w") as f:
|
||||
f.write(output_header_content)
|
||||
|
||||
# Write the updated hash to the hash file
|
||||
with open(hash_file, "w") as f:
|
||||
f.write(output_hash_hex)
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: python2 embed_files.py <folder_path_1> [<folder_path_2> ...] <output_directory>")
|
||||
sys.exit(1)
|
||||
|
||||
folder_paths = sys.argv[1:-1]
|
||||
out_dir = sys.argv[-1]
|
||||
embed_files(folder_paths, out_dir)
|
||||
Reference in New Issue
Block a user