feat(browser): introduce quadrant rendering (#120)

Fixes #28
Fixes #55
Fixes #78
This commit is contained in:
Fathy Boundjadj 2023-02-14 05:42:13 +01:00 committed by GitHub
parent 6b906b0165
commit 67474982b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
67 changed files with 3328 additions and 1294 deletions

View File

@ -1,216 +1,265 @@
import { commit } from "refloat";
import docker from "github.com/refloat-plugins/docker";
import pkg from "../package.json";
const { version } = JSON.parse(pkg);
const triple = (platform, arch) => `${archs[arch]}-${platforms[platform]}`;
const sharedLib = {
macos: "dylib",
linux: "so",
macos: "dylib",
linux: "so",
};
const platforms = {
macos: "apple-darwin",
linux: "unknown-linux-gnu",
macos: "apple-darwin",
linux: "unknown-linux-gnu",
};
const archs = {
arm64: "aarch64",
amd64: "x86_64",
arm64: "aarch64",
amd64: "x86_64",
};
export const jobs = ["macos", "linux"].flatMap((platform) => {
return [
return [
{
name: `Build runtime (${platform})`,
agent: { tags: ["chromium-src", platform] },
steps: [
...["arm64", "amd64"].map((arch) => ({
import: { workspace: `core-${triple(platform, arch)}` },
})),
{
name: `Build runtime (${platform})`,
agent: { tags: ["chromium-src", platform] },
steps: [
...["arm64", "amd64"].map((arch) => ({
import: { workspace: `core-${triple(platform, arch)}` },
})),
{
name: "Fetch Chromium",
command: `
if [ -z "$CHROMIUM_ROOT" ]; then
echo "Chromium build environment not setup"
exit 2
fi
if scripts/runtime-pull.sh arm64; then
touch skip-build-arm64
fi
if scripts/runtime-pull.sh amd64; then
touch skip-build-amd64
fi
if [ ! -f skip-build-amd64 ] || [ ! -f skip-build-amd64 ]; then
cp chromium/.gclient "$CHROMIUM_ROOT"
scripts/gclient.sh sync
scripts/patches.sh apply
rm -rf "$CHROMIUM_ROOT/src/carbonyl"
mkdir "$CHROMIUM_ROOT/src/carbonyl"
ln -s "$(pwd)/src" "$CHROMIUM_ROOT/src/carbonyl/src"
ln -s "$(pwd)/build" "$CHROMIUM_ROOT/src/carbonyl/build"
fi
`,
},
{
parallel: ["arm64", "amd64"].map((arch) => {
const target =
platform === "linux" && arch === "amd64" ? "Default" : arch;
return {
serial: [
{
name: `Build Chromium (${arch})`,
command: `
if [ ! -f skip-build-${arch} ]; then
(
export PATH="$PATH:$CHROMIUM_ROOT/depot_tools"
cd "$CHROMIUM_ROOT/src/out/${target}"
ninja headless:headless_shell -j4
)
scripts/copy-binaries.sh ${target} ${arch}
fi
`,
env: {
AR_AARCH64_UNKNOWN_LINUX_GNU: "aarch64-linux-gnu-ar",
CC_AARCH64_UNKNOWN_LINUX_GNU: "aarch64-linux-gnu-gcc",
CXX_AARCH64_UNKNOWN_LINUX_GNU: "aarch64-linux-gnu-g++",
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER:
"aarch64-linux-gnu-gcc",
AR_X86_64_UNKNOWN_LINUX_GNU: "x86_64-linux-gnu-ar",
CC_X86_64_UNKNOWN_LINUX_GNU: "x86_64-linux-gnu-gcc",
CXX_X86_64_UNKNOWN_LINUX_GNU: "x86_64-linux-gnu-g++",
CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER:
"x86_64-linux-gnu-gcc",
},
},
{
parallel: [
{
name: `Push binaries to CDN (${arch})`,
command: `
if [ ! -f skip-build-${arch} ]; then
scripts/runtime-push.sh ${arch}
fi
`,
env: {
CDN_ACCESS_KEY_ID: { secret: true },
CDN_SECRET_ACCESS_KEY: { secret: true },
},
},
{
export: {
workspace: `runtime-${triple(platform, arch)}`,
path: `build/pre-built/${triple(platform, arch)}`,
},
},
],
},
],
};
}),
},
],
parallel: ["arm64", "amd64"].map((arch) => ({
name: `Fetch pre-built runtime for ${arch}`,
command: `
if scripts/runtime-pull.sh ${arch}; then
touch skip-build-${arch}
fi
`,
})),
},
...["arm64", "amd64"].flatMap((arch) => {
const triple = `${archs[arch]}-${platforms[platform]}`;
const lib = `build/${triple}/release/libcarbonyl.${sharedLib[platform]}`;
{
name: "Fetch Chromium",
command: `
if [ -z "$CHROMIUM_ROOT" ]; then
echo "Chromium build environment not setup"
return [
exit 2
fi
if [ ! -f skip-build-arm64 ] || [ ! -f skip-build-amd64 ]; then
cp chromium/.gclient "$CHROMIUM_ROOT"
scripts/gclient.sh sync
scripts/patches.sh apply
rm -rf "$CHROMIUM_ROOT/src/carbonyl"
mkdir "$CHROMIUM_ROOT/src/carbonyl"
ln -s "$(pwd)/src" "$CHROMIUM_ROOT/src/carbonyl/src"
ln -s "$(pwd)/build" "$CHROMIUM_ROOT/src/carbonyl/build"
fi
`,
},
{
parallel: ["arm64", "amd64"].map((arch) => {
const target =
platform === "linux" && arch === "amd64" ? "Default" : arch;
return {
serial: [
{
name: `Build core (${platform}/${arch})`,
docker:
platform === "linux"
? {
image: "fathyb/rust-cross",
cache: ["/usr/local/cargo/registry"],
}
: undefined,
agent: { tags: platform === "linux" ? ["docker"] : ["macos"] },
steps: [
{
name: "Install Rust toolchain",
command: `rustup target add ${triple}`,
},
{
name: "Build core library",
command: `cargo build --target ${triple} --release`,
env: { MACOSX_DEPLOYMENT_TARGET: "10.13" },
},
{
name: "Set core library install name",
command:
platform === "macos"
? `install_name_tool -id @executable_path/libcarbonyl.dylib ${lib}`
: "echo not necessary",
},
{
export: {
workspace: `core-${triple}`,
path: "build/*/release/*.{dylib,so,dll}",
},
},
],
name: `Build Chromium (${arch})`,
command: `
if [ ! -f skip-build-${arch} ]; then
scripts/build.sh ${target} ${arch}
scripts/copy-binaries.sh ${target} ${arch}
fi
`,
env: {
MACOSX_DEPLOYMENT_TARGET: "10.13",
CARBONYL_SKIP_CARGO_BUILD: "true",
AR_AARCH64_UNKNOWN_LINUX_GNU: "aarch64-linux-gnu-ar",
CC_AARCH64_UNKNOWN_LINUX_GNU: "aarch64-linux-gnu-gcc",
CXX_AARCH64_UNKNOWN_LINUX_GNU: "aarch64-linux-gnu-g++",
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER:
"aarch64-linux-gnu-gcc",
AR_X86_64_UNKNOWN_LINUX_GNU: "x86_64-linux-gnu-ar",
CC_X86_64_UNKNOWN_LINUX_GNU: "x86_64-linux-gnu-gcc",
CXX_X86_64_UNKNOWN_LINUX_GNU: "x86_64-linux-gnu-g++",
CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER:
"x86_64-linux-gnu-gcc",
},
},
{
name: `Package (${platform}/${arch})`,
docker: "fathyb/rust-cross",
agent: { tags: ["docker"] },
steps: [
{
import: { workspace: `runtime-${triple}` },
},
{
name: "Zip binaries",
command: `
mkdir build/zip
cp -r build/pre-built/${triple} build/zip/carbonyl-${version}
cd build/zip
zip -r package.zip carbonyl-${version}
`,
},
{
export: {
artifact: {
name: `carbonyl.${platform}-${arch}.zip`,
path: "build/zip/package.zip",
},
},
},
],
parallel: [
{
name: `Push binaries to CDN (${arch})`,
command: `
if [ ! -f skip-build-${arch} ]; then
scripts/runtime-push.sh ${arch}
fi
`,
env: {
CDN_ACCESS_KEY_ID: { secret: true },
CDN_SECRET_ACCESS_KEY: { secret: true },
},
},
{
export: {
workspace: `runtime-${triple(platform, arch)}`,
path: `build/pre-built/${triple(platform, arch)}`,
},
},
],
},
];
}),
];
],
};
}),
},
],
},
...["arm64", "amd64"].flatMap((arch) => {
const triple = `${archs[arch]}-${platforms[platform]}`;
const lib = `build/${triple}/release/libcarbonyl.${sharedLib[platform]}`;
return [
{
name: `Build core (${platform}/${arch})`,
docker:
platform === "linux"
? {
image: "fathyb/rust-cross",
cache: ["/usr/local/cargo/registry"],
}
: undefined,
agent: { tags: platform === "linux" ? ["docker"] : ["macos"] },
steps: [
{
name: "Install Rust toolchain",
command: `rustup target add ${triple}`,
},
{
name: "Build core library",
command: `cargo build --target ${triple} --release`,
env: { MACOSX_DEPLOYMENT_TARGET: "10.13" },
},
{
name: "Set core library install name",
command:
platform === "macos"
? `install_name_tool -id @executable_path/libcarbonyl.dylib ${lib}`
: "echo not necessary",
},
{
export: {
workspace: `core-${triple}`,
path: "build/*/release/*.{dylib,so,dll}",
},
},
],
},
{
name: `Package (${platform}/${arch})`,
docker: "fathyb/rust-cross",
agent: { tags: ["docker"] },
steps: [
{
import: { workspace: `runtime-${triple}` },
},
{
name: "Zip binaries",
command: `
mkdir build/zip
cp -r build/pre-built/${triple} build/zip/carbonyl-${version}
cd build/zip
zip -r package.zip carbonyl-${version}
`,
},
{
export: {
artifact: {
name: `carbonyl.${platform}-${arch}.zip`,
path: "build/zip/package.zip",
},
},
},
],
},
];
}),
];
});
if (commit.defaultBranch) {
jobs.push({
name: "Publish to npm",
agent: { tags: ["carbonyl-publish"] },
docker: "node:18",
steps: [
...["macos", "linux"].flatMap((platform) =>
["arm64", "amd64"].map((arch) => ({
import: { workspace: `runtime-${triple(platform, arch)}` },
}))
),
{
name: "Publish",
env: { CARBONYL_NPM_PUBLISH_TOKEN: { secret: true } },
command: `
echo "//registry.npmjs.org/:_authToken=\${CARBONYL_NPM_PUBLISH_TOKEN}" > ~/.npmrc
scripts/npm-publish.sh --tag next
`,
},
],
});
jobs.push(
{
name: "Publish to Docker",
agent: { tags: ["carbonyl-publish"] },
docker: "fathyb/rust-cross",
steps: [
{
serial: ["arm64", "amd64"].map((arch) => ({
import: { workspace: `runtime-${triple("linux", arch)}` },
})),
},
{
parallel: ["arm64", "amd64"].map((arch) => ({
serial: [
{
name: `Build ${arch} image`,
command: `scripts/docker-build.sh ${arch}`,
},
],
})),
},
{
name: "Publish images to DockerHub",
command: "scripts/docker-push.sh next",
using: docker.login({
username: { secret: "DOCKER_PUBLISH_USERNAME" },
password: { secret: "DOCKER_PUBLISH_TOKEN" },
}),
},
],
},
{
name: "Publish to npm",
agent: { tags: ["carbonyl-publish"] },
docker: "node:18",
steps: [
...["macos", "linux"].flatMap((platform) =>
["arm64", "amd64"].map((arch) => ({
import: { workspace: `runtime-${triple(platform, arch)}` },
}))
),
{
name: "Package",
command: "scripts/npm-package.sh",
},
{
name: "Write npm token",
env: { CARBONYL_NPM_PUBLISH_TOKEN: { secret: true } },
command:
'echo "//registry.npmjs.org/:_authToken=${CARBONYL_NPM_PUBLISH_TOKEN}" > ~/.npmrc',
},
{
parallel: ["amd64", "arm64"].flatMap((arch) =>
["linux", "macos"].map((platform) => ({
name: `Publish ${platform}/${arch} package`,
command: "scripts/npm-publish.sh --tag next",
env: {
CARBONYL_PUBLISH_ARCH: arch,
CARBONYL_PUBLISH_PLATFORM: platform,
},
}))
),
},
{
name: "Publish main package",
command: "scripts/npm-publish.sh --tag next",
},
],
}
);
}

View File

@ -24,21 +24,15 @@ RUN groupadd -r carbonyl && \
mkdir -p /carbonyl/data && \
chown -R carbonyl:carbonyl /carbonyl && \
apt-get update && \
apt-get install -y \
libasound2 libatk-bridge2.0-0 libatk1.0-0 libatomic1 libatspi2.0-0 \
libbrotli1 libc6 libcairo2 libcups2 libdbus-1-3 libdouble-conversion3 \
libdrm2 libevent-2.1-7 libexpat1 libflac8 libfontconfig1 libfreetype6 \
libgbm1 libgcc-s1 libglib2.0-0 libjpeg62-turbo libjsoncpp24 liblcms2-2 \
libminizip1 libnspr4 libnss3 libopenjp2-7 libopus0 libpango-1.0-0 \
libpng16-16 libpulse0 libre2-9 libsnappy1v5 libstdc++6 libwebp6 \
libwebpdemux2 libwebpmux3 libwoff1 libx11-6 libxcb1 libxcomposite1 \
libxdamage1 libxext6 libxfixes3 libxkbcommon0 libxml2 libxnvctrl0 \
libxrandr2 libxslt1.1 zlib1g libgtk-3-0 && \
apt-get install -y libasound2 libexpat1 libfontconfig1 libnss3 && \
rm -rf /var/lib/apt/lists/*
USER carbonyl
VOLUME /carbonyl/data
ENV HOME=/carbonyl/data
COPY . /carbonyl
RUN /carbonyl/carbonyl --version
ENTRYPOINT ["/carbonyl/carbonyl", "--no-sandbox", "--disable-dev-shm-usage", "--user-data-dir=/carbonyl/data"]

View File

@ -1,7 +1,7 @@
From 0ed9a390f25d73492ce1170ce229b95772fd458d Mon Sep 17 00:00:00 2001
From: Fathy Boundjadj <hey@fathy.fr>
Date: Thu, 9 Feb 2023 03:20:50 +0100
Subject: [PATCH 1/9] Add Carbonyl library
Subject: [PATCH 01/14] Add Carbonyl library
---
carbonyl/build | 1 +
@ -66,6 +66,3 @@ index bfae1e3290de0..8018111ed9898 100644
if (!headless_use_embedded_resources) {
data = [
--
2.38.1

View File

@ -1,7 +1,7 @@
From 795b29828fd7ac95548c4dcab483cbc3b6c1d361 Mon Sep 17 00:00:00 2001
From: Fathy Boundjadj <hey@fathy.fr>
Date: Thu, 9 Feb 2023 03:21:59 +0100
Subject: [PATCH 2/9] Add Carbonyl service
Subject: [PATCH 02/14] Add Carbonyl service
---
cc/trees/layer_tree_host.cc | 21 ++
@ -637,6 +637,3 @@ index 089a11b156ade..dfdc79eacce3b 100644
ShapeResultBloberizer::FillGlyphsNG bloberizer(
GetFontDescription(), device_scale_factor > 1.0f, text_info.text,
text_info.from, text_info.to, text_info.shape_result,
--
2.38.1

View File

@ -1,7 +1,7 @@
From eea9662f4ba08a655057143d320e4e692fd92469 Mon Sep 17 00:00:00 2001
From: Fathy Boundjadj <hey@fathy.fr>
Date: Thu, 9 Feb 2023 03:24:29 +0100
Subject: [PATCH 3/9] Setup shared software rendering surface
Subject: [PATCH 03/14] Setup shared software rendering surface
---
components/viz/host/host_display_client.cc | 4 +++-
@ -224,6 +224,3 @@ index 50cea82c6b477..f024e6013bfb9 100644
// The root of the Layer tree drawn by this compositor.
raw_ptr<Layer> root_layer_ = nullptr;
--
2.38.1

View File

@ -1,7 +1,7 @@
From c960c9b1f7ef3f16b27e4eaa4896e3563c88ea91 Mon Sep 17 00:00:00 2001
From: Fathy Boundjadj <hey@fathy.fr>
Date: Thu, 9 Feb 2023 03:27:27 +0100
Subject: [PATCH 4/9] Setup browser default settings
Subject: [PATCH 04/14] Setup browser default settings
---
headless/public/headless_browser.cc | 4 ++--
@ -46,6 +46,3 @@ index 48efaa7d57ca2..afc0236147519 100644
// Whether or not BeginFrames will be issued over DevTools protocol
// (experimental).
--
2.38.1

View File

@ -1,7 +1,7 @@
From 481ff19118891fe65e80b8be0e1f4498874d3b56 Mon Sep 17 00:00:00 2001
From: Fathy Boundjadj <hey@fathy.fr>
Date: Thu, 9 Feb 2023 03:28:35 +0100
Subject: [PATCH 5/9] Remove some debug assertions
Subject: [PATCH 05/14] Remove some debug assertions
---
.../browser/web_contents/web_contents_impl.cc | 1 -
@ -124,6 +124,3 @@ index 2518b71275670..3a1b8e6646c43 100644
}
void GraphicsContext::CopyConfigFrom(GraphicsContext& other) {
--
2.38.1

View File

@ -1,19 +1,28 @@
From 5693445bf736c51d3255c10115767ba8886d68da Mon Sep 17 00:00:00 2001
From cc9c37adb3ad2613a114bd37e1fde43f83951d88 Mon Sep 17 00:00:00 2001
From: Fathy Boundjadj <hey@fathy.fr>
Date: Thu, 9 Feb 2023 03:29:34 +0100
Subject: [PATCH 6/9] Setup display DPI
Date: Sun, 12 Feb 2023 01:00:43 +0100
Subject: [PATCH 06/14] Setup display DPI
---
.../lib/browser/headless_browser_impl_aura.cc | 9 +---
headless/lib/browser/headless_screen.cc | 2 +-
ui/display/display.cc | 50 +++++++++----------
3 files changed, 28 insertions(+), 33 deletions(-)
.../lib/browser/headless_browser_impl_aura.cc | 11 ++--
headless/lib/browser/headless_screen.cc | 5 +-
ui/display/display.cc | 52 ++++++++++---------
3 files changed, 35 insertions(+), 33 deletions(-)
diff --git a/headless/lib/browser/headless_browser_impl_aura.cc b/headless/lib/browser/headless_browser_impl_aura.cc
index 81261215c702f..b8eb5fc429a2a 100644
index 81261215c702f..508660db32151 100644
--- a/headless/lib/browser/headless_browser_impl_aura.cc
+++ b/headless/lib/browser/headless_browser_impl_aura.cc
@@ -57,13 +57,8 @@ void HeadlessBrowserImpl::PlatformSetWebContentsBounds(
@@ -19,6 +19,8 @@
#include "ui/events/devices/device_data_manager.h"
#include "ui/gfx/geometry/rect.h"
+#include "carbonyl/src/browser/bridge.h"
+
namespace headless {
void HeadlessBrowserImpl::PlatformInitialize() {
@@ -57,13 +59,8 @@ void HeadlessBrowserImpl::PlatformSetWebContentsBounds(
const gfx::Rect& bounds) {
// Browser's window bounds should contain all web contents, so that we're sure
// that we will actually produce visible damage when taking a screenshot.
@ -24,29 +33,48 @@ index 81261215c702f..b8eb5fc429a2a 100644
- std::max(old_host_bounds.height(), bounds.y() + bounds.height()));
- web_contents->window_tree_host()->SetBoundsInPixels(new_host_bounds);
- web_contents->window_tree_host()->window()->SetBounds(new_host_bounds);
+ web_contents->window_tree_host()->SetBoundsInPixels(ScaleToEnclosedRect(bounds, 1.0 / 7.0));
+ web_contents->window_tree_host()->SetBoundsInPixels(ScaleToEnclosedRect(bounds, carbonyl::Renderer::GetDPI()));
+ web_contents->window_tree_host()->window()->SetBounds(bounds);
gfx::NativeView native_view = web_contents->web_contents()->GetNativeView();
native_view->SetBounds(bounds);
diff --git a/headless/lib/browser/headless_screen.cc b/headless/lib/browser/headless_screen.cc
index 28f1a65f6dce5..e595735be187b 100644
index 28f1a65f6dce5..8bf00ef5e036a 100644
--- a/headless/lib/browser/headless_screen.cc
+++ b/headless/lib/browser/headless_screen.cc
@@ -49,7 +49,7 @@ display::Display HeadlessScreen::GetDisplayNearestWindow(
@@ -13,6 +13,8 @@
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/gfx/native_widget_types.h"
+#include "carbonyl/src/browser/bridge.h"
+
namespace headless {
// static
@@ -49,7 +51,8 @@ display::Display HeadlessScreen::GetDisplayNearestWindow(
HeadlessScreen::HeadlessScreen(const gfx::Rect& screen_bounds) {
static int64_t synthesized_display_id = 2000;
display::Display display(synthesized_display_id++);
- display.SetScaleAndBounds(1.0f, screen_bounds);
+ display.SetScaleAndBounds(1.0f / 7.0, ScaleToEnclosedRect(screen_bounds, 1.0 / 7.0));
+ float dpi = carbonyl::Renderer::GetDPI();
+ display.SetScaleAndBounds(dpi, ScaleToEnclosedRect(screen_bounds, dpi));
ProcessDisplayChanged(display, true /* is_primary */);
}
diff --git a/ui/display/display.cc b/ui/display/display.cc
index 466ef1fd1fe6e..cb503dc5cda0f 100644
index 466ef1fd1fe6e..1d71f3b4c9857 100644
--- a/ui/display/display.cc
+++ b/ui/display/display.cc
@@ -39,22 +39,22 @@ float g_forced_device_scale_factor = -1.0;
@@ -21,6 +21,8 @@
#include "ui/gfx/geometry/transform.h"
#include "ui/gfx/icc_profile.h"
+#include "carbonyl/src/browser/bridge.h"
+
namespace display {
namespace {
@@ -39,22 +41,22 @@ float g_forced_device_scale_factor = -1.0;
constexpr float kDisplaySizeAllowanceEpsilon = 0.01f;
bool HasForceDeviceScaleFactorImpl() {
@ -78,11 +106,11 @@ index 466ef1fd1fe6e..cb503dc5cda0f 100644
+ // scale_in_double = 1.0;
+ // }
+ // }
+ return 1.0 / 7.0;
+ return carbonyl::Bridge::GetCurrent()->GetDPI();
}
const char* ToRotationString(display::Display::Rotation rotation) {
@@ -97,11 +97,11 @@ void Display::ResetForceDeviceScaleFactorForTesting() {
@@ -97,11 +99,11 @@ void Display::ResetForceDeviceScaleFactorForTesting() {
// static
void Display::SetForceDeviceScaleFactor(double dsf) {
// Reset any previously set values and unset the flag.
@ -98,7 +126,7 @@ index 466ef1fd1fe6e..cb503dc5cda0f 100644
}
// static
@@ -273,15 +273,15 @@ void Display::SetScaleAndBounds(float device_scale_factor,
@@ -273,15 +275,15 @@ void Display::SetScaleAndBounds(float device_scale_factor,
}
void Display::SetScale(float device_scale_factor) {
@ -122,6 +150,3 @@ index 466ef1fd1fe6e..cb503dc5cda0f 100644
}
void Display::SetSize(const gfx::Size& size_in_pixel) {
--
2.38.1

View File

@ -1,7 +1,7 @@
From 5557ea65c9769fd0f99bd1f8eb58b05634f9faa2 Mon Sep 17 00:00:00 2001
From 022ed4d808369659eab4e83cd677eb974215c58c Mon Sep 17 00:00:00 2001
From: Fathy Boundjadj <hey@fathy.fr>
Date: Thu, 9 Feb 2023 03:31:17 +0100
Subject: [PATCH 7/9] Disable text effects
Subject: [PATCH 07/14] Disable text effects
---
.../core/paint/ng/ng_text_painter_base.cc | 30 ++++++++--------
@ -108,6 +108,3 @@ index 67fbf128ea158..a645ba61c8597 100644
}
StyleIterator::StyleIterator(const BreakList<SkColor>* colors,
--
2.38.1

View File

@ -1,7 +1,7 @@
From 1820cd34967c756d2459f4a2894492662edbf58b Mon Sep 17 00:00:00 2001
From 7b1f72900f704ffecc48c66da7ccd6de205b88f7 Mon Sep 17 00:00:00 2001
From: Fathy Boundjadj <hey@fathy.fr>
Date: Thu, 9 Feb 2023 03:32:14 +0100
Subject: [PATCH 8/9] Fix text layout
Subject: [PATCH 08/14] Fix text layout
---
.../core/css/resolver/style_resolver.cc | 17 ++++++++++++++++-
@ -42,6 +42,3 @@ index 6207b72d17cb9..79cb8c85b697f 100644
state.LoadPendingResources();
// Now return the style.
--
2.38.1

View File

@ -1,7 +1,7 @@
From 0697c3ab8f52592b0a0d4241e2938b8e622a01f6 Mon Sep 17 00:00:00 2001
From 792e123bb57b1b379b0367b2568302e2cb0dc3c9 Mon Sep 17 00:00:00 2001
From: Fathy Boundjadj <hey@fathy.fr>
Date: Thu, 9 Feb 2023 03:32:30 +0100
Subject: [PATCH 9/9] Bridge browser into Carbonyl library
Subject: [PATCH 09/14] Bridge browser into Carbonyl library
---
headless/app/headless_shell.cc | 33 +-
@ -645,6 +645,3 @@ index b80147fd06be8..09773596aa5ce 100644
content::WebContents* web_contents() const;
bool OpenURL(const GURL& url);
--
2.38.1

View File

@ -0,0 +1,137 @@
From bdc80f35a7113b7523c4d992edc9170db082deb0 Mon Sep 17 00:00:00 2001
From: Fathy Boundjadj <hey@fathy.fr>
Date: Sun, 12 Feb 2023 00:55:33 +0100
Subject: [PATCH 10/14] Conditionally enable text rendering
---
content/renderer/render_frame_impl.cc | 3 ++-
.../core/css/resolver/style_resolver.cc | 26 +++++++++++--------
third_party/blink/renderer/platform/BUILD.gn | 1 +
.../blink/renderer/platform/fonts/font.cc | 10 +++----
4 files changed, 23 insertions(+), 17 deletions(-)
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 97b61ffb954be..891efd6a9d796 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -259,6 +259,7 @@
// Carbonyl
#include <stdlib.h>
#include <iostream>
+#include "carbonyl/src/browser/bridge.h"
#include "cc/paint/paint_recorder.h"
#include "cc/paint/skia_paint_canvas.h"
#include "cc/raster/playback_image_provider.h"
@@ -2221,7 +2222,7 @@ void RenderFrameImpl::Initialize(blink::WebFrame* parent) {
render_callback_ = std::make_shared<std::function<bool()>>(
[=]() -> bool {
- if (!IsMainFrame() || IsHidden()) {
+ if (!IsMainFrame() || IsHidden() || carbonyl::Bridge::BitmapMode()) {
return false;
}
diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver.cc b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
index 79cb8c85b697f..7129982acf4a6 100644
--- a/third_party/blink/renderer/core/css/resolver/style_resolver.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
@@ -116,6 +116,8 @@
#include "third_party/blink/renderer/platform/wtf/text/atomic_string_hash.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
+#include "carbonyl/src/browser/bridge.h"
+
namespace blink {
namespace {
@@ -1041,18 +1043,20 @@ scoped_refptr<ComputedStyle> StyleResolver::ResolveStyle(
UseCounter::Count(GetDocument(), WebFeature::kHasGlyphRelativeUnits);
}
- auto font = state.StyleBuilder().GetFontDescription();
- FontFamily family;
+ if (!carbonyl::Bridge::BitmapMode()) {
+ auto font = state.StyleBuilder().GetFontDescription();
+ FontFamily family;
- family.SetFamily("monospace", FontFamily::Type::kGenericFamily);
- font.SetFamily(family);
- font.SetStretch(ExtraExpandedWidthValue());
- font.SetKerning(FontDescription::kNoneKerning);
- font.SetComputedSize(11.75 / 7.0);
- font.SetGenericFamily(FontDescription::kMonospaceFamily);
- font.SetIsAbsoluteSize(true);
- state.StyleBuilder().SetFontDescription(font);
- state.StyleBuilder().SetLineHeight(Length::Fixed(14.0 / 7.0));
+ family.SetFamily("monospace", FontFamily::Type::kGenericFamily);
+ font.SetFamily(family);
+ font.SetStretch(ExtraExpandedWidthValue());
+ font.SetKerning(FontDescription::kNoneKerning);
+ font.SetComputedSize(13.25 / 4.0);
+ font.SetGenericFamily(FontDescription::kMonospaceFamily);
+ font.SetIsAbsoluteSize(true);
+ state.StyleBuilder().SetFontDescription(font);
+ state.StyleBuilder().SetLineHeight(Length::Fixed(16.0 / 4.0));
+ }
state.LoadPendingResources();
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index e7b1c1a52e4c9..63fc13e44b5ae 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -1678,6 +1678,7 @@ component("platform") {
"//base/allocator:buildflags",
"//build:chromecast_buildflags",
"//build:chromeos_buildflags",
+ "//carbonyl/src/browser:carbonyl",
"//cc/ipc",
"//cc/mojo_embedder",
"//components/paint_preview/common",
diff --git a/third_party/blink/renderer/platform/fonts/font.cc b/third_party/blink/renderer/platform/fonts/font.cc
index dfdc79eacce3b..4625300729523 100644
--- a/third_party/blink/renderer/platform/fonts/font.cc
+++ b/third_party/blink/renderer/platform/fonts/font.cc
@@ -49,6 +49,8 @@
#include "third_party/skia/include/core/SkTextBlob.h"
#include "ui/gfx/geometry/rect_f.h"
+#include "carbonyl/src/browser/bridge.h"
+
namespace blink {
namespace {
@@ -151,14 +153,12 @@ bool Font::operator==(const Font& other) const {
namespace {
-static const bool carbonyl_b64_text = true;
-
void DrawBlobs(cc::PaintCanvas* canvas,
const cc::PaintFlags& flags,
const ShapeResultBloberizer::BlobBuffer& blobs,
const gfx::PointF& point,
cc::NodeId node_id = cc::kInvalidNodeId) {
- if (carbonyl_b64_text) {
+ if (!carbonyl::Bridge::BitmapMode()) {
return;
}
@@ -237,7 +237,7 @@ void Font::DrawText(cc::PaintCanvas* canvas,
if (ShouldSkipDrawing())
return;
- if (carbonyl_b64_text) {
+ if (!carbonyl::Bridge::BitmapMode()) {
auto string = StringView(
run_info.run.ToStringView(),
run_info.from,
@@ -285,7 +285,7 @@ void Font::DrawText(cc::PaintCanvas* canvas,
if (ShouldSkipDrawing())
return;
- if (carbonyl_b64_text) {
+ if (!carbonyl::Bridge::BitmapMode()) {
auto string = StringView(
text_info.text,
text_info.from,

View File

@ -0,0 +1,138 @@
From fa52dbb68b7822ee4c01a697197e68ef1ab4a19c Mon Sep 17 00:00:00 2001
From: Fathy Boundjadj <hey@fathy.fr>
Date: Sun, 12 Feb 2023 01:29:05 +0100
Subject: [PATCH 11/14] Rename carbonyl::Renderer to carbonyl::Bridge
---
headless/app/headless_shell.cc | 5 ++++-
headless/app/headless_shell_main.cc | 2 +-
headless/lib/browser/headless_browser_impl.cc | 8 ++++----
headless/lib/browser/headless_browser_impl_aura.cc | 2 +-
headless/lib/browser/headless_screen.cc | 2 +-
headless/lib/browser/headless_web_contents_impl.cc | 4 ++--
6 files changed, 13 insertions(+), 10 deletions(-)
diff --git a/headless/app/headless_shell.cc b/headless/app/headless_shell.cc
index 5b51c22ae1da3..b6a52857e8f90 100644
--- a/headless/app/headless_shell.cc
+++ b/headless/app/headless_shell.cc
@@ -12,6 +12,7 @@
#include "base/bind.h"
#include "base/command_line.h"
#include "base/files/file_util.h"
+#include "base/functional/callback.h"
#include "base/i18n/rtl.h"
#include "base/task/thread_pool.h"
#include "build/branding_buildflags.h"
@@ -92,7 +93,9 @@ void HeadlessShell::OnBrowserStart(HeadlessBrowser* browser) {
HeadlessBrowserContext::Builder context_builder =
browser_->CreateBrowserContextBuilder();
- context_builder.SetWindowSize(carbonyl::Renderer::Main()->GetSize());
+ carbonyl::Bridge::GetCurrent()->StartRenderer();
+
+ context_builder.SetWindowSize(carbonyl::Bridge::GetCurrent()->GetSize());
// Retrieve the locale set by InitApplicationLocale() in
// headless_content_main_delegate.cc in a way that is free of side-effects.
diff --git a/headless/app/headless_shell_main.cc b/headless/app/headless_shell_main.cc
index f9b8bac5c18a5..739df1ae1bd58 100644
--- a/headless/app/headless_shell_main.cc
+++ b/headless/app/headless_shell_main.cc
@@ -17,7 +17,7 @@
#include "carbonyl/src/browser/bridge.h"
int main(int argc, const char** argv) {
- carbonyl_shell_main();
+ carbonyl::Bridge::Main();
#if BUILDFLAG(IS_WIN)
sandbox::SandboxInterfaceInfo sandbox_info = {nullptr};
diff --git a/headless/lib/browser/headless_browser_impl.cc b/headless/lib/browser/headless_browser_impl.cc
index fd45d215479ab..1df3ffe72c93d 100644
--- a/headless/lib/browser/headless_browser_impl.cc
+++ b/headless/lib/browser/headless_browser_impl.cc
@@ -119,7 +119,7 @@ void HeadlessBrowserImpl::set_browser_main_parts(
}
void HeadlessBrowserImpl::Resize() {
- auto size = carbonyl::Renderer::GetSize();
+ auto size = carbonyl::Bridge::GetCurrent()->Resize();
auto rect = gfx::Rect(0, 0, size.width(), size.height());
for (auto* ctx: GetAllBrowserContexts()) {
@@ -134,7 +134,7 @@ void HeadlessBrowserImpl::Resize() {
}
}
- carbonyl::Renderer::Main()->Resize();
+ carbonyl::Bridge::GetCurrent()->Resize();
}
void HeadlessBrowserImpl::OnShutdownInput() {
@@ -279,7 +279,7 @@ void HeadlessBrowserImpl::OnKeyPressInput(char key) {
blink::WebKeyboardEvent::Type::kRawKeyDown,
blink::WebInputEvent::kNoModifiers,
base::TimeTicks::Now());
-
+
// TODO(fathy): support IME
switch (key) {
case 0x11:
@@ -500,7 +500,7 @@ void HeadlessBrowserImpl::RunOnStartCallback() {
}
};
- carbonyl::Renderer::Main()->Listen(&delegate);
+ carbonyl::Bridge::GetCurrent()->Listen(&delegate);
});
}
diff --git a/headless/lib/browser/headless_browser_impl_aura.cc b/headless/lib/browser/headless_browser_impl_aura.cc
index 508660db32151..80340d9f1b3b3 100644
--- a/headless/lib/browser/headless_browser_impl_aura.cc
+++ b/headless/lib/browser/headless_browser_impl_aura.cc
@@ -59,7 +59,7 @@ void HeadlessBrowserImpl::PlatformSetWebContentsBounds(
const gfx::Rect& bounds) {
// Browser's window bounds should contain all web contents, so that we're sure
// that we will actually produce visible damage when taking a screenshot.
- web_contents->window_tree_host()->SetBoundsInPixels(ScaleToEnclosedRect(bounds, carbonyl::Renderer::GetDPI()));
+ web_contents->window_tree_host()->SetBoundsInPixels(ScaleToEnclosedRect(bounds, carbonyl::Bridge::GetCurrent()->GetDPI()));
web_contents->window_tree_host()->window()->SetBounds(bounds);
gfx::NativeView native_view = web_contents->web_contents()->GetNativeView();
diff --git a/headless/lib/browser/headless_screen.cc b/headless/lib/browser/headless_screen.cc
index 8bf00ef5e036a..89c5ccc8d7759 100644
--- a/headless/lib/browser/headless_screen.cc
+++ b/headless/lib/browser/headless_screen.cc
@@ -51,7 +51,7 @@ display::Display HeadlessScreen::GetDisplayNearestWindow(
HeadlessScreen::HeadlessScreen(const gfx::Rect& screen_bounds) {
static int64_t synthesized_display_id = 2000;
display::Display display(synthesized_display_id++);
- float dpi = carbonyl::Renderer::GetDPI();
+ float dpi = carbonyl::Bridge::GetCurrent()->GetDPI();
display.SetScaleAndBounds(dpi, ScaleToEnclosedRect(screen_bounds, dpi));
ProcessDisplayChanged(display, true /* is_primary */);
}
diff --git a/headless/lib/browser/headless_web_contents_impl.cc b/headless/lib/browser/headless_web_contents_impl.cc
index fad8c3fdd2bfe..a166a08f6ea15 100644
--- a/headless/lib/browser/headless_web_contents_impl.cc
+++ b/headless/lib/browser/headless_web_contents_impl.cc
@@ -400,7 +400,7 @@ void HeadlessWebContentsImpl::TitleWasSet(content::NavigationEntry* entry) {
if (!web_contents() || !web_contents()->GetPrimaryMainFrame()->IsActive())
return;
- carbonyl::Renderer::Main()->SetTitle(base::UTF16ToUTF8(entry->GetTitleForDisplay()));
+ carbonyl::Bridge::GetCurrent()->SetTitle(base::UTF16ToUTF8(entry->GetTitleForDisplay()));
}
void HeadlessWebContentsImpl::DidFinishNavigation(content::NavigationHandle* handle) {
@@ -411,7 +411,7 @@ void HeadlessWebContentsImpl::DidFinishNavigation(content::NavigationHandle* han
auto& nav = web_contents()->GetController();
- carbonyl::Renderer::Main()->PushNav(
+ carbonyl::Bridge::GetCurrent()->PushNav(
handle->GetURL().spec(),
nav.CanGoBack(),
nav.CanGoForward()

View File

@ -0,0 +1,86 @@
From 6862e372717eff278470453e800dc693f33b873c Mon Sep 17 00:00:00 2001
From: Fathy Boundjadj <hey@fathy.fr>
Date: Sun, 12 Feb 2023 02:20:32 +0100
Subject: [PATCH 12/14] Create separate bridge for Blink
---
.../blink/renderer/core/css/resolver/style_resolver.cc | 4 ++--
third_party/blink/renderer/platform/BUILD.gn | 2 +-
third_party/blink/renderer/platform/fonts/font.cc | 8 ++++----
3 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver.cc b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
index 7129982acf4a6..cb116ee07c8f6 100644
--- a/third_party/blink/renderer/core/css/resolver/style_resolver.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
@@ -116,7 +116,7 @@
#include "third_party/blink/renderer/platform/wtf/text/atomic_string_hash.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
-#include "carbonyl/src/browser/bridge.h"
+#include "carbonyl/src/browser/blink.h"
namespace blink {
@@ -1043,7 +1043,7 @@ scoped_refptr<ComputedStyle> StyleResolver::ResolveStyle(
UseCounter::Count(GetDocument(), WebFeature::kHasGlyphRelativeUnits);
}
- if (!carbonyl::Bridge::BitmapMode()) {
+ if (!carbonyl::blink::BitmapMode()) {
auto font = state.StyleBuilder().GetFontDescription();
FontFamily family;
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index 63fc13e44b5ae..ceb41d781acf6 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -1678,7 +1678,7 @@ component("platform") {
"//base/allocator:buildflags",
"//build:chromecast_buildflags",
"//build:chromeos_buildflags",
- "//carbonyl/src/browser:carbonyl",
+ "//carbonyl/src/browser:blink",
"//cc/ipc",
"//cc/mojo_embedder",
"//components/paint_preview/common",
diff --git a/third_party/blink/renderer/platform/fonts/font.cc b/third_party/blink/renderer/platform/fonts/font.cc
index 4625300729523..3d1b463e9651c 100644
--- a/third_party/blink/renderer/platform/fonts/font.cc
+++ b/third_party/blink/renderer/platform/fonts/font.cc
@@ -49,7 +49,7 @@
#include "third_party/skia/include/core/SkTextBlob.h"
#include "ui/gfx/geometry/rect_f.h"
-#include "carbonyl/src/browser/bridge.h"
+#include "carbonyl/src/browser/blink.h"
namespace blink {
@@ -158,7 +158,7 @@ void DrawBlobs(cc::PaintCanvas* canvas,
const ShapeResultBloberizer::BlobBuffer& blobs,
const gfx::PointF& point,
cc::NodeId node_id = cc::kInvalidNodeId) {
- if (!carbonyl::Bridge::BitmapMode()) {
+ if (!carbonyl::blink::BitmapMode()) {
return;
}
@@ -237,7 +237,7 @@ void Font::DrawText(cc::PaintCanvas* canvas,
if (ShouldSkipDrawing())
return;
- if (!carbonyl::Bridge::BitmapMode()) {
+ if (!carbonyl::blink::BitmapMode()) {
auto string = StringView(
run_info.run.ToStringView(),
run_info.from,
@@ -285,7 +285,7 @@ void Font::DrawText(cc::PaintCanvas* canvas,
if (ShouldSkipDrawing())
return;
- if (!carbonyl::Bridge::BitmapMode()) {
+ if (!carbonyl::blink::BitmapMode()) {
auto string = StringView(
text_info.text,
text_info.from,

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,46 @@
From 2275364ee7e16ba6b46f0f339e34326d4a8c7584 Mon Sep 17 00:00:00 2001
From: Fathy Boundjadj <hey@fathy.fr>
Date: Mon, 13 Feb 2023 17:13:38 +0100
Subject: [PATCH 14/14] Move Skia text rendering control to bridge
---
content/renderer/render_frame_impl.cc | 5 -----
skia/BUILD.gn | 2 +-
2 files changed, 1 insertion(+), 6 deletions(-)
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 379cf6c58b2b0..891efd6a9d796 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -285,7 +285,6 @@
#include "third_party/skia/include/svg/SkSVGCanvas.h"
#include "third_party/skia/include/utils/SkBase64.h"
#include "third_party/skia/src/text/GlyphRun.h"
-#include "third_party/skia/src/core/SkBitmapDevice.h"
#include "third_party/skia/src/core/SkClipStackDevice.h"
#include "third_party/skia/src/core/SkDevice.h"
#include "third_party/skia/src/core/SkFontPriv.h"
@@ -2244,10 +2243,6 @@ void RenderFrameImpl::Initialize(blink::WebFrame* parent) {
);
host->ObserveTerminalRender(render_callback_);
-
- if (!carbonyl::Bridge::BitmapMode()) {
- SkBitmapDevice::DisableTextRendering();
- }
}
void RenderFrameImpl::GetInterface(
diff --git a/skia/BUILD.gn b/skia/BUILD.gn
index b330273c16db3..297ffacf073fa 100644
--- a/skia/BUILD.gn
+++ b/skia/BUILD.gn
@@ -203,7 +203,7 @@ source_set("skcms") {
}
component("skia") {
- deps = []
+ deps = [ "//carbonyl/src/browser:bridge" ]
sources = [
# Chrome sources.
"config/SkUserConfig.h",

View File

@ -1,27 +1,35 @@
From 5d889651fd377f5fe51feafc6efbba31a72c6b03 Mon Sep 17 00:00:00 2001
From 218fbf4bba772c465712c4ea442adb57968e9c22 Mon Sep 17 00:00:00 2001
From: Fathy Boundjadj <hey@fathy.fr>
Date: Thu, 9 Feb 2023 03:37:53 +0100
Date: Mon, 13 Feb 2023 17:18:18 +0100
Subject: [PATCH 1/2] Disable text rendering
---
src/core/SkBitmapDevice.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
src/core/SkBitmapDevice.cpp | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/src/core/SkBitmapDevice.cpp b/src/core/SkBitmapDevice.cpp
index b497d690f7..5e4ee2b1ce 100644
index b497d690f7..9631f47967 100644
--- a/src/core/SkBitmapDevice.cpp
+++ b/src/core/SkBitmapDevice.cpp
@@ -522,8 +522,8 @@ void SkBitmapDevice::onDrawGlyphRunList(SkCanvas* canvas,
@@ -28,6 +28,8 @@
#include "src/image/SkImage_Base.h"
#include "src/text/GlyphRun.h"
+#include "carbonyl/src/browser/bridge.h"
+
struct Bounder {
SkRect fBounds;
bool fHasBounds;
@@ -522,8 +524,10 @@ void SkBitmapDevice::onDrawGlyphRunList(SkCanvas* canvas,
const sktext::GlyphRunList& glyphRunList,
const SkPaint& initialPaint,
const SkPaint& drawingPaint) {
- SkASSERT(!glyphRunList.hasRSXForm());
- LOOP_TILER( drawGlyphRunList(canvas, &fGlyphPainter, glyphRunList, drawingPaint), nullptr )
+ // SkASSERT(!glyphRunList.hasRSXForm());
+ // LOOP_TILER( drawGlyphRunList(canvas, &fGlyphPainter, glyphRunList, drawingPaint), nullptr )
+ if (carbonyl::Bridge::BitmapMode()) {
+ SkASSERT(!glyphRunList.hasRSXForm());
+ LOOP_TILER( drawGlyphRunList(canvas, &fGlyphPainter, glyphRunList, drawingPaint), nullptr )
+ }
}
void SkBitmapDevice::drawVertices(const SkVertices* vertices,
--
2.38.1

View File

@ -1,4 +1,4 @@
From a2a7ed0af31ef13bd8e30be86bcc58fa2c34b41c Mon Sep 17 00:00:00 2001
From a271b203a2b60f0cd450bda0fa2cc14885f1d9a8 Mon Sep 17 00:00:00 2001
From: Fathy Boundjadj <hey@fathy.fr>
Date: Thu, 9 Feb 2023 03:38:05 +0100
Subject: [PATCH 2/2] Export some private APIs
@ -77,6 +77,3 @@ index 95ca905bf1..a31aba8e2b 100644
public:
/* This is the size we use when we ask for a glyph's path. We then
* post-transform it as we draw to match the request.
--
2.38.1

View File

@ -0,0 +1,22 @@
From 5ad57c96f23739717bcea018baf2bc8f4157b01d Mon Sep 17 00:00:00 2001
From: Fathy Boundjadj <hey@fathy.fr>
Date: Mon, 13 Feb 2023 16:37:40 +0100
Subject: [PATCH] Disable GIO on Linux
---
modules/portal/BUILD.gn | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/modules/portal/BUILD.gn b/modules/portal/BUILD.gn
index 36bcb53e8e..822688b1dc 100644
--- a/modules/portal/BUILD.gn
+++ b/modules/portal/BUILD.gn
@@ -85,7 +85,7 @@ if ((is_linux || is_chromeos) && rtc_use_pipewire) {
# `rtc_use_pipewire` is not set, which causes pipewire_config to not be
# included in targets. More details in: webrtc:13898
if (is_linux && !is_castos) {
- defines += [ "WEBRTC_USE_GIO" ]
+ $ defines += [ "WEBRTC_USE_GIO" ]
}
}

View File

@ -1,24 +1,40 @@
#!/usr/bin/env bash
export CARBONYL_ROOT=$(cd $(dirname -- "$0") && dirname -- "$(pwd)")
export INSTALL_DEPOT_TOOLS="true"
cd $CARBONYL_ROOT
cd "$CARBONYL_ROOT"
source scripts/env.sh
triple=$(scripts/platform-triple.sh "$2")
target="$1"
cpu="$2"
if [ -z "$MACOSX_DEPLOYMENT_TARGET" ]; then
export MACOSX_DEPLOYMENT_TARGET=10.13
if [ ! -z "$target" ]; then
shift
fi
if [ ! -z "$cpu" ]; then
shift
fi
cargo build --target "$triple" --release
triple=$(scripts/platform-triple.sh "$cpu")
if [ -z "$CARBONYL_SKIP_CARGO_BUILD" ]; then
if [ -z "$MACOSX_DEPLOYMENT_TARGET" ]; then
export MACOSX_DEPLOYMENT_TARGET=10.13
fi
cargo build --target "$triple" --release
fi
if [ -f "build/$triple/release/libcarbonyl.dylib" ]; then
cp "build/$triple/release/libcarbonyl.dylib" "$CHROMIUM_SRC/out/$target"
install_name_tool \
-id @executable_path/libcarbonyl.dylib \
"build/$triple/release/libcarbonyl.dylib"
else
cp "build/$triple/release/libcarbonyl.so" "$CHROMIUM_SRC/out/$target"
fi
cd "$CHROMIUM_SRC/out/$1"
cd "$CHROMIUM_SRC/out/$target"
ninja headless:headless_shell
ninja headless:headless_shell "$@"

View File

@ -1,7 +1,6 @@
#!/usr/bin/env bash
export CARBONYL_ROOT=$(cd $(dirname -- "$0") && dirname -- $(pwd))
export SKIP_DEPOT_TOOLS="true"
cd "$CARBONYL_ROOT"
source "scripts/env.sh"

View File

@ -2,15 +2,16 @@
export CARBONYL_ROOT=$(cd $(dirname -- "$0") && dirname -- "$(pwd)")
source "$CARBONYL_ROOT/scripts/env.sh"
cd "$CARBONYL_ROOT"
source "scripts/env.sh"
target="$1"
cpu="$2"
cpu="$1"
build_dir="$CARBONYL_ROOT/build/browser/$cpu"
triple=$(scripts/platform-triple.sh "$cpu")
triple=$(scripts/platform-triple.sh "$cpu" linux)
build_dir="build/docker/$triple"
rm -rf "$build_dir"
mkdir -p "build/docker"
cp -r "$CARBONYL_ROOT/build/pre-built/$triple" "$build_dir"
cp "$CARBONYL_ROOT/Dockerfile" "$build_dir"

View File

@ -20,7 +20,7 @@ fi
export PATH="$PATH:$DEPOT_TOOLS_ROOT"
if [ ! -f "$DEPOT_TOOLS_ROOT/README.md" ] && [ -z "$SKIP_DEPOT_TOOLS" ]; then
if [ "$INSTALL_DEPOT_TOOLS" = "true" ] && [ ! -f "$DEPOT_TOOLS_ROOT/README.md" ]; then
echo "depot_tools not found, fetching submodule.."
git -C "$CARBONYL_ROOT" submodule update --init --recursive

View File

@ -1,6 +1,7 @@
#!/usr/bin/env bash
export CARBONYL_ROOT=$(cd $(dirname -- "$0") && dirname -- "$(pwd)")
export INSTALL_DEPOT_TOOLS="true"
source "$CARBONYL_ROOT/scripts/env.sh"

View File

@ -1,6 +1,7 @@
#!/usr/bin/env bash
export CARBONYL_ROOT=$(cd $(dirname -- "$0") && dirname -- "$(pwd)")
export INSTALL_DEPOT_TOOLS="true"
source "$CARBONYL_ROOT/scripts/env.sh"

153
scripts/npm-package.mjs Normal file
View File

@ -0,0 +1,153 @@
import fs from "fs/promises";
import path from "path";
import { fileURLToPath } from "url";
const dirname = path.dirname(fileURLToPath(import.meta.url));
const pkg = JSON.parse(
await fs.readFile(path.resolve(dirname, "../package.json"), "utf-8")
);
const version = process.env.RELEASE_MODE
? pkg.version
: `${pkg.version}-next.${process.env.VERSION_ID}`;
const manifest = {
version,
license: "BSD-3-Clause",
description: "Chromium running in your terminal",
homepage: "https://github.com/fathyb/carbonyl",
repository: "fathyb/carbonyl",
bugs: "https://github.com/fathyb/carbonyl/issues",
author: {
name: "Fathy Boundjadj",
email: "hey@fathy.fr",
url: "https://fathy.fr",
},
};
async function buildMain() {
const root = path.resolve(dirname, "../build/packages/carbonyl");
await fs.rm(root, { recursive: true, force: true });
await fs.mkdir(root, { recursive: true });
await Promise.all([
Promise.all(
["readme.md", "license.md"].map((file) =>
fs.cp(path.join(dirname, "..", file), path.join(root, file))
)
),
fs.writeFile(
path.join(root, "package.json"),
JSON.stringify(
{
name: "carbonyl",
...manifest,
files: ["index.sh", "index.sh.js", "index.js"],
bin: { carbonyl: "index.sh" },
optionalDependencies: {
"@fathyb/carbonyl-linux-amd64": version,
"@fathyb/carbonyl-linux-arm64": version,
"@fathyb/carbonyl-macos-amd64": version,
"@fathyb/carbonyl-macos-arm64": version,
},
},
null,
4
)
),
fs.writeFile(
path.join(root, "index.sh"),
["#!/usr/bin/env bash", `"$(node "$(realpath "$0")".js)" "$@"`].join(
"\n"
),
{ mode: "755" }
),
fs.writeFile(
path.join(root, "index.sh.js"),
`process.stdout.write(require('.'))`
),
fs.writeFile(
path.join(root, "index.js"),
`
const archs = {
x64: 'amd64',
arm64: 'arm64',
}
const platforms = {
linux: 'linux',
darwin: 'macos',
}
const arch = archs[process.arch]
const platform = platforms[process.platform]
if (!arch) {
throw new Error('Processor architecture not supported: ' + process.arch)
}
if (!platform) {
throw new Error('Platform not supported: ' + process.platform)
}
module.exports = require('@fathyb/carbonyl-' + platform + '-' + arch)
`
),
]);
return root;
}
async function buildPlatform([os, npmOs, llvmOs], [cpu, npmCpu, llvmCpu]) {
const pkg = `carbonyl-${os}-${cpu}`;
const root = path.resolve(dirname, `../build/packages/${pkg}`);
await fs.rm(root, { recursive: true, force: true });
await fs.mkdir(root, { recursive: true });
await Promise.all([
Promise.all(
["readme.md", "license.md"].map((file) =>
fs.cp(path.join(dirname, "..", file), path.join(root, file))
)
),
fs.cp(
path.join(dirname, `../build/pre-built/${llvmCpu}-${llvmOs}`),
path.join(root, "build"),
{ recursive: true }
),
fs.writeFile(
path.join(root, "package.json"),
JSON.stringify(
{
name: `@fathyb/${pkg}`,
...manifest,
files: ["build", "index.js"],
os: [npmOs],
cpu: [npmCpu],
},
null,
4
)
),
fs.writeFile(
path.join(root, "index.js"),
`module.exports = __dirname + '/build/carbonyl'`
),
]);
return root;
}
const [root, platforms] = await Promise.all([
buildMain(),
Promise.all(
[
["macos", "darwin", "apple-darwin"],
["linux", "linux", "unknown-linux-gnu"],
].map(
async (os) =>
await Promise.all(
[
["arm64", "arm64", "aarch64"],
["amd64", "x64", "x86_64"],
].map(async (cpu) => await buildPlatform(os, cpu))
)
)
),
]);

9
scripts/npm-package.sh Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
export CARBONYL_ROOT=$(cd $(dirname -- "$0") && dirname -- $(pwd))
cd "$CARBONYL_ROOT"
source "scripts/env.sh"
VERSION_ID="$(git rev-parse --short HEAD)" \
node "$CARBONYL_ROOT/scripts/npm-package.mjs"

View File

@ -1,149 +0,0 @@
import fs from 'fs/promises'
import path from 'path'
import { fileURLToPath } from 'url'
const dirname = path.dirname(fileURLToPath(import.meta.url))
const pkg = JSON.parse(await fs.readFile(path.resolve(dirname, '../package.json'), 'utf-8'))
const version = process.env.RELEASE_MODE ? pkg.version : `${pkg.version}-next.${process.env.VERSION_ID}`
const manifest = {
version,
license: 'BSD-3-Clause',
description: 'Chromium running in your terminal',
homepage: 'https://github.com/fathyb/carbonyl',
repository: 'fathyb/carbonyl',
bugs: 'https://github.com/fathyb/carbonyl/issues',
author: {
name: 'Fathy Boundjadj',
email: 'hey@fathy.fr',
url: 'https://fathy.fr'
}
}
async function buildMain() {
const root = path.resolve(dirname, '../build/packages/carbonyl')
await fs.rm(root, { recursive: true, force: true })
await fs.mkdir(root, { recursive: true })
await Promise.all([
Promise.all(
['readme.md', 'license.md'].map(file =>
fs.cp(path.join(dirname, '..', file), path.join(root, file)),
)
),
fs.writeFile(
path.join(root, 'package.json'),
JSON.stringify(
{
name: 'carbonyl',
...manifest,
files: ['index.sh', 'index.sh.js', 'index.js'],
bin: { carbonyl: 'index.sh' },
optionalDependencies: {
'@fathyb/carbonyl-linux-amd64': version,
'@fathyb/carbonyl-linux-arm64': version,
'@fathyb/carbonyl-macos-amd64': version,
'@fathyb/carbonyl-macos-arm64': version
}
},
null,
4
)
),
fs.writeFile(
path.join(root, 'index.sh'),
[
'#!/usr/bin/env bash',
`"$(node "$(realpath "$0")".js)" "$@"`
].join('\n'),
{ mode: '755' }
),
fs.writeFile(
path.join(root, 'index.sh.js'),
`process.stdout.write(require('.'))`
),
fs.writeFile(
path.join(root, 'index.js'),
`
function tryModule(name) {
try {
return require(name)
} catch {
return null
}
}
const path = (
tryModule('@fathyb/carbonyl-linux-amd64') ||
tryModule('@fathyb/carbonyl-linux-arm64') ||
tryModule('@fathyb/carbonyl-macos-amd64') ||
tryModule('@fathyb/carbonyl-macos-arm64')
)
if (path) {
module.exports = path
} else {
throw new Error('Could not find a Carbonyl runtime installed')
}
`
),
])
return root
}
async function buildPlatform([os, npmOs, llvmOs], [cpu, npmCpu, llvmCpu]) {
const pkg = `carbonyl-${os}-${cpu}`
const root = path.resolve(dirname, `../build/packages/${pkg}`)
await fs.rm(root, { recursive: true, force: true })
await fs.mkdir(root, { recursive: true })
await Promise.all([
Promise.all(
['readme.md', 'license.md'].map(file =>
fs.cp(path.join(dirname, '..', file), path.join(root, file)),
)
),
fs.cp(
path.join(dirname, `../build/pre-built/${llvmCpu}-${llvmOs}`),
path.join(root, 'build'),
{ recursive: true }
),
fs.writeFile(
path.join(root, 'package.json'),
JSON.stringify(
{
name: `@fathyb/${pkg}`,
...manifest,
files: ['build', 'index.js'],
os: [npmOs],
cpu: [npmCpu],
},
null,
4
)
),
fs.writeFile(
path.join(root, 'index.js'),
`module.exports = __dirname + '/build/carbonyl'`
)
])
return root
}
const [root, platforms] = await Promise.all([
buildMain(),
Promise.all([
['macos', 'darwin', 'apple-darwin'],
['linux', 'linux', 'unknown-linux-gnu']
].map(async (os) =>
await Promise.all(
[
['arm64', 'arm64', 'aarch64'],
['amd64', 'x64', 'x86_64']
].map(async (cpu) => await buildPlatform(os, cpu))
)
)),
])

View File

@ -1,27 +1,16 @@
#!/usr/bin/env bash
export CARBONYL_ROOT=$(cd $(dirname -- "$0") && dirname -- $(pwd))
export SKIP_DEPOT_TOOLS="true"
cd "$CARBONYL_ROOT"
source "scripts/env.sh"
VERSION_ID="$(git rev-parse --short HEAD)" \
node "$CARBONYL_ROOT/scripts/npm-publish.mjs"
cd "build/packages"
cd "$CARBONYL_ROOT/build/packages"
if [ -z "$CARBONYL_PUBLISH_PLATFORM" ] && [ -z "$CARBONYL_PUBLISH_ARCH" ]; then
cd "carbonyl"
else
cd "carbonyl-$CARBONYL_PUBLISH_PLATFORM-$CARBONYL_PUBLISH_ARCH"
fi
cd carbonyl-linux-amd64
yarn publish --non-interactive --access public "$@"
cd ../carbonyl-linux-arm64
yarn publish --non-interactive --access public "$@"
cd ../carbonyl-macos-amd64
yarn publish --non-interactive --access public "$@"
cd ../carbonyl-macos-arm64
yarn publish --non-interactive --access public "$@"
cd ../carbonyl
yarn publish --non-interactive --access public "$@"

View File

@ -8,6 +8,7 @@ cd "$CHROMIUM_SRC"
chromium_upstream="92da8189788b1b373cbd3348f73d695dfdc521b6"
skia_upstream="486deb23bc2a4d3d09c66fef52c2ad64d8b4f761"
webrtc_upstream="727080cbacd58a2f303ed8a03f0264fe1493e47a"
if [[ "$1" == "apply" ]]; then
echo "Stashing Chromium changes.."
@ -20,7 +21,7 @@ if [[ "$1" == "apply" ]]; then
"$CARBONYL_ROOT/scripts/restore-mtime.sh" "$chromium_upstream"
echo "Stashing Skia changes.."
cd third_party/skia
cd "$CHROMIUM_SRC/third_party/skia"
git add -A .
git stash
@ -29,6 +30,16 @@ if [[ "$1" == "apply" ]]; then
git am --committer-date-is-author-date "$CARBONYL_ROOT/chromium/patches/skia"/*
"$CARBONYL_ROOT/scripts/restore-mtime.sh" "$skia_upstream"
echo "Stashing WebRTC changes.."
cd "$CHROMIUM_SRC/third_party/webrtc"
git add -A .
git stash
echo "Applying WebRTC patches.."
git checkout "$webrtc_upstream"
git am --committer-date-is-author-date "$CARBONYL_ROOT/chromium/patches/webrtc"/*
"$CARBONYL_ROOT/scripts/restore-mtime.sh" "$webrtc_upstream"
echo "Patches successfully applied"
elif [[ "$1" == "save" ]]; then
if [[ -d carbonyl ]]; then
@ -36,11 +47,18 @@ elif [[ "$1" == "save" ]]; then
fi
echo "Updating Chromium patches.."
git format-patch --output-directory "$CARBONYL_ROOT/chromium/patches/chromium" "$chromium_upstream"
rm -rf "$CARBONYL_ROOT/chromium/patches/chromium"
git format-patch --no-signature --output-directory "$CARBONYL_ROOT/chromium/patches/chromium" "$chromium_upstream"
echo "Updating Skia patches.."
cd third_party/skia
git format-patch --output-directory "$CARBONYL_ROOT/chromium/patches/skia" "$skia_upstream"
cd "$CHROMIUM_SRC/third_party/skia"
rm -rf "$CARBONYL_ROOT/chromium/patches/skia"
git format-patch --no-signature --output-directory "$CARBONYL_ROOT/chromium/patches/skia" "$skia_upstream"
echo "Updating WebRTC patches.."
cd "$CHROMIUM_SRC/third_party/webrtc"
rm -rf "$CARBONYL_ROOT/chromium/patches/webrtc"
git format-patch --no-signature --output-directory "$CARBONYL_ROOT/chromium/patches/webrtc" "$webrtc_upstream"
echo "Patches successfully updated"
else

View File

@ -1,7 +1,6 @@
#!/usr/bin/env bash
export CARBONYL_ROOT=$(cd $(dirname -- "$0") && dirname -- $(pwd))
export SKIP_DEPOT_TOOLS="true"
cd "$CARBONYL_ROOT"
source "scripts/env.sh"

View File

@ -1,7 +1,6 @@
#!/usr/bin/env bash
export CARBONYL_ROOT=$(cd $(dirname -- "$0") && dirname -- $(pwd))
export SKIP_DEPOT_TOOLS="true"
cd "$CARBONYL_ROOT"
source "scripts/env.sh"

View File

@ -10,32 +10,37 @@ mojom("mojom") {
]
}
component("carbonyl") {
component("bridge") {
output_name = "carbonyl_bridge"
defines = [ "CARBONYL_BRIDGE_IMPLEMENTATION" ]
sources = [
"bridge.cc",
"bridge.h",
]
}
component("viz") {
output_name = "carbonyl_viz"
defines = [ "CARBONYL_VIZ_IMPLEMENTATION" ]
sources = [
"host_display_client.cc",
"host_display_client.h",
"render_service_impl.cc",
"render_service_impl.h",
"software_output_device_proxy.cc",
"software_output_device_proxy.h",
]
deps = [
":mojom",
":renderer",
"//base",
"//components/viz/host",
"//services/viz/privileged/mojom/compositing",
"//ui/compositor",
"//ui/display",
"//services/viz/privileged/mojom",
]
}
config("lib") {
target = ""
if (target_cpu == "x64") {
if (current_cpu == "x64") {
target += "x86_64-"
} else if (target_cpu == "arm64") {
} else if (current_cpu == "arm64") {
target += "aarch64-"
}
@ -48,3 +53,22 @@ component("carbonyl") {
libs = ["carbonyl"]
lib_dirs = ["//carbonyl/build/$target/release"]
}
component("renderer") {
output_name = "carbonyl_renderer"
defines = [ "CARBONYL_RENDERER_IMPLEMENTATION" ]
sources = [
"render_service_impl.cc",
"render_service_impl.h",
"renderer.cc",
"renderer.h",
]
configs += [ ":lib" ]
deps = [
":mojom",
":bridge",
"//base",
"//skia"
]
}

View File

@ -5,10 +5,41 @@ headless_use_embedded_resources = true
ffmpeg_branding = "Chrome"
proprietary_codecs = true
# Disable unused dependencies
ozone_platform = "headless"
ozone_platform_x11 = false
use_static_angle = true
use_qt = false
use_gio = false
use_gtk = false
use_cups = false
use_dbus = false
use_glib = false
use_libpci = false
use_kerberos = false
use_vaapi_x11 = false
use_xkbcommon = false
angle_use_x11 = false
angle_use_wayland = false
rtc_use_x11 = false
rtc_use_pipewire = false
rtc_use_x11_extensions = false
# Linux only
# use_wayland_gbm = false
# use_system_libdrm = false
# use_system_minigbm = false
# Disable unused features
enable_pdf = false
enable_nacl = false
enable_ppapi = false
enable_printing = false
enable_print_content_analysis = false
enable_plugins = false
enable_rust_json = false
enable_tagged_pdf = false

View File

@ -1,114 +1,27 @@
#include "carbonyl/src/browser/bridge.h"
#include <memory>
#include <iostream>
#include <stdio.h>
namespace {
#include "third_party/skia/include/core/SkColor.h"
extern "C" {
struct carbonyl_renderer* carbonyl_renderer_create();
void carbonyl_renderer_resize(struct carbonyl_renderer* renderer);
void carbonyl_output_get_size(struct carbonyl_bridge_size* size);
void carbonyl_renderer_push_nav(struct carbonyl_renderer* renderer, const char* url, bool can_go_back, bool can_go_forward);
void carbonyl_renderer_set_title(struct carbonyl_renderer* renderer, const char* title);
void carbonyl_renderer_clear_text(struct carbonyl_renderer* renderer);
void carbonyl_input_listen(struct carbonyl_renderer* renderer, const struct carbonyl_bridge_browser_delegate* delegate);
void carbonyl_renderer_draw_text(
struct carbonyl_renderer* renderer,
const char* title,
const struct carbonyl_bridge_rect* rect,
const struct carbonyl_bridge_color* color
);
void carbonyl_renderer_draw_background(
struct carbonyl_renderer* renderer,
const unsigned char* pixels,
size_t pixels_size,
const struct carbonyl_bridge_rect* rect
);
float dpi_ = 0.0;
bool bitmap_mode_ = false;
}
namespace carbonyl {
namespace {
static std::unique_ptr<Renderer> globalInstance;
void Bridge::Resize() {}
float Bridge::GetDPI() {
return dpi_;
}
Renderer::Renderer(struct carbonyl_renderer* ptr): ptr_(ptr) {}
Renderer* Renderer::Main() {
if (!globalInstance) {
globalInstance = std::unique_ptr<Renderer>(
new Renderer(carbonyl_renderer_create())
);
}
return globalInstance.get();
bool Bridge::BitmapMode() {
return bitmap_mode_;
}
gfx::Size Renderer::GetSize() {
struct carbonyl_bridge_size size;
carbonyl_output_get_size(&size);
return gfx::Size(size.width, size.height);
}
void Renderer::Resize() {
carbonyl_renderer_resize(ptr_);
}
void Renderer::Listen(const struct carbonyl_bridge_browser_delegate* delegate) {
carbonyl_input_listen(ptr_, delegate);
}
void Renderer::PushNav(const std::string& url, bool can_go_back, bool can_go_forward) {
if (!url.size()) {
return;
}
carbonyl_renderer_push_nav(ptr_, url.c_str(), can_go_back, can_go_forward);
}
void Renderer::SetTitle(const std::string& title) {
if (!title.size()) {
return;
}
carbonyl_renderer_set_title(ptr_, title.c_str());
}
void Renderer::ClearText() {
carbonyl_renderer_clear_text(ptr_);
}
void Renderer::DrawText(const std::string& text, const gfx::RectF& bounds, uint32_t sk_color) {
struct carbonyl_bridge_rect rect;
struct carbonyl_bridge_color color;
rect.origin.x = bounds.x();
rect.origin.y = bounds.y();
rect.size.width = bounds.width();
rect.size.height = bounds.height();
color.r = SkColorGetR(sk_color);
color.g = SkColorGetG(sk_color);
color.b = SkColorGetB(sk_color);
carbonyl_renderer_draw_text(ptr_, text.c_str(), &rect, &color);
}
void Renderer::DrawBackground(const unsigned char* pixels, size_t pixels_size, const gfx::Rect& bounds) {
struct carbonyl_bridge_rect rect;
rect.origin.x = bounds.x();
rect.origin.y = bounds.y();
rect.size.width = bounds.width();
rect.size.height = bounds.height();
carbonyl_renderer_draw_background(ptr_, pixels, pixels_size, &rect);
void Bridge::Configure(float dpi, bool bitmap_mode) {
dpi_ = dpi;
bitmap_mode_ = bitmap_mode;
}
}

View File

@ -1,68 +1,22 @@
#ifndef CARBONYL_SRC_BROWSER_BRIDGE_H_
#define CARBONYL_SRC_BROWSER_BRIDGE_H_
#include <cstdint>
#include "ui/gfx/geometry/rect_f.h"
extern "C" {
struct carbonyl_renderer;
struct carbonyl_bridge_size {
unsigned int width;
unsigned int height;
};
struct carbonyl_bridge_point {
unsigned int x;
unsigned int y;
};
struct carbonyl_bridge_rect {
struct carbonyl_bridge_point origin;
struct carbonyl_bridge_size size;
};
struct carbonyl_bridge_color {
uint8_t r;
uint8_t g;
uint8_t b;
};
struct carbonyl_bridge_browser_delegate {
void (*shutdown) ();
void (*refresh) ();
void (*go_to) (const char* url);
void (*go_back) ();
void (*go_forward) ();
void (*scroll) (int);
void (*key_press) (char);
void (*mouse_up) (unsigned int, unsigned int);
void (*mouse_down) (unsigned int, unsigned int);
void (*mouse_move) (unsigned int, unsigned int);
void (*post_task) (void (*)(void*), void*);
};
void carbonyl_shell_main();
} /* end extern "C" */
#include "carbonyl/src/browser/export.h"
namespace carbonyl {
class Renderer {
public:
static Renderer* Main();
static gfx::Size GetSize();
class Renderer;
void Resize();
void Listen(const struct carbonyl_bridge_browser_delegate* delegate);
void PushNav(const std::string& url, bool can_go_back, bool can_go_forward);
void SetTitle(const std::string& title);
void ClearText();
void DrawText(const std::string& text, const gfx::RectF& bounds, uint32_t color);
void DrawBackground(const unsigned char* pixels, size_t pixels_size, const gfx::Rect& bounds);
class CARBONYL_BRIDGE_EXPORT Bridge {
public:
static float GetDPI();
static bool BitmapMode();
private:
Renderer(struct carbonyl_renderer* ptr);
friend class Renderer;
struct carbonyl_renderer* ptr_;
static void Resize();
static void Configure(float dpi, bool bitmap_mode);
};
}

View File

@ -1,57 +1,94 @@
use std::ffi::{CStr, CString};
use std::io::Write;
use std::process::{Command, Stdio};
use std::sync::mpsc;
use std::{env, io};
use std::sync::{mpsc, Mutex};
use std::{env, io, thread};
use libc::{c_char, c_int, c_uchar, c_uint, c_void, size_t};
use libc::{c_char, c_float, c_int, c_uchar, c_uint, c_void, size_t};
use crate::cli;
use crate::cli::{CommandLine, CommandLineProgram, EnvVar};
use crate::gfx::{Cast, Color, Point, Rect, Size};
use crate::output::Renderer;
use crate::output::{RenderThread, Window};
use crate::ui::navigation::NavigationAction;
use crate::{input, output, utils::log};
use crate::{input, utils::log};
#[repr(C)]
#[derive(Copy, Clone)]
pub struct CSize {
width: c_uint,
height: c_uint,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct CPoint {
x: c_uint,
y: c_uint,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct CRect {
origin: CPoint,
size: CSize,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct CColor {
r: u8,
g: u8,
b: u8,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct CText {
text: *const c_char,
rect: CRect,
color: CColor,
}
impl<T: Copy> From<&CPoint> for Point<T>
#[repr(C)]
pub struct RendererBridge {
cmd: CommandLine,
window: Window,
renderer: RenderThread,
}
unsafe impl Send for RendererBridge {}
unsafe impl Sync for RendererBridge {}
pub type RendererPtr = *const Mutex<RendererBridge>;
impl<T: Copy> From<CPoint> for Point<T>
where
c_uint: Cast<T>,
{
fn from(value: &CPoint) -> Self {
fn from(value: CPoint) -> Self {
Point::new(value.x, value.y).cast()
}
}
impl<T: Copy> From<&CSize> for Size<T>
impl From<Size<c_uint>> for CSize {
fn from(value: Size<c_uint>) -> Self {
Self {
width: value.width,
height: value.height,
}
}
}
impl<T: Copy> From<CSize> for Size<T>
where
c_uint: Cast<T>,
{
fn from(value: &CSize) -> Self {
fn from(value: CSize) -> Self {
Size::new(value.width, value.height).cast()
}
}
impl From<CColor> for Color {
fn from(value: CColor) -> Self {
Color::new(value.r, value.g, value.b)
}
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct BrowserDelegate {
shutdown: extern "C" fn(),
refresh: extern "C" fn(),
@ -67,23 +104,21 @@ pub struct BrowserDelegate {
}
fn main() -> io::Result<Option<i32>> {
const CARBONYL_INSIDE_SHELL: &str = "CARBONYL_INSIDE_SHELL";
if env::vars().find(|(key, value)| key == CARBONYL_INSIDE_SHELL && value == "1") != None {
return Ok(None);
}
let cmd = match cli::main() {
let cmd = match CommandLineProgram::parse_or_run() {
None => return Ok(Some(0)),
Some(cmd) => cmd,
};
if cmd.shell_mode {
return Ok(None);
}
let mut terminal = input::Terminal::setup();
let output = Command::new(env::current_exe()?)
.args(cmd.args)
.arg("--disable-threaded-scrolling")
.arg("--disable-threaded-animation")
.env(CARBONYL_INSIDE_SHELL, "1")
.env(EnvVar::ShellMode, "1")
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::piped())
@ -101,126 +136,176 @@ fn main() -> io::Result<Option<i32>> {
}
#[no_mangle]
pub extern "C" fn carbonyl_shell_main() {
pub extern "C" fn carbonyl_bridge_main() {
if let Some(code) = main().unwrap() {
std::process::exit(code)
}
}
#[no_mangle]
pub extern "C" fn carbonyl_renderer_create() -> *mut Renderer {
let mut renderer = Box::new(Renderer::new());
let src = output::size().unwrap();
log::debug!("creating renderer, terminal size: {:?}", src);
renderer.set_size(Size::new(7, 14), src);
Box::into_raw(renderer)
pub extern "C" fn carbonyl_bridge_bitmap_mode() -> bool {
CommandLine::parse().bitmap
}
#[no_mangle]
pub extern "C" fn carbonyl_renderer_resize(renderer: *mut Renderer) {
let renderer = unsafe { &mut *renderer };
let src = output::size().unwrap();
log::debug!("resizing renderer, terminal size: {:?}", src);
renderer.set_size(Size::new(7, 14), src);
pub extern "C" fn carbonyl_bridge_get_dpi() -> c_float {
Window::read().dpi
}
#[no_mangle]
pub extern "C" fn carbonyl_renderer_clear_text(renderer: *mut Renderer) {
let renderer = unsafe { &mut *renderer };
pub extern "C" fn carbonyl_renderer_create() -> RendererPtr {
let bridge = RendererBridge {
cmd: CommandLine::parse(),
window: Window::read(),
renderer: RenderThread::new(),
};
renderer.clear_text()
Box::into_raw(Box::new(Mutex::new(bridge)))
}
#[no_mangle]
pub extern "C" fn carbonyl_renderer_start(bridge: RendererPtr) {
{
let bridge = unsafe { bridge.as_ref() };
let mut bridge = bridge.unwrap().lock().unwrap();
bridge.renderer.enable()
}
carbonyl_renderer_resize(bridge);
}
#[no_mangle]
pub extern "C" fn carbonyl_renderer_resize(bridge: RendererPtr) {
let bridge = unsafe { bridge.as_ref() };
let mut bridge = bridge.unwrap().lock().unwrap();
let window = bridge.window.update();
let cells = window.cells.clone();
log::debug!("resizing renderer, terminal window: {:?}", window);
bridge
.renderer
.render(move |renderer| renderer.set_size(cells));
}
#[no_mangle]
pub extern "C" fn carbonyl_renderer_push_nav(
renderer: *mut Renderer,
bridge: RendererPtr,
url: *const c_char,
can_go_back: bool,
can_go_forward: bool,
) {
let (renderer, url) = unsafe { (&mut *renderer, CStr::from_ptr(url)) };
let (bridge, url) = unsafe { (bridge.as_ref(), CStr::from_ptr(url)) };
let (mut bridge, url) = (bridge.unwrap().lock().unwrap(), url.to_owned());
renderer.push_nav(url.to_str().unwrap(), can_go_back, can_go_forward)
bridge.renderer.render(move |renderer| {
renderer.push_nav(url.to_str().unwrap(), can_go_back, can_go_forward)
});
}
#[no_mangle]
pub extern "C" fn carbonyl_renderer_set_title(renderer: *mut Renderer, title: *const c_char) {
let (renderer, title) = unsafe { (&mut *renderer, CStr::from_ptr(title)) };
pub extern "C" fn carbonyl_renderer_set_title(bridge: RendererPtr, title: *const c_char) {
let (bridge, title) = unsafe { (bridge.as_ref(), CStr::from_ptr(title)) };
let (mut bridge, title) = (bridge.unwrap().lock().unwrap(), title.to_owned());
renderer.set_title(title.to_str().unwrap()).unwrap()
bridge
.renderer
.render(move |renderer| renderer.set_title(title.to_str().unwrap()).unwrap());
}
#[no_mangle]
pub extern "C" fn carbonyl_renderer_draw_text(
renderer: *mut Renderer,
text: *const c_char,
rect: *const CRect,
color: *const CColor,
bridge: RendererPtr,
text: *const CText,
text_size: size_t,
) {
let (renderer, text, rect, color) =
unsafe { (&mut *renderer, CStr::from_ptr(text), &*rect, &*color) };
let (bridge, text) = unsafe { (bridge.as_ref(), std::slice::from_raw_parts(text, text_size)) };
let mut bridge = bridge.unwrap().lock().unwrap();
let mut vec = text
.iter()
.map(|text| {
let str = unsafe { CStr::from_ptr(text.text) };
renderer.draw_text(
text.to_str().unwrap(),
Point::from(&rect.origin),
Size::from(&rect.size),
Color::new(color.r, color.g, color.b),
)
(
str.to_str().unwrap().to_owned(),
text.rect.origin.into(),
text.rect.size.into(),
text.color.into(),
)
})
.collect::<Vec<(String, Point, Size, Color)>>();
bridge.renderer.render(move |renderer| {
renderer.clear_text();
for (text, origin, size, color) in std::mem::take(&mut vec) {
renderer.draw_text(&text, origin, size, color)
}
});
}
#[no_mangle]
pub extern "C" fn carbonyl_renderer_draw_background(
renderer: *mut Renderer,
pixels: *mut c_uchar,
pixels_size: size_t,
rect: *const CRect,
) {
let (renderer, pixels, rect) = unsafe {
(
&mut *renderer,
std::slice::from_raw_parts_mut(pixels, pixels_size),
&*rect,
)
};
#[derive(Clone, Copy)]
struct CallbackData(*const c_void);
renderer
.draw_background(
impl CallbackData {
pub fn as_ptr(&self) -> *const c_void {
self.0
}
}
unsafe impl Send for CallbackData {}
unsafe impl Sync for CallbackData {}
#[no_mangle]
pub extern "C" fn carbonyl_renderer_draw_bitmap(
bridge: RendererPtr,
pixels: *const c_uchar,
pixels_size: CSize,
rect: CRect,
callback: extern "C" fn(*const c_void),
callback_data: *const c_void,
) {
let length = (pixels_size.width * pixels_size.height * 4) as usize;
let (bridge, pixels) = unsafe { (bridge.as_ref(), std::slice::from_raw_parts(pixels, length)) };
let callback_data = CallbackData(callback_data);
let mut bridge = bridge.unwrap().lock().unwrap();
bridge.renderer.render(move |renderer| {
renderer.draw_background(
pixels,
pixels_size.into(),
Rect {
origin: Point::from(&rect.origin),
size: Size::from(&rect.size),
size: rect.size.into(),
origin: rect.origin.into(),
},
)
.unwrap()
);
callback(callback_data.as_ptr());
});
}
#[no_mangle]
pub extern "C" fn carbonyl_output_get_size(size: *mut CSize) {
let dst = unsafe { &mut *size };
let src = output::size().unwrap().cast::<c_uint>();
pub extern "C" fn carbonyl_renderer_get_size(bridge: RendererPtr) -> CSize {
let bridge = unsafe { bridge.as_ref() };
let bridge = bridge.unwrap().lock().unwrap();
log::debug!("terminal size: {:?}", src);
log::debug!("terminal size: {:?}", bridge.window.browser);
dst.width = src.width * 7;
dst.height = src.height * 14;
bridge.window.browser.into()
}
extern "C" fn post_task_handler(callback: *mut c_void) {
let mut closure = unsafe { Box::from_raw(callback as *mut Box<dyn FnMut() -> io::Result<()>>) };
let mut closure = unsafe { Box::from_raw(callback as *mut Box<dyn FnMut()>) };
closure().unwrap();
closure()
}
fn post_task<F>(handle: &extern "C" fn(extern "C" fn(*mut c_void), *mut c_void), run: F)
unsafe fn post_task<F>(handle: extern "C" fn(extern "C" fn(*mut c_void), *mut c_void), run: F)
where
F: FnMut() -> io::Result<()>,
F: FnMut() + Send + 'static,
{
let closure: *mut Box<dyn FnMut() -> io::Result<()>> = Box::into_raw(Box::new(Box::new(run)));
let closure: *mut Box<dyn FnMut()> = Box::into_raw(Box::new(Box::new(run)));
handle(post_task_handler, closure as *mut c_void);
}
@ -230,100 +315,113 @@ where
/// This will block so the calling code should start and own a dedicated thread.
/// It will panic if there is any error.
#[no_mangle]
pub extern "C" fn carbonyl_input_listen(renderer: *mut Renderer, delegate: *mut BrowserDelegate) {
let char_width = 7;
let char_height = 14;
let BrowserDelegate {
shutdown,
refresh,
go_to,
go_back,
go_forward,
scroll,
key_press,
mouse_up,
mouse_down,
mouse_move,
post_task: handle,
} = unsafe { &*delegate };
let dispatch = |action: NavigationAction| {
use NavigationAction::*;
match action {
Ignore => (),
Forward => return true,
GoBack() => go_back(),
GoForward() => go_forward(),
Refresh() => refresh(),
GoTo(url) => {
let c_str = CString::new(url).unwrap();
go_to(c_str.as_ptr())
}
}
false
};
pub extern "C" fn carbonyl_renderer_listen(bridge: RendererPtr, delegate: *mut BrowserDelegate) {
let bridge = unsafe { &*bridge };
let delegate = unsafe { *delegate };
use input::*;
listen(|event| {
post_task(handle, move || {
let renderer = unsafe { &mut *renderer };
thread::spawn(move || {
macro_rules! emit {
($event:ident($($args:expr),*) => $closure:expr) => {{
let run = move || {
(delegate.$event)($($args),*);
use Event::*;
$closure
};
match event.clone() {
Exit => (),
Scroll { delta } => scroll(delta as c_int * char_height as c_int),
KeyPress { ref key } => {
if dispatch(renderer.keypress(key)?) {
key_press(key.char as c_char)
}
}
MouseUp { col, row } => {
if dispatch(renderer.mouse_up((col as _, row as _).into())?) {
mouse_up(
(col as c_uint) * char_width,
(row as c_uint - 1) * char_height,
)
}
}
MouseDown { col, row } => {
if dispatch(renderer.mouse_down((col as _, row as _).into())?) {
mouse_down(
(col as c_uint) * char_width,
(row as c_uint - 1) * char_height,
)
}
}
MouseMove { col, row } => {
if dispatch(renderer.mouse_move((col as _, row as _).into())?) {
mouse_move(
(col as c_uint) * char_width,
(row as c_uint - 1) * char_height,
)
}
}
Terminal(terminal) => match terminal {
TerminalEvent::Name(name) => log::debug!("terminal name: {name}"),
TerminalEvent::TrueColorSupported => renderer.enable_true_color(),
},
};
unsafe { post_task(delegate.post_task, run) }
}};
($event:ident($($args:expr),*)) => {{
emit!($event($($args),*) => {})
}};
}
Ok(())
listen(|mut events| {
bridge.lock().unwrap().renderer.render(move |renderer| {
let get_scale = || bridge.lock().unwrap().window.scale;
let scale = |col, row| {
let scale = get_scale();
scale
.mul(((col as f32 + 0.5), (row as f32 - 0.5)))
.floor()
.cast()
.into()
};
let dispatch = |action| {
match action {
NavigationAction::Ignore => (),
NavigationAction::Forward => return true,
NavigationAction::GoBack() => emit!(go_back()),
NavigationAction::GoForward() => emit!(go_forward()),
NavigationAction::Refresh() => emit!(refresh()),
NavigationAction::GoTo(url) => {
let c_str = CString::new(url).unwrap();
emit!(go_to(c_str.as_ptr()))
}
};
return false;
};
for event in std::mem::take(&mut events) {
use Event::*;
match event {
Exit => (),
Scroll { delta } => {
let scale = get_scale();
emit!(scroll((delta as f32 * scale.height) as c_int))
}
KeyPress { key } => {
if dispatch(renderer.keypress(&key).unwrap()) {
emit!(key_press(key.char as c_char))
}
}
MouseUp { col, row } => {
if dispatch(renderer.mouse_up((col as _, row as _).into()).unwrap()) {
let (width, height) = scale(col, row);
emit!(mouse_up(width, height))
}
}
MouseDown { col, row } => {
if dispatch(renderer.mouse_down((col as _, row as _).into()).unwrap()) {
let (width, height) = scale(col, row);
emit!(mouse_down(width, height))
}
}
MouseMove { col, row } => {
if dispatch(renderer.mouse_move((col as _, row as _).into()).unwrap()) {
let (width, height) = scale(col, row);
emit!(mouse_move(width, height))
}
}
Terminal(terminal) => match terminal {
TerminalEvent::Name(name) => log::debug!("terminal name: {name}"),
TerminalEvent::TrueColorSupported => renderer.enable_true_color(),
},
}
}
})
})
})
.unwrap();
.unwrap();
let (tx, rx) = mpsc::channel();
// Setup single-use channel
let (tx, rx) = mpsc::channel();
post_task(handle, move || {
shutdown();
tx.send(()).unwrap();
// Signal the browser to shutdown and notify our thread
emit!(shutdown() => tx.send(()).unwrap());
rx.recv().unwrap();
Ok(())
// Shutdown rendering thread
// if let Some(handle) = { bridge.lock().unwrap().renderer().stop() } {
// handle.join().unwrap()
// }
});
rx.recv().unwrap();
}

85
src/browser/export.h Normal file
View File

@ -0,0 +1,85 @@
#ifndef CARBONYL_SRC_BROWSER_BRIDGE_EXPORT_H_
#define CARBONYL_SRC_BROWSER_BRIDGE_EXPORT_H_
// CARBONYL_BRIDGE_EXPORT
#if defined(COMPONENT_BUILD)
#if defined(WIN32)
#if defined(CARBONYL_BRIDGE_IMPLEMENTATION)
#define CARBONYL_BRIDGE_EXPORT __declspec(dllexport)
#else
#define CARBONYL_BRIDGE_EXPORT __declspec(dllimport)
#endif
#else // !defined(WIN32)
#if defined(CARBONYL_BRIDGE_IMPLEMENTATION)
#define CARBONYL_BRIDGE_EXPORT __attribute__((visibility("default")))
#else
#define CARBONYL_BRIDGE_EXPORT
#endif
#endif
#else // !defined(COMPONENT_BUILD)
#define CARBONYL_BRIDGE_EXPORT
#endif
// CARBONYL_RENDERER_EXPORT
#if defined(COMPONENT_BUILD)
#if defined(WIN32)
#if defined(CARBONYL_RENDERER_IMPLEMENTATION)
#define CARBONYL_RENDERER_EXPORT __declspec(dllexport)
#else
#define CARBONYL_RENDERER_EXPORT __declspec(dllimport)
#endif
#else // !defined(WIN32)
#if defined(CARBONYL_RENDERER_IMPLEMENTATION)
#define CARBONYL_RENDERER_EXPORT __attribute__((visibility("default")))
#else
#define CARBONYL_RENDERER_EXPORT
#endif
#endif
#else // !defined(COMPONENT_BUILD)
#define CARBONYL_RENDERER_EXPORT
#endif
// CARBONYL_VIZ_EXPORT
#if defined(COMPONENT_BUILD)
#if defined(WIN32)
#if defined(CARBONYL_VIZ_IMPLEMENTATION)
#define CARBONYL_VIZ_EXPORT __declspec(dllexport)
#else
#define CARBONYL_VIZ_EXPORT __declspec(dllimport)
#endif
#else // !defined(WIN32)
#if defined(CARBONYL_VIZ_IMPLEMENTATION)
#define CARBONYL_VIZ_EXPORT __attribute__((visibility("default")))
#else
#define CARBONYL_VIZ_EXPORT
#endif
#endif
#else // !defined(COMPONENT_BUILD)
#define CARBONYL_VIZ_EXPORT
#endif
#endif // CARBONYL_SRC_BROWSER_BRIDGE_EXPORT_H_

View File

@ -15,13 +15,17 @@
#include "skia/ext/skia_utils_win.h"
#endif
#include "carbonyl/src/browser/bridge.h"
#include "carbonyl/src/browser/renderer.h"
namespace carbonyl {
LayeredWindowUpdater::LayeredWindowUpdater(
mojo::PendingReceiver<viz::mojom::LayeredWindowUpdater> receiver)
: receiver_(this, std::move(receiver)) {}
mojo::PendingReceiver<viz::mojom::LayeredWindowUpdater> receiver
)
:
receiver_(this, std::move(receiver)),
task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault())
{}
LayeredWindowUpdater::~LayeredWindowUpdater() = default;
@ -30,17 +34,27 @@ void LayeredWindowUpdater::OnAllocatedSharedMemory(
base::UnsafeSharedMemoryRegion region) {
if (region.IsValid())
shm_mapping_ = region.Map();
pixel_size_ = pixel_size;
}
void LayeredWindowUpdater::Draw(const gfx::Rect& damage_rect,
DrawCallback draw_callback) {
Renderer::Main()->DrawBackground(
DrawCallback callback) {
Renderer::GetCurrent()->DrawBitmap(
shm_mapping_.GetMemoryAs<uint8_t>(),
shm_mapping_.size(),
damage_rect
pixel_size_,
damage_rect,
base::BindOnce(
[](
scoped_refptr<base::SingleThreadTaskRunner> task_runner,
DrawCallback callback
) {
task_runner->PostTask(FROM_HERE, std::move(callback));
},
task_runner_,
std::move(callback)
)
);
std::move(draw_callback).Run();
}
HostDisplayClient::HostDisplayClient()

View File

@ -5,6 +5,7 @@
#include "base/callback.h"
#include "base/memory/shared_memory_mapping.h"
#include "carbonyl/src/browser/export.h"
#include "components/viz/host/host_display_client.h"
#include "services/viz/privileged/mojom/compositing/layered_window_updater.mojom.h"
#include "ui/gfx/native_widget_types.h"
@ -14,7 +15,7 @@ namespace carbonyl {
typedef base::RepeatingCallback<void(const gfx::Rect&, const SkBitmap&)>
OnPaintCallback;
class LayeredWindowUpdater : public viz::mojom::LayeredWindowUpdater {
class CARBONYL_VIZ_EXPORT LayeredWindowUpdater : public viz::mojom::LayeredWindowUpdater {
public:
explicit LayeredWindowUpdater(
mojo::PendingReceiver<viz::mojom::LayeredWindowUpdater> receiver);
@ -32,9 +33,13 @@ class LayeredWindowUpdater : public viz::mojom::LayeredWindowUpdater {
private:
mojo::Receiver<viz::mojom::LayeredWindowUpdater> receiver_;
base::WritableSharedMemoryMapping shm_mapping_;
gfx::Size pixel_size_;
DrawCallback callback_;
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
base::WeakPtrFactory<LayeredWindowUpdater> weak_ptr_factory_ { this };
};
class HostDisplayClient : public viz::HostDisplayClient {
class CARBONYL_VIZ_EXPORT HostDisplayClient : public viz::HostDisplayClient {
public:
explicit HostDisplayClient();
~HostDisplayClient() override;

View File

@ -2,7 +2,7 @@
#include <iostream>
#include "carbonyl/src/browser/bridge.h"
#include "carbonyl/src/browser/renderer.h"
namespace carbonyl {
@ -14,13 +14,13 @@ CarbonylRenderServiceImpl::CarbonylRenderServiceImpl(
CarbonylRenderServiceImpl::~CarbonylRenderServiceImpl() = default;
void CarbonylRenderServiceImpl::DrawText(std::vector<mojom::TextDataPtr> data) {
auto* renderer = Renderer::Main();
renderer->ClearText();
std::vector<Text> mapped;
for (auto& text: data) {
renderer->DrawText(text->contents, text->bounds, text->color);
mapped.emplace_back(text->contents, text->bounds, text->color);
}
Renderer::GetCurrent()->DrawText(mapped);
}
}

View File

@ -1,13 +1,14 @@
#ifndef CARBONYL_SRC_BROWSER_RENDER_SERVICE_IMPL_H_
#define CARBONYL_SRC_BROWSER_RENDER_SERVICE_IMPL_H_
#include "carbonyl/src/browser/export.h"
#include "carbonyl/src/browser/carbonyl.mojom.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver.h"
namespace carbonyl {
class CarbonylRenderServiceImpl: public mojom::CarbonylRenderService {
class CARBONYL_RENDERER_EXPORT CarbonylRenderServiceImpl: public mojom::CarbonylRenderService {
public:
explicit CarbonylRenderServiceImpl(mojo::PendingReceiver<mojom::CarbonylRenderService> receiver);
CarbonylRenderServiceImpl(const CarbonylRenderServiceImpl&) = delete;

181
src/browser/renderer.cc Normal file
View File

@ -0,0 +1,181 @@
#include "carbonyl/src/browser/renderer.h"
#include <memory>
#include <iostream>
#include <stdio.h>
#include "base/functional/callback.h"
#include "carbonyl/src/browser/bridge.h"
#include "ui/gfx/geometry/rect_f.h"
#include "third_party/skia/include/core/SkColor.h"
extern "C" {
struct carbonyl_renderer_size {
unsigned int width;
unsigned int height;
};
struct carbonyl_renderer_point {
unsigned int x;
unsigned int y;
};
struct carbonyl_renderer_rect {
struct carbonyl_renderer_point origin;
struct carbonyl_renderer_size size;
};
struct carbonyl_renderer_color {
uint8_t r;
uint8_t g;
uint8_t b;
};
struct carbonyl_renderer_text {
const char* text;
carbonyl_renderer_rect rect;
carbonyl_renderer_color color;
};
void carbonyl_bridge_main();
bool carbonyl_bridge_bitmap_mode();
float carbonyl_bridge_get_dpi();
struct carbonyl_renderer* carbonyl_renderer_create();
void carbonyl_renderer_start(struct carbonyl_renderer* renderer);
void carbonyl_renderer_resize(struct carbonyl_renderer* renderer);
struct carbonyl_renderer_size carbonyl_renderer_get_size(struct carbonyl_renderer* renderer);
void carbonyl_renderer_push_nav(struct carbonyl_renderer* renderer, const char* url, bool can_go_back, bool can_go_forward);
void carbonyl_renderer_set_title(struct carbonyl_renderer* renderer, const char* title);
void carbonyl_renderer_clear_text(struct carbonyl_renderer* renderer);
void carbonyl_renderer_listen(struct carbonyl_renderer* renderer, const struct carbonyl_renderer_browser_delegate* delegate);
void carbonyl_renderer_draw_text(
struct carbonyl_renderer* renderer,
const struct carbonyl_renderer_text* text,
size_t text_size
);
void carbonyl_renderer_draw_bitmap(
struct carbonyl_renderer* renderer,
const unsigned char* pixels,
const struct carbonyl_renderer_size size,
const struct carbonyl_renderer_rect rect,
void (*callback) (void*),
void* callback_data
);
}
namespace carbonyl {
namespace {
static std::unique_ptr<Renderer> globalInstance;
}
Renderer::Renderer(struct carbonyl_renderer* ptr): ptr_(ptr) {}
void Renderer::Main() {
carbonyl_bridge_main();
Bridge::Configure(
carbonyl_bridge_get_dpi(),
carbonyl_bridge_bitmap_mode()
);
}
Renderer* Renderer::GetCurrent() {
if (!globalInstance) {
globalInstance = std::unique_ptr<Renderer>(
new Renderer(carbonyl_renderer_create())
);
}
return globalInstance.get();
}
void Renderer::StartRenderer() {
carbonyl_renderer_start(ptr_);
}
gfx::Size Renderer::GetSize() {
auto size = carbonyl_renderer_get_size(ptr_);
return gfx::Size(size.width, size.height);
}
gfx::Size Renderer::Resize() {
carbonyl_renderer_resize(ptr_);
Bridge::Resize();
return GetSize();
}
void Renderer::Listen(const struct carbonyl_renderer_browser_delegate* delegate) {
carbonyl_renderer_listen(ptr_, delegate);
}
void Renderer::PushNav(const std::string& url, bool can_go_back, bool can_go_forward) {
if (!url.size()) {
return;
}
carbonyl_renderer_push_nav(ptr_, url.c_str(), can_go_back, can_go_forward);
}
void Renderer::SetTitle(const std::string& title) {
if (!title.size()) {
return;
}
carbonyl_renderer_set_title(ptr_, title.c_str());
}
void Renderer::DrawText(const std::vector<Text>& text) {
struct carbonyl_renderer_text data[text.size()];
for (size_t i = 0; i < text.size(); i++) {
data[i].text = text[i].text.c_str();
data[i].color.r = SkColorGetR(text[i].color);
data[i].color.g = SkColorGetG(text[i].color);
data[i].color.b = SkColorGetB(text[i].color);
data[i].rect.origin.x = text[i].rect.x();
data[i].rect.origin.y = text[i].rect.y();
data[i].rect.size.width = std::ceil(text[i].rect.width());
data[i].rect.size.height = std::ceil(text[i].rect.height());
}
carbonyl_renderer_draw_text(ptr_, data, text.size());
}
void Renderer::DrawBitmap(
const unsigned char* pixels,
const gfx::Size& pixels_size,
const gfx::Rect& damage,
base::OnceCallback<void()> callback
) {
auto* box = new base::OnceCallback<void()>(std::move(callback));
carbonyl_renderer_draw_bitmap(
ptr_,
pixels,
{
.width = (unsigned int)pixels_size.width(),
.height = (unsigned int)pixels_size.height(),
},
{
.origin = {
.x = (unsigned int)damage.x(),
.y = (unsigned int)damage.y(),
},
.size = {
.width = (unsigned int)damage.width(),
.height = (unsigned int)damage.height(),
},
},
[](void* box) {
auto* ptr = static_cast<base::OnceCallback<void()>*>(box);
std::move(*ptr).Run();
delete ptr;
},
box
);
}
}

76
src/browser/renderer.h Normal file
View File

@ -0,0 +1,76 @@
#ifndef CARBONYL_SRC_BROWSER_RENDERER_H_
#define CARBONYL_SRC_BROWSER_RENDERER_H_
#include <cstdint>
#include <functional>
#include "base/functional/callback.h"
#include "carbonyl/src/browser/export.h"
#include "ui/gfx/geometry/rect_f.h"
extern "C" {
struct carbonyl_renderer;
struct carbonyl_renderer_browser_delegate {
void (*shutdown) ();
void (*refresh) ();
void (*go_to) (const char* url);
void (*go_back) ();
void (*go_forward) ();
void (*scroll) (int);
void (*key_press) (char);
void (*mouse_up) (unsigned int, unsigned int);
void (*mouse_down) (unsigned int, unsigned int);
void (*mouse_move) (unsigned int, unsigned int);
void (*post_task) (void (*)(void*), void*);
};
} /* end extern "C" */
namespace carbonyl {
struct CARBONYL_RENDERER_EXPORT Text {
Text(
std::string text,
gfx::RectF rect,
uint32_t color
):
text(text),
rect(rect),
color(color)
{}
std::string text;
gfx::RectF rect;
uint32_t color;
};
class CARBONYL_RENDERER_EXPORT Renderer {
public:
static void Main();
static Renderer* GetCurrent();
gfx::Size GetSize();
gfx::Size Resize();
void StartRenderer();
void Listen(const struct carbonyl_renderer_browser_delegate* delegate);
void PushNav(const std::string& url, bool can_go_back, bool can_go_forward);
void SetTitle(const std::string& title);
void DrawText(const std::vector<Text>& text);
void DrawBitmap(
const unsigned char* pixels,
const gfx::Size& size,
const gfx::Rect& damage,
base::OnceCallback<void()> callback
);
private:
Renderer(struct carbonyl_renderer* ptr);
struct carbonyl_renderer* ptr_;
};
}
#endif // CARBONYL_SRC_BROWSER_RENDERER_H_

View File

@ -1,155 +0,0 @@
#include "carbonyl/src/browser/software_output_device_proxy.h"
#include "base/memory/unsafe_shared_memory_region.h"
#include "base/threading/thread_checker.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "components/viz/common/resources/resource_sizes.h"
#include "components/viz/service/display_embedder/output_device_backing.h"
#include "mojo/public/cpp/system/platform_handle.h"
#include "services/viz/privileged/mojom/compositing/layered_window_updater.mojom.h"
#include "skia/ext/platform_canvas.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "ui/gfx/skia_util.h"
#if BUILDFLAG(IS_WIN)
#include "skia/ext/skia_utils_win.h"
#include "ui/gfx/gdi_util.h"
#include "ui/gfx/win/hwnd_util.h"
#else
#include "mojo/public/cpp/base/shared_memory_utils.h"
#endif
namespace viz {
SoftwareOutputDeviceBase::~SoftwareOutputDeviceBase() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!in_paint_);
}
void SoftwareOutputDeviceBase::Resize(const gfx::Size& viewport_pixel_size,
float scale_factor) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!in_paint_);
if (viewport_pixel_size_ == viewport_pixel_size)
return;
viewport_pixel_size_ = viewport_pixel_size;
ResizeDelegated();
}
SkCanvas* SoftwareOutputDeviceBase::BeginPaint(
const gfx::Rect& damage_rect) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!in_paint_);
damage_rect_ = damage_rect;
in_paint_ = true;
return BeginPaintDelegated();
}
void SoftwareOutputDeviceBase::EndPaint() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(in_paint_);
in_paint_ = false;
gfx::Rect intersected_damage_rect = damage_rect_;
intersected_damage_rect.Intersect(gfx::Rect(viewport_pixel_size_));
if (intersected_damage_rect.IsEmpty())
return;
EndPaintDelegated(intersected_damage_rect);
}
SoftwareOutputDeviceProxy::~SoftwareOutputDeviceProxy() = default;
SoftwareOutputDeviceProxy::SoftwareOutputDeviceProxy(
mojo::PendingRemote<mojom::LayeredWindowUpdater> layered_window_updater)
: layered_window_updater_(std::move(layered_window_updater)) {
DCHECK(layered_window_updater_.is_bound());
}
void SoftwareOutputDeviceProxy::OnSwapBuffers(
SoftwareOutputDevice::SwapBuffersCallback swap_ack_callback,
gfx::FrameData data) {
DCHECK(swap_ack_callback_.is_null());
// We aren't waiting on DrawAck() and can immediately run the callback.
if (!waiting_on_draw_ack_) {
task_runner_->PostTask(FROM_HERE,
base::BindOnce(std::move(swap_ack_callback), viewport_pixel_size_));
return;
}
swap_ack_callback_ = std::move(swap_ack_callback);
}
void SoftwareOutputDeviceProxy::ResizeDelegated() {
canvas_.reset();
size_t required_bytes;
if (!ResourceSizes::MaybeSizeInBytes(
viewport_pixel_size_, ResourceFormat::RGBA_8888, &required_bytes)) {
DLOG(ERROR) << "Invalid viewport size " << viewport_pixel_size_.ToString();
return;
}
base::UnsafeSharedMemoryRegion region =
base::UnsafeSharedMemoryRegion::Create(required_bytes);
if (!region.IsValid()) {
DLOG(ERROR) << "Failed to allocate " << required_bytes << " bytes";
return;
}
#if defined(WIN32)
canvas_ = skia::CreatePlatformCanvasWithSharedSection(
viewport_pixel_size_.width(), viewport_pixel_size_.height(), false,
region.GetPlatformHandle(), skia::CRASH_ON_FAILURE);
#else
shm_mapping_ = region.Map();
if (!shm_mapping_.IsValid()) {
DLOG(ERROR) << "Failed to map " << required_bytes << " bytes";
return;
}
canvas_ = skia::CreatePlatformCanvasWithPixels(
viewport_pixel_size_.width(), viewport_pixel_size_.height(), false,
static_cast<uint8_t*>(shm_mapping_.memory()), skia::CRASH_ON_FAILURE);
#endif
// Transfer region ownership to the browser process.
layered_window_updater_->OnAllocatedSharedMemory(viewport_pixel_size_,
std::move(region));
}
SkCanvas* SoftwareOutputDeviceProxy::BeginPaintDelegated() {
return canvas_.get();
}
void SoftwareOutputDeviceProxy::EndPaintDelegated(
const gfx::Rect& damage_rect) {
DCHECK(!waiting_on_draw_ack_);
if (!canvas_)
return;
layered_window_updater_->Draw(damage_rect, base::BindOnce(
&SoftwareOutputDeviceProxy::DrawAck, base::Unretained(this)));
waiting_on_draw_ack_ = true;
TRACE_EVENT_ASYNC_BEGIN0("viz", "SoftwareOutputDeviceProxy::Draw", this);
}
void SoftwareOutputDeviceProxy::DrawAck() {
DCHECK(waiting_on_draw_ack_);
DCHECK(!swap_ack_callback_.is_null());
TRACE_EVENT_ASYNC_END0("viz", "SoftwareOutputDeviceProxy::Draw", this);
waiting_on_draw_ack_ = false;
std::move(swap_ack_callback_).Run(viewport_pixel_size_);
}
} // namespace viz

View File

@ -1,91 +0,0 @@
#ifndef CARBONYL_SRC_BROWSER_SOFTWARE_OUTPUT_DEVICE_PROXY_H_
#define CARBONYL_SRC_BROWSER_SOFTWARE_OUTPUT_DEVICE_PROXY_H_
#include <memory>
#include "base/memory/shared_memory_mapping.h"
#include "base/threading/thread_checker.h"
#include "build/build_config.h"
#include "components/viz/host/host_display_client.h"
#include "components/viz/service/display/software_output_device.h"
#include "components/viz/service/viz_service_export.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/viz/privileged/mojom/compositing/display_private.mojom.h"
#include "services/viz/privileged/mojom/compositing/layered_window_updater.mojom.h"
#if BUILDFLAG(IS_WIN)
#include <windows.h>
#endif
namespace viz {
// Shared base class for SoftwareOutputDevice implementations.
class SoftwareOutputDeviceBase : public SoftwareOutputDevice {
public:
SoftwareOutputDeviceBase() = default;
~SoftwareOutputDeviceBase() override;
SoftwareOutputDeviceBase(const SoftwareOutputDeviceBase&) = delete;
SoftwareOutputDeviceBase& operator=(const SoftwareOutputDeviceBase&) = delete;
// SoftwareOutputDevice implementation.
void Resize(const gfx::Size& viewport_pixel_size,
float scale_factor) override;
SkCanvas* BeginPaint(const gfx::Rect& damage_rect) override;
void EndPaint() override;
// Called from Resize() if |viewport_pixel_size_| has changed.
virtual void ResizeDelegated() = 0;
// Called from BeginPaint() and should return an SkCanvas.
virtual SkCanvas* BeginPaintDelegated() = 0;
// Called from EndPaint() if there is damage.
virtual void EndPaintDelegated(const gfx::Rect& damage_rect) = 0;
private:
bool in_paint_ = false;
THREAD_CHECKER(thread_checker_);
};
// SoftwareOutputDevice implementation that draws indirectly. An implementation
// of mojom::LayeredWindowUpdater in the browser process handles the actual
// drawing. Pixel backing is in SharedMemory so no copying between processes
// is required.
class SoftwareOutputDeviceProxy : public SoftwareOutputDeviceBase {
public:
explicit SoftwareOutputDeviceProxy(
mojo::PendingRemote<mojom::LayeredWindowUpdater> layered_window_updater);
~SoftwareOutputDeviceProxy() override;
SoftwareOutputDeviceProxy(const SoftwareOutputDeviceProxy&) = delete;
SoftwareOutputDeviceProxy& operator=(const SoftwareOutputDeviceProxy&) = delete;
// SoftwareOutputDevice implementation.
void OnSwapBuffers(SoftwareOutputDevice::SwapBuffersCallback swap_ack_callback, gfx::FrameData data) override;
// SoftwareOutputDeviceBase implementation.
void ResizeDelegated() override;
SkCanvas* BeginPaintDelegated() override;
void EndPaintDelegated(const gfx::Rect& rect) override;
private:
// Runs |swap_ack_callback_| after draw has happened.
void DrawAck();
mojo::Remote<mojom::LayeredWindowUpdater> layered_window_updater_;
std::unique_ptr<SkCanvas> canvas_;
bool waiting_on_draw_ack_ = false;
SoftwareOutputDevice::SwapBuffersCallback swap_ack_callback_;
#if !defined(WIN32)
base::WritableSharedMemoryMapping shm_mapping_;
#endif
};
} // namespace viz
#endif // CARBONYL_SRC_BROWSER_SOFTWARE_OUTPUT_DEVICE_PROXY_H_

View File

@ -1,5 +1,5 @@
mod main;
mod parser;
mod cli;
mod program;
pub use main::*;
pub use parser::*;
pub use cli::*;
pub use program::*;

109
src/cli/cli.rs Normal file
View File

@ -0,0 +1,109 @@
use std::{env, ffi::OsStr};
use super::CommandLineProgram;
#[derive(Clone, Debug)]
pub struct CommandLine {
pub args: Vec<String>,
pub fps: f32,
pub zoom: f32,
pub debug: bool,
pub bitmap: bool,
pub program: CommandLineProgram,
pub shell_mode: bool,
}
pub enum EnvVar {
Debug,
Bitmap,
ShellMode,
}
impl EnvVar {
pub fn as_str(&self) -> &'static str {
match self {
EnvVar::Debug => "CARBONYL_ENV_DEBUG",
EnvVar::Bitmap => "CARBONYL_ENV_BITMAP",
EnvVar::ShellMode => "CARBONYL_ENV_SHELL_MODE",
}
}
}
impl AsRef<OsStr> for EnvVar {
fn as_ref(&self) -> &OsStr {
self.as_str().as_ref()
}
}
impl CommandLine {
pub fn parse() -> CommandLine {
let mut fps = 60.0;
let mut zoom = 1.0;
let mut debug = false;
let mut bitmap = false;
let mut shell_mode = false;
let mut program = CommandLineProgram::Main;
let args = env::args().skip(1).collect::<Vec<String>>();
for arg in &args {
let split: Vec<&str> = arg.split("=").collect();
let default = arg.as_str();
let (key, value) = (split.get(0).unwrap_or(&default), split.get(1));
macro_rules! set {
($var:ident, $enum:ident) => {{
$var = true;
env::set_var(EnvVar::$enum, "1");
}};
}
macro_rules! set_f32 {
($var:ident = $expr:expr) => {{
if let Some(value) = value {
if let Some(value) = value.parse::<f32>().ok() {
$var = {
let $var = value;
$expr
};
}
}
}};
}
match *key {
"-f" | "--fps" => set_f32!(fps = fps),
"-z" | "--zoom" => set_f32!(zoom = zoom / 100.0),
"-d" | "--debug" => set!(debug, Debug),
"-b" | "--bitmap" => set!(bitmap, Bitmap),
"-h" | "--help" => program = CommandLineProgram::Help,
"-v" | "--version" => program = CommandLineProgram::Version,
_ => (),
}
}
if env::var(EnvVar::Debug).is_ok() {
debug = true;
}
if env::var(EnvVar::Bitmap).is_ok() {
bitmap = true;
}
if env::var(EnvVar::ShellMode).is_ok() {
shell_mode = true;
}
CommandLine {
args,
fps,
zoom,
debug,
bitmap,
program,
shell_mode,
}
}
}

View File

@ -1,15 +0,0 @@
use super::{CommandLine, CommandLineProgram};
pub fn main() -> Option<CommandLine> {
match CommandLineProgram::parse() {
CommandLineProgram::Main(cmd) => return Some(cmd),
CommandLineProgram::Help => {
println!("{}", include_str!("usage.txt"))
}
CommandLineProgram::Version => {
println!("Carbonyl {}", env!("CARGO_PKG_VERSION"))
}
}
None
}

View File

@ -1,30 +0,0 @@
use std::env;
pub struct CommandLine {
pub args: Vec<String>,
pub debug: bool,
}
pub enum CommandLineProgram {
Help,
Version,
Main(CommandLine),
}
impl CommandLineProgram {
pub fn parse() -> CommandLineProgram {
let mut debug = false;
let mut args = Vec::new();
for arg in env::args().skip(1) {
match arg.as_str() {
"-d" | "--debug" => debug = true,
"-h" | "--help" => return CommandLineProgram::Help,
"-v" | "--version" => return CommandLineProgram::Version,
_ => args.push(arg),
}
}
CommandLineProgram::Main(CommandLine { args, debug })
}
}

26
src/cli/program.rs Normal file
View File

@ -0,0 +1,26 @@
use super::CommandLine;
#[derive(Clone, Debug)]
pub enum CommandLineProgram {
Main,
Help,
Version,
}
impl CommandLineProgram {
pub fn parse_or_run() -> Option<CommandLine> {
let cmd = CommandLine::parse();
match cmd.program {
CommandLineProgram::Main => return Some(cmd),
CommandLineProgram::Help => {
println!("{}", include_str!("usage.txt"))
}
CommandLineProgram::Version => {
println!("Carbonyl {}", env!("CARGO_PKG_VERSION"))
}
}
None
}
}

View File

@ -7,6 +7,9 @@ O —— Cr —— O In addition to the following options,
Usage: carbonyl [options] [url]
Options:
-h, --help display this help message
-f, --fps=<fps> set the maximum number of frames per second (default: 60)
-z, --zoom=<zoom> set the zoom level in percent (default: 100)
-b, --bitmap render text as bitmaps
-d, --debug enable debug logs
-h, --help display this help message
-v, --version output the version number

View File

@ -1,7 +1,7 @@
use super::Vector2;
use crate::impl_vector_overload;
#[derive(Clone, Copy, Debug, PartialEq)]
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub struct Size<T: Copy = u32> {
pub width: T,
pub height: T,

View File

@ -137,6 +137,12 @@ macro_rules! impl_vector_overload {
}
}
impl<T: Copy> From<$struct<T>> for (T, T) {
fn from(vector: $struct<T>) -> Self {
(vector.$x, vector.$y)
}
}
impl<T: Copy> From<(T, T)> for $struct<T> {
fn from((x, y): (T, T)) -> Self {
Self::new(x, y)
@ -219,6 +225,12 @@ macro_rules! impl_vector_overload {
}
}
impl<T: Copy> From<$struct<T>> for (T, T, T) {
fn from(vector: $struct<T>) -> Self {
(vector.$x, vector.$y, vector.$z)
}
}
impl<T: Copy> From<[T; 3]> for $struct<T> {
fn from(array: [T; 3]) -> Self {
Self::new(array[0], array[1], array[2])
@ -338,6 +350,14 @@ macro_rules! impl_vector_traits {
self.map(|v| v.round())
}
pub fn floor(&self) -> Self {
self.map(|v| v.floor())
}
pub fn ceil(&self) -> Self {
self.map(|v| v.ceil())
}
pub fn min<U>(&self, min: U) -> Self
where
U: Into<Self>

View File

@ -6,7 +6,7 @@ use crate::input::*;
/// This will block, so it should run from a dedicated thread.
pub fn listen<F>(mut callback: F) -> io::Result<()>
where
F: FnMut(Event),
F: FnMut(Vec<Event>),
{
let mut buf = [0u8; 1024];
let mut stdin = io::stdin();
@ -15,20 +15,22 @@ where
loop {
// Wait for some input
let size = stdin.read(&mut buf)?;
let read = parser.parse(&buf[0..size]);
let mut scroll = 0;
let mut events = Vec::with_capacity(read.len());
// Parse the input for xterm commands
for event in parser.parse(&buf[0..size]) {
// Allow the callback to return early (ie. handle ctrl+c)
for event in read {
match event {
Event::Exit => return Ok(()),
Event::Scroll { delta } => scroll += delta,
event => callback(event),
event => events.push(event),
}
}
if scroll != 0 {
callback(Event::Scroll { delta: scroll })
events.push(Event::Scroll { delta: scroll })
}
callback(events)
}
}

View File

@ -1,12 +1,18 @@
// mod kd_tree;
// mod quantizer;
mod cell;
mod frame_sync;
mod painter;
mod quad;
mod render_thread;
mod renderer;
mod size;
mod window;
mod xterm;
pub use cell::*;
pub use frame_sync::*;
pub use painter::*;
pub use quad::*;
pub use render_thread::*;
pub use renderer::*;
pub use size::*;
pub use window::*;

View File

@ -14,22 +14,23 @@ pub struct Grapheme {
/// Terminal cell with `height = width * 2`
#[derive(PartialEq)]
pub struct Cell {
/// Top pixel color value
pub top: Color,
/// Bottom pixel color value
pub bottom: Color,
pub cursor: Point<u32>,
/// Text grapheme if any
pub grapheme: Option<Rc<Grapheme>>,
pub quadrant: (Color, Color, Color, Color),
}
impl Cell {
pub fn new(x: u32, y: u32) -> Cell {
Cell {
top: Color::black(),
bottom: Color::black(),
cursor: Point::new(x, y),
grapheme: None,
quadrant: (
Color::black(),
Color::black(),
Color::black(),
Color::black(),
),
}
}
}

34
src/output/frame_sync.rs Normal file
View File

@ -0,0 +1,34 @@
use std::time::{Duration, Instant};
/// A utility to synchronize rendering with a given FPS
pub struct FrameSync {
render_start: Option<Instant>,
frame_duration: Duration,
}
impl FrameSync {
pub fn new(fps: f32) -> Self {
Self {
render_start: None,
frame_duration: Duration::from_micros((1_000_000.0 / fps) as u64),
}
}
/// Mark the beginning of the render
pub fn start(&mut self) {
self.render_start = Some(Instant::now());
}
/// Get a deadline until the next frame
pub fn deadline(&self) -> Instant {
match self.render_start {
// We never rendered yet, render now!
None => Instant::now(),
// Else we should render `frame_duration` after the last render start.
// If we render at 60 FPS, this should be 16ms after the render start.
// If the render takes more than the frame duration, this will always
// return a deadline in a the past, making render happen immediately.
Some(render_start) => render_start + self.frame_duration,
}
}
}

View File

@ -2,16 +2,9 @@ use std::io::{self, Stdout, Write};
use crate::gfx::{Color, Point};
use super::Cell;
#[derive(PartialEq)]
enum PaintMode {
Text,
Bitmap,
}
use super::{binarize_quandrant, Cell};
pub struct Painter {
mode: PaintMode,
output: Stdout,
buffer: Vec<u8>,
cursor: Option<Point<u32>>,
@ -25,7 +18,6 @@ pub struct Painter {
impl Painter {
pub fn new() -> Painter {
Painter {
mode: PaintMode::Text,
buffer: Vec::new(),
cursor: None,
output: io::stdout(),
@ -73,42 +65,29 @@ impl Painter {
pub fn paint(&mut self, cell: &Cell) -> io::Result<()> {
let &Cell {
cursor,
quadrant,
ref grapheme,
top: mut background,
bottom: mut foreground,
} = cell;
let (char, width, escape) = if let Some(grapheme) = grapheme {
let (char, background, foreground, width) = if let Some(grapheme) = grapheme {
if grapheme.index > 0 {
return Ok(());
}
background = background.avg_with(foreground);
foreground = grapheme.color;
(
grapheme.char.as_str(),
quadrant
.0
.avg_with(quadrant.1)
.avg_with(quadrant.2)
.avg_with(quadrant.3),
grapheme.color,
grapheme.width as u32,
if self.mode == PaintMode::Bitmap {
self.mode = PaintMode::Text;
Some("\x1b[22m\x1b[24m")
} else {
None
},
)
} else {
(
"",
1,
if self.mode == PaintMode::Text {
self.mode = PaintMode::Bitmap;
let (char, background, foreground) = binarize_quandrant(quadrant);
Some("\x1b[1m\x1b[4m")
} else {
None
},
)
(char, background, foreground, 1)
};
if self.cursor != Some(cursor) {
@ -157,10 +136,6 @@ impl Painter {
}
}
if let Some(escape) = escape {
self.buffer.write_all(escape.as_bytes())?
}
self.buffer.write_all(char.as_bytes())?;
Ok(())

40
src/output/quad.rs Normal file
View File

@ -0,0 +1,40 @@
use crate::gfx::Color;
use crate::utils::FourBits::{self, *};
/// Turn a quadrant of four colors into two colors and a quadrant unicode character.
pub fn binarize_quandrant(
(x, y, z, w): (Color, Color, Color, Color),
) -> (&'static str, Color, Color) {
// Step 1: grayscale
const LUMA: Color<f32> = Color::new(0.299, 0.587, 0.114);
let (a, b, c, d) = (
LUMA.dot(x.cast()),
LUMA.dot(y.cast()),
LUMA.dot(z.cast()),
LUMA.dot(w.cast()),
);
// Step 2: luminance middlepoint
let min = a.min(b).min(c).min(d);
let max = a.max(b).max(c).max(d);
let mid = min + (max - min) / 2.0;
// Step 3: average colors based on binary mask
match FourBits::new(a > mid, b > mid, c > mid, d > mid) {
B0000 => ("", x.avg_with(y), z.avg_with(w)),
B0001 => ("", x.avg_with(y).avg_with(z), w),
B0010 => ("", x.avg_with(y).avg_with(w), z),
B0011 => ("", x.avg_with(y), z.avg_with(w)),
B0100 => ("", x.avg_with(z).avg_with(w), y),
B0101 => ("", x.avg_with(z), y.avg_with(w)),
B0110 => ("", x.avg_with(w), y.avg_with(z)),
B0111 => ("", y.avg_with(z).avg_with(w), x),
B1000 => ("", y.avg_with(z).avg_with(w), x),
B1001 => ("", y.avg_with(z), x.avg_with(w)),
B1010 => ("", y.avg_with(w), x.avg_with(z)),
B1011 => ("", x.avg_with(z).avg_with(w), y),
B1100 => ("", x.avg_with(y), z.avg_with(w)),
B1101 => ("", x.avg_with(y).avg_with(w), z),
B1110 => ("", x.avg_with(y).avg_with(z), w),
B1111 => ("", x.avg_with(y), z.avg_with(w)),
}
}

109
src/output/render_thread.rs Normal file
View File

@ -0,0 +1,109 @@
use std::{
sync::mpsc::{self, Receiver, Sender},
thread::{self, JoinHandle},
time::Instant,
};
use crate::cli::CommandLine;
use super::{FrameSync, Renderer};
/// Control a rendering thread that lazily starts.
/// This allows the `Bridge` struct to be used in places
/// where we do not expected the rendering thread to start.
pub struct RenderThread {
thread: Option<(Sender<Message>, JoinHandle<()>)>,
enabled: bool,
}
type RenderClosure = Box<dyn FnMut(&mut Renderer) + Send>;
enum Message {
Run(RenderClosure),
Shutdown,
}
impl RenderThread {
pub fn new() -> Self {
Self {
thread: None,
enabled: false,
}
}
/// Enable the rendering thread.
/// Allows the thread to be lazily initiated.
pub fn enable(&mut self) {
self.enabled = true
}
/// Stop the rendering thread.
/// Returns a `JoinHandle` if a thread was started.
pub fn stop(&mut self) -> Option<JoinHandle<()>> {
self.enabled = false;
self.send(Message::Shutdown);
let (_, handle) = self.thread.take()?;
Some(handle)
}
/// Run a closure on the rendering thread.
pub fn render<F>(&mut self, run: F)
where
F: FnMut(&mut Renderer) + Send + 'static,
{
self.send(Message::Run(Box::new(run)))
}
/// Boot the rendering thread, contains a simple event loop.
fn boot(rx: Receiver<Message>) {
let cmd = CommandLine::parse();
let mut sync = FrameSync::new(cmd.fps);
let mut renderer = Renderer::new();
let mut needs_render = false;
loop {
// Get the deadline for the next frame
let deadline = sync.deadline();
loop {
// Wait for some events before the deadline
match rx.recv_timeout(deadline - Instant::now()).ok() {
// Timeout and no message, render if needed
None => break,
// Shutdown the thread
Some(Message::Shutdown) => return,
// Run a closure and schedule a render
Some(Message::Run(mut closure)) => {
closure(&mut renderer);
needs_render = true;
}
}
}
// Render if needed
if needs_render {
needs_render = false;
// Update the frame sync timings
sync.start();
renderer.render().unwrap();
}
}
}
/// Send a message to the rendering thread.
/// Creates a new thread if enabled and needed.
fn send(&mut self, message: Message) {
if let Some((tx, _)) = &self.thread {
tx.send(message).unwrap()
} else if self.enabled {
let (tx, rx) = mpsc::channel();
tx.send(message).unwrap();
self.thread = Some((tx.clone(), thread::spawn(move || Self::boot(rx))));
}
}
}

View File

@ -10,24 +10,16 @@ use crate::{
gfx::{Color, Point, Rect, Size},
input::Key,
ui::navigation::{Navigation, NavigationAction},
utils::log,
};
use super::{Cell, Grapheme, Painter};
struct Dimensions {
/// Size of a terminal cell in pixels
cell: Size,
/// Size of the browser window in pixels
browser: Size,
/// Size of the terminal window in cells
terminal: Size,
}
pub struct Renderer {
nav: Navigation,
cells: Vec<(Cell, Cell)>,
dimensions: Dimensions,
painter: Painter,
size: Size,
}
impl Renderer {
@ -36,11 +28,7 @@ impl Renderer {
nav: Navigation::new(),
cells: Vec::with_capacity(0),
painter: Painter::new(),
dimensions: Dimensions {
cell: Size::new(7, 14),
browser: Size::new(0, 0),
terminal: Size::new(0, 0),
},
size: Size::new(0, 0),
}
}
@ -51,29 +39,21 @@ impl Renderer {
pub fn keypress(&mut self, key: &Key) -> io::Result<NavigationAction> {
let action = self.nav.keypress(key);
self.render()?;
Ok(action)
}
pub fn mouse_up(&mut self, origin: Point) -> io::Result<NavigationAction> {
let action = self.nav.mouse_up(origin);
self.render()?;
Ok(action)
}
pub fn mouse_down(&mut self, origin: Point) -> io::Result<NavigationAction> {
let action = self.nav.mouse_down(origin);
self.render()?;
Ok(action)
}
pub fn mouse_move(&mut self, origin: Point) -> io::Result<NavigationAction> {
let action = self.nav.mouse_move(origin);
self.render()?;
Ok(action)
}
@ -82,23 +62,20 @@ impl Renderer {
}
pub fn get_size(&self) -> Size {
self.dimensions.terminal
self.size
}
pub fn set_size(&mut self, cell: Size, terminal: Size) {
let size = (terminal.width + terminal.width * terminal.height) as usize;
self.nav.set_size(terminal);
self.dimensions.cell = cell;
self.dimensions.terminal = terminal;
self.dimensions.browser = cell * terminal;
pub fn set_size(&mut self, size: Size) {
self.nav.set_size(size);
self.size = size;
let mut x = 0;
let mut y = 0;
let bound = terminal.width - 1;
let bound = size.width - 1;
let cells = (size.width + size.width * size.height) as usize;
self.cells.clear();
self.cells.resize_with(size, || {
self.cells.resize_with(cells, || {
let cell = (Cell::new(x, y), Cell::new(x, y));
if x < bound {
@ -113,14 +90,19 @@ impl Renderer {
}
pub fn render(&mut self) -> io::Result<()> {
let size = self.dimensions.terminal;
let size = self.size;
for (origin, element) in self.nav.render(size) {
self.fill_rect(
Rect::new(origin.x, origin.y, element.text.width() as u32, 1),
element.background,
);
self.draw_text(&element.text, origin, Size::splat(0), element.foreground);
self.draw_text(
&element.text,
origin * (2, 1),
Size::splat(0),
element.foreground,
);
}
self.painter.begin()?;
@ -130,8 +112,7 @@ impl Renderer {
continue;
}
previous.top = current.top;
previous.bottom = current.bottom;
previous.quadrant = current.quadrant;
previous.grapheme = current.grapheme.clone();
self.painter.paint(current)?;
@ -143,53 +124,55 @@ impl Renderer {
}
/// Draw the background from a pixel array encoded in RGBA8888
pub fn draw_background(&mut self, pixels: &mut [u8], rect: Rect) -> io::Result<()> {
let viewport = self.dimensions.terminal.cast::<usize>();
let pixels_row = viewport.width * 4;
pub fn draw_background(&mut self, pixels: &[u8], pixels_size: Size, rect: Rect) {
let viewport = self.size.cast::<usize>();
if pixels.len() != pixels_row * viewport.height * 2 {
return Ok(());
if pixels.len() < viewport.width * viewport.height * 8 * 4 {
log::debug!(
"unexpected size, actual: {}, expected: {}",
pixels.len(),
viewport.width * viewport.height * 8 * 4
);
return;
}
let pos = rect.origin.cast::<usize>() / (1, 2);
let size = rect.size.cast::<usize>() / (1, 2);
let pixels_left = pos.x * 4;
let pixels_width = size.width * 4;
let origin = rect.origin.cast::<f32>().max(0.0) / (2.0, 4.0);
let size = rect.size.cast::<f32>().max(0.0) / (2.0, 4.0);
let top = (origin.y.floor() as usize).min(viewport.height);
let left = (origin.x.floor() as usize).min(viewport.width);
let right = ((origin.x + size.width).ceil() as usize)
.min(viewport.width)
.max(left);
let bottom = ((origin.y + size.height).ceil() as usize)
.min(viewport.height)
.max(top);
let row_length = pixels_size.width as usize;
let pixel = |x, y| {
Color::new(
pixels[((x + y * row_length) * 4 + 2) as usize],
pixels[((x + y * row_length) * 4 + 1) as usize],
pixels[((x + y * row_length) * 4 + 0) as usize],
)
};
let pair = |x, y| pixel(x, y).avg_with(pixel(x, y + 1));
// Iterate over each row
for y in pos.y..pos.y + size.height {
// Terminal chars have an aspect ratio of 2:1.
// In order to display perfectly squared pixels, we
// render a unicode glyph taking the bottom half of the cell
// using a foreground representing the bottom pixel,
// and a background representing the top pixel.
// This means that the pixel input buffer should be twice the size
// of the terminal cell buffer (two pixels take one terminal cell).
let left = pixels_left + y * 2 * pixels_row;
let right = left + pixels_width;
// Get a slice pointing to the top pixel row
let mut top_row = pixels[left..right].iter();
// Get a slice pointing to the bottom pixel row
let mut bottom_row = pixels[left + pixels_row..right + pixels_row].iter();
let cells_left = y * viewport.width + pos.x + viewport.width;
let cells = self.cells[cells_left..].iter_mut();
for y in top..bottom {
let index = (y + 1) * viewport.width;
let start = index + left;
let end = index + right;
let (mut x, y) = (left * 2, y * 4);
// Iterate over each column
for (_, cell) in cells {
match (
Color::from_iter(&mut top_row),
Color::from_iter(&mut bottom_row),
) {
(Some(top), Some(bottom)) => {
cell.top = top;
cell.bottom = bottom;
}
_ => break,
}
for (_, cell) in &mut self.cells[start..end] {
cell.quadrant = (
pair(x + 0, y + 0),
pair(x + 1, y + 0),
pair(x + 1, y + 2),
pair(x + 0, y + 2),
);
x += 2;
}
}
self.render()
}
pub fn clear_text(&mut self) {
@ -210,9 +193,8 @@ impl Renderer {
pub fn fill_rect(&mut self, rect: Rect, color: Color) {
self.draw(rect, |cell| {
cell.top = color;
cell.bottom = color;
cell.grapheme = None;
cell.quadrant = (color, color, color, color);
})
}
@ -222,7 +204,7 @@ impl Renderer {
{
let origin = bounds.origin.cast::<usize>();
let size = bounds.size.cast::<usize>();
let viewport_width = self.dimensions.terminal.width as usize;
let viewport_width = self.size.width as usize;
let top = origin.y;
let bottom = top + size.height;
@ -241,25 +223,28 @@ impl Renderer {
pub fn draw_text(&mut self, string: &str, origin: Point, size: Size, color: Color) {
// Get an iterator starting at the text origin
let len = self.cells.len();
let viewport = &self.dimensions.terminal;
let viewport = &self.size.cast::<usize>();
if size.width > 2 && size.height > 2 {
let x = origin.x.max(0).min(viewport.width as i32);
let top = (origin.y + 1).max(0);
let bottom = top + size.height as i32;
let origin = (origin.cast::<f32>() / (2.0, 4.0) + (0.0, 1.0)).round();
let size = (size.cast::<f32>() / (2.0, 4.0)).round();
let left = (origin.x.max(0.0) as usize).min(viewport.width);
let right = ((origin.x + size.width).max(0.0) as usize).min(viewport.width);
let top = (origin.y.max(0.0) as usize).min(viewport.height);
let bottom = ((origin.y + size.height).max(0.0) as usize).min(viewport.height);
for y in top..bottom {
let index = x + y / 2 * (viewport.width as i32);
let left = len.min(index as usize);
let right = len.min(left + size.width as usize);
let index = y * viewport.width;
let start = index + left;
let end = index + right;
for (_, cell) in self.cells[left..right].iter_mut() {
for (_, cell) in self.cells[start..end].iter_mut() {
cell.grapheme = None
}
}
} else {
// Compute the buffer index based on the position
let index = origin.x + (origin.y + 1) / 2 * (viewport.width as i32);
let index = origin.x / 2 + (origin.y + 1) / 4 * (viewport.width as i32);
let mut iter = self.cells[len.min(index as usize)..].iter_mut();
// Get every Unicode grapheme in the input string

View File

@ -1,43 +0,0 @@
use core::mem::MaybeUninit;
use std::{io, str::FromStr};
use crate::{gfx::Size, utils::log};
pub fn size() -> io::Result<Size> {
let mut ptr = MaybeUninit::<libc::winsize>::uninit();
let mut size = unsafe {
if libc::ioctl(libc::STDOUT_FILENO, libc::TIOCGWINSZ, ptr.as_mut_ptr()) == 0 {
Some(ptr.assume_init())
} else {
None
}
}
.ok_or_else(io::Error::last_os_error)?;
if size.ws_col == 0 || size.ws_row == 0 {
let cols = parse_var("COLUMNS").unwrap_or(80);
let rows = parse_var("LINES").unwrap_or(24);
log::warning!(
"TIOCGWINSZ returned an empty size ({}x{}), defaulting to {}x{}",
size.ws_col,
size.ws_row,
cols,
rows
);
size.ws_col = cols;
size.ws_row = rows;
}
Ok(Size::new(
if size.ws_col > 2 { size.ws_col } else { 1 },
// Keep some space for the UI
if size.ws_row > 2 { size.ws_row - 1 } else { 1 },
)
.cast())
}
fn parse_var<T: FromStr>(var: &str) -> Option<T> {
std::env::var(var).ok()?.parse().ok()
}

105
src/output/window.rs Normal file
View File

@ -0,0 +1,105 @@
use core::mem::MaybeUninit;
use std::str::FromStr;
use crate::{cli::CommandLine, gfx::Size, utils::log};
/// A terminal window.
#[derive(Clone, Debug)]
pub struct Window {
/// Device pixel ratio
pub dpi: f32,
/// Size of a terminal cell in pixels
pub scale: Size<f32>,
/// Size of the termina window in cells
pub cells: Size,
/// Size of the browser window in pixels
pub browser: Size,
/// Command line arguments
pub cmd: CommandLine,
}
impl Window {
/// Read the window
pub fn read() -> Window {
let mut window = Self {
dpi: 1.0,
scale: (0.0, 0.0).into(),
cells: (0, 0).into(),
browser: (0, 0).into(),
cmd: CommandLine::parse(),
};
window.update();
window
}
pub fn update(&mut self) -> &Self {
let (mut term, mut cell) = unsafe {
let mut ptr = MaybeUninit::<libc::winsize>::uninit();
if libc::ioctl(libc::STDOUT_FILENO, libc::TIOCGWINSZ, ptr.as_mut_ptr()) == 0 {
let size = ptr.assume_init();
(
Size::new(size.ws_col, size.ws_row),
Size::new(size.ws_xpixel, size.ws_ypixel),
)
} else {
(Size::splat(0), Size::splat(0))
}
};
if cell.width == 0 || cell.height == 0 {
cell.width = 8;
cell.height = 16;
}
if term.width == 0 || term.height == 0 {
let cols = match parse_var("COLUMNS").unwrap_or(0) {
0 => 80,
x => x,
};
let rows = match parse_var("LINES").unwrap_or(0) {
0 => 24,
x => x,
};
log::warning!(
"TIOCGWINSZ returned an empty size ({}x{}), defaulting to {}x{}",
term.width,
term.height,
cols,
rows
);
term.width = cols;
term.height = rows;
}
let zoom = 1.5 * self.cmd.zoom;
let cells = Size::new(term.width.max(1), term.height.max(2) - 1);
let auto_scale = false;
let cell_pixels = if auto_scale {
Size::new(cell.width as f32, cell.height as f32) / cells.cast()
} else {
Size::new(8.0, 16.0)
};
// Normalize the cells dimensions for an aspect ratio of 1:2
let cell_width = (cell_pixels.width + cell_pixels.height / 2.0) / 2.0;
// Round DPI to 2 decimals for proper viewport computations
self.dpi = (2.0 / cell_width * zoom * 100.0).ceil() / 100.0;
// A virtual cell should contain a 2x4 pixel quadrant
self.scale = Size::new(2.0, 4.0) / self.dpi;
// Keep some space for the UI
self.cells = Size::new(term.width.max(1), term.height.max(2) - 1).cast();
self.browser = self.cells.cast::<f32>().mul(self.scale).ceil().cast();
self
}
}
fn parse_var<T: FromStr>(var: &str) -> Option<T> {
std::env::var(var).ok()?.parse().ok()
}

View File

@ -189,8 +189,9 @@ impl Navigation {
}
pub fn render(&self, size: Size) -> Vec<(Point, NavigationElement)> {
let space = if size.width >= 13 {
size.width as usize - 13
let ui_elements = 13;
let space = if size.width >= ui_elements {
(size.width - ui_elements) as usize
} else {
0
};

View File

@ -1,4 +1,8 @@
pub mod log;
mod four_bits;
mod try_block;
pub mod log;
use try_block::*;
pub use four_bits::*;

44
src/utils/four_bits.rs Normal file
View File

@ -0,0 +1,44 @@
pub enum FourBits {
B0000 = 0b0000,
B0001 = 0b0001,
B0010 = 0b0010,
B0011 = 0b0011,
B0100 = 0b0100,
B0101 = 0b0101,
B0110 = 0b0110,
B0111 = 0b0111,
B1000 = 0b1000,
B1001 = 0b1001,
B1010 = 0b1010,
B1011 = 0b1011,
B1100 = 0b1100,
B1101 = 0b1101,
B1110 = 0b1110,
B1111 = 0b1111,
}
impl FourBits {
pub fn new(x: bool, y: bool, z: bool, w: bool) -> Self {
use FourBits::*;
match (x as u8) << 3 | (y as u8) << 2 | (z as u8) << 1 | (w as u8) << 0 {
0b0000 => B0000,
0b0001 => B0001,
0b0010 => B0010,
0b0011 => B0011,
0b0100 => B0100,
0b0101 => B0101,
0b0110 => B0110,
0b0111 => B0111,
0b1000 => B1000,
0b1001 => B1001,
0b1010 => B1010,
0b1011 => B1011,
0b1100 => B1100,
0b1101 => B1101,
0b1110 => B1110,
0b1111 => B1111,
_ => panic!("Unexpected mask value"),
}
}
}