Repository: fathyb/carbonyl Branch: main Commit: ab80a276b1bd Files: 104 Total size: 274.8 KB Directory structure: gitextract_4jcv0hr0/ ├── .cargo/ │ └── config.toml ├── .dockerignore ├── .github/ │ └── funding.yml ├── .gitignore ├── .gitmodules ├── .refloat/ │ └── config.js ├── Cargo.toml ├── Dockerfile ├── build.rs ├── changelog.md ├── chromium/ │ ├── .gclient │ └── patches/ │ ├── chromium/ │ │ ├── 0001-Add-Carbonyl-library.patch │ │ ├── 0002-Add-Carbonyl-service.patch │ │ ├── 0003-Setup-shared-software-rendering-surface.patch │ │ ├── 0004-Setup-browser-default-settings.patch │ │ ├── 0005-Remove-some-debug-assertions.patch │ │ ├── 0006-Setup-display-DPI.patch │ │ ├── 0007-Disable-text-effects.patch │ │ ├── 0008-Fix-text-layout.patch │ │ ├── 0009-Bridge-browser-into-Carbonyl-library.patch │ │ ├── 0010-Conditionally-enable-text-rendering.patch │ │ ├── 0011-Rename-carbonyl-Renderer-to-carbonyl-Bridge.patch │ │ ├── 0012-Create-separate-bridge-for-Blink.patch │ │ ├── 0013-Refactor-rendering-bridge.patch │ │ └── 0014-Move-Skia-text-rendering-control-to-bridge.patch │ ├── skia/ │ │ ├── 0001-Disable-text-rendering.patch │ │ └── 0002-Export-some-private-APIs.patch │ └── webrtc/ │ └── 0001-Disable-GIO-on-Linux.patch ├── cliff.toml ├── license.md ├── package.json ├── readme.md ├── scripts/ │ ├── build.sh │ ├── changelog.sh │ ├── copy-binaries.sh │ ├── docker-build.sh │ ├── docker-push.sh │ ├── env.sh │ ├── gclient.sh │ ├── gn.sh │ ├── npm-package.mjs │ ├── npm-package.sh │ ├── npm-publish.sh │ ├── patches.sh │ ├── platform-triple.sh │ ├── release.sh │ ├── restore-mtime.sh │ ├── run.sh │ ├── runtime-hash.sh │ ├── runtime-pull.sh │ └── runtime-push.sh └── src/ ├── browser/ │ ├── BUILD.gn │ ├── args.gn │ ├── bridge.cc │ ├── bridge.h │ ├── bridge.rs │ ├── carbonyl.mojom │ ├── export.h │ ├── host_display_client.cc │ ├── host_display_client.h │ ├── render_service_impl.cc │ ├── render_service_impl.h │ ├── renderer.cc │ └── renderer.h ├── browser.rs ├── cli/ │ ├── cli.rs │ ├── program.rs │ └── usage.txt ├── cli.rs ├── gfx/ │ ├── color.rs │ ├── point.rs │ ├── rect.rs │ ├── size.rs │ └── vector.rs ├── gfx.rs ├── input/ │ ├── dcs/ │ │ ├── control_flow.rs │ │ ├── parser.rs │ │ ├── resource.rs │ │ └── status.rs │ ├── dcs.rs │ ├── keyboard.rs │ ├── listen.rs │ ├── mouse.rs │ ├── parser.rs │ └── tty.rs ├── input.rs ├── lib.rs ├── output/ │ ├── cell.rs │ ├── frame_sync.rs │ ├── kd_tree.rs │ ├── painter.rs │ ├── quad.rs │ ├── quantizer.rs │ ├── render_thread.rs │ ├── renderer.rs │ ├── window.rs │ └── xterm.rs ├── output.rs ├── ui/ │ └── navigation.rs ├── ui.rs ├── utils/ │ ├── four_bits.rs │ ├── log.rs │ └── try_block.rs └── utils.rs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .cargo/config.toml ================================================ [build] target-dir = "build" ================================================ FILE: .dockerignore ================================================ /build !/build/browser /chromium ================================================ FILE: .github/funding.yml ================================================ github: fathyb ================================================ FILE: .gitignore ================================================ .DS_Store node_modules/ /.ccache /.git_cache /.vscode /build /chromium/* !/chromium/.gclient !/chromium/depot_tools !/chromium/patches /packages/*/build ================================================ FILE: .gitmodules ================================================ [submodule "chromium/depot_tools"] path = chromium/depot_tools url = https://chromium.googlesource.com/chromium/tools/depot_tools.git ================================================ FILE: .refloat/config.js ================================================ 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 lib = (platform, arch) => `build/${triple(platform, arch)}/release/libcarbonyl.${sharedLib[platform]}`; const sharedLib = { macos: "dylib", linux: "so", }; const platforms = { macos: "apple-darwin", linux: "unknown-linux-gnu", }; const archs = { arm64: "aarch64", amd64: "x86_64", }; export const jobs = ["macos", "linux"].flatMap((platform) => { return [ { name: `Build runtime (${platform})`, agent: { tags: ["chromium-src", platform] }, steps: [ ...["arm64", "amd64"].map((arch) => ({ import: { workspace: `core-${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} cp \\ ${lib(platform, arch)} \\ build/pre-built/${triple(platform, arch)} fi `, })), }, { name: "Fetch Chromium", command: ` if [ -z "$CHROMIUM_ROOT" ]; then echo "Chromium build environment not setup" 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 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", }, }, { 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 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", }, ], } ); } ================================================ FILE: Cargo.toml ================================================ [package] name = "carbonyl" version = "0.0.3" edition = "2021" [dependencies] libc = "0.2" unicode-width = "0.1.10" unicode-segmentation = "1.10.0" chrono = "0.4.23" [lib] name = "carbonyl" path = "src/lib.rs" crate-type = ["cdylib"] ================================================ FILE: Dockerfile ================================================ FROM rust:1.67 AS cross-compile RUN apt-get update && \ apt-get install -y \ zip g++-aarch64-linux-gnu g++-x86-64-linux-gnu libc6-dev-arm64-cross libc6-dev-amd64-cross && \ rustup target add aarch64-unknown-linux-gnu x86_64-unknown-linux-gnu && \ rustup toolchain install stable-aarch64-unknown-linux-gnu stable-x86_64-unknown-linux-gnu && \ rm -rf /var/lib/apt/lists/* ENV AR_AARCH64_UNKNOWN_LINUX_GNU=aarch64-linux-gnu-ar ENV CC_AARCH64_UNKNOWN_LINUX_GNU=aarch64-linux-gnu-gcc ENV CXX_AARCH64_UNKNOWN_LINUX_GNU=aarch64-linux-gnu-g++ ENV CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc ENV AR_X86_64_UNKNOWN_LINUX_GNU=x86_64-linux-gnu-ar ENV CC_X86_64_UNKNOWN_LINUX_GNU=x86_64-linux-gnu-gcc ENV CXX_X86_64_UNKNOWN_LINUX_GNU=x86_64-linux-gnu-g++ ENV CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=x86_64-linux-gnu-gcc FROM debian:bullseye-slim RUN groupadd -r carbonyl && \ useradd -r -g carbonyl carbonyl && \ mkdir -p /carbonyl/data && \ chown -R carbonyl:carbonyl /carbonyl && \ apt-get update && \ 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"] ================================================ FILE: build.rs ================================================ use std::path::PathBuf; #[cfg(target_arch = "x86_64")] fn link_sysroot() { let sysroot_path = PathBuf::from("./chromium/src/build/linux/debian_bullseye_amd64-sysroot"); if sysroot_path.is_dir() { println!("cargo:rustc-link-search=chromium/src/build/linux/debian_bullseye_amd64-sysroot/lib/x86_64-linux-gnu"); println!("cargo:rustc-link-search=chromium/src/build/linux/debian_bullseye_amd64-sysroot/usr/lib/x86_64-linux-gnu"); println!( "cargo:rustc-link-arg=--sysroot=./chromium/src/build/linux/debian_bullseye_amd64-sysroot" ); } else { println!("cargo:warning={}", "x86_64 debian sysroot provided by chromium was not found!"); println!("cargo:warning={}", "carbonyl may fail to link against a proper libc!"); } } #[cfg(target_arch = "x86")] fn link_sysroot() { let sysroot_path = PathBuf::from("./chromium/src/build/linux/debian_bullseye_i386-sysroot"); if sysroot_path.is_dir() { println!("cargo:rustc-link-search=chromium/src/build/linux/debian_bullseye_i386-sysroot/lib/i386-linux-gnu"); println!("cargo:rustc-link-search=chromium/src/build/linux/debian_bullseye_i386-sysroot/usr/lib/i386-linux-gnu"); println!( "cargo:rustc-link-arg=--sysroot=./chromium/src/build/linux/debian_bullseye_i386-sysroot" ); } else { println!("cargo:warning={}", "x86 debian sysroot provided by chromium was not found!"); println!("cargo:warning={}", "carbonyl may fail to link against a proper libc!"); } } #[cfg(not(any(target_arch = "x86_64", target_arch = "x86")))] fn link_sysroot() { // Intentionally left blank. } fn main() { link_sysroot(); } ================================================ FILE: changelog.md ================================================ # Changelog All notable changes to this project will be documented in this file. ## [0.0.3] - 2023-02-18 ### 🚀 Features - Add `--help` and `--version` ([#105](https://github.com/fathyb/carbonyl/issues/105)) - Add logo and description to `--help` ([#106](https://github.com/fathyb/carbonyl/issues/106)) - Use Cmd instead of Alt for navigation shortcuts ([#109](https://github.com/fathyb/carbonyl/issues/109)) - Enable h.264 support ([#103](https://github.com/fathyb/carbonyl/issues/103)) - Introduce quadrant rendering ([#120](https://github.com/fathyb/carbonyl/issues/120)) ### 🐛 Bug Fixes - Fix arguments parsing ([#108](https://github.com/fathyb/carbonyl/issues/108)) - Fix missing module error on npm package ([#113](https://github.com/fathyb/carbonyl/issues/113)) - Enable threaded compositing with bitmap mode - Fix idling CPU usage ([#126](https://github.com/fathyb/carbonyl/issues/126)) - Package proper library in binaries ([#127](https://github.com/fathyb/carbonyl/issues/127)) ### 📖 Documentation - Update download links - Fix commit_preprocessors url ([#102](https://github.com/fathyb/carbonyl/issues/102)) - Add `--rm` to Docker example ([#101](https://github.com/fathyb/carbonyl/issues/101)) ## [0.0.2] - 2023-02-09 ### 🚀 Features - Better true color detection - Linux support - Xterm title - Hide stderr unless crash - Add `--debug` to print stderr on exit ([#23](https://github.com/fathyb/carbonyl/issues/23)) - Add navigation UI ([#86](https://github.com/fathyb/carbonyl/issues/86)) - Handle terminal resize ([#87](https://github.com/fathyb/carbonyl/issues/87)) ### 🐛 Bug Fixes - Parser fixes - Properly enter tab and return keys - Fix some special characters ([#35](https://github.com/fathyb/carbonyl/issues/35)) - Improve terminal size detection ([#36](https://github.com/fathyb/carbonyl/issues/36)) - Allow working directories that contain spaces ([#63](https://github.com/fathyb/carbonyl/issues/63)) - Do not use tags for checkout ([#64](https://github.com/fathyb/carbonyl/issues/64)) - Do not checkout nacl ([#79](https://github.com/fathyb/carbonyl/issues/79)) - Wrap zip files in carbonyl folder ([#88](https://github.com/fathyb/carbonyl/issues/88)) - Fix WebGL support on Linux ([#90](https://github.com/fathyb/carbonyl/issues/90)) - Fix initial freeze on Docker ([#91](https://github.com/fathyb/carbonyl/issues/91)) ### 📖 Documentation - Upload demo videos - Fix video layout - Fix a typo ([#1](https://github.com/fathyb/carbonyl/issues/1)) - Fix a typo `ie.` -> `i.e.` ([#9](https://github.com/fathyb/carbonyl/issues/9)) - Fix build instructions ([#15](https://github.com/fathyb/carbonyl/issues/15)) - Add ascii logo - Add comparisons ([#34](https://github.com/fathyb/carbonyl/issues/34)) - Add OS support ([#50](https://github.com/fathyb/carbonyl/issues/50)) - Add download link - Fix linux download links - Document shared library - Fix a typo (`know` -> `known`) ([#71](https://github.com/fathyb/carbonyl/issues/71)) - Add license ### Build - Various build system fixes ([#20](https://github.com/fathyb/carbonyl/issues/20)) ================================================ FILE: chromium/.gclient ================================================ solutions = [ { "name": "src", "url": "https://chromium.googlesource.com/chromium/src.git@111.0.5511.1", "managed": False, "custom_deps": {}, "custom_vars": { "use_rust": True, "checkout_pgo_profiles": True, "checkout_nacl": False, } }, ] ================================================ FILE: chromium/patches/chromium/0001-Add-Carbonyl-library.patch ================================================ From 0ed9a390f25d73492ce1170ce229b95772fd458d Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Thu, 9 Feb 2023 03:20:50 +0100 Subject: [PATCH 01/14] Add Carbonyl library --- carbonyl/build | 1 + carbonyl/src | 1 + headless/BUILD.gn | 15 ++++++++++++++- 3 files changed, 16 insertions(+), 1 deletion(-) create mode 120000 carbonyl/build create mode 120000 carbonyl/src diff --git a/carbonyl/build b/carbonyl/build new file mode 120000 index 0000000000000..44735d5866459 --- /dev/null +++ b/carbonyl/build @@ -0,0 +1 @@ +../../../build \ No newline at end of file diff --git a/carbonyl/src b/carbonyl/src new file mode 120000 index 0000000000000..dabb0e15a991e --- /dev/null +++ b/carbonyl/src @@ -0,0 +1 @@ +../../../src \ No newline at end of file diff --git a/headless/BUILD.gn b/headless/BUILD.gn index bfae1e3290de0..8018111ed9898 100644 --- a/headless/BUILD.gn +++ b/headless/BUILD.gn @@ -453,6 +453,7 @@ component("headless_non_renderer") { "//build:branding_buildflags", "//build:branding_buildflags", "//build:chromeos_buildflags", + "//carbonyl/src/browser:carbonyl", "//components/cookie_config", "//components/crash/core/common:common", "//components/embedder_support", @@ -993,13 +994,25 @@ static_library("headless_shell_lib") { } executable("headless_shell") { + if (is_mac && !use_lld) { + ldflags = [ "-Wl,-no_compact_unwind" ] + } else if (is_linux) { + ldflags = [ + "-Wl,-rpath=\$ORIGIN/.", + "-Wl,-rpath-link=.", + ] + } + configs -= [ "//build/config/compiler:thinlto_optimize_default" ] configs += [ "//build/config/compiler:thinlto_optimize_max" ] sources = [ "app/headless_shell_main.cc" ] defines = [] - deps = [ ":headless_shell_lib" ] + deps = [ + ":headless_shell_lib", + "//carbonyl/src/browser:carbonyl", + ] if (!headless_use_embedded_resources) { data = [ ================================================ FILE: chromium/patches/chromium/0002-Add-Carbonyl-service.patch ================================================ From 795b29828fd7ac95548c4dcab483cbc3b6c1d361 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Thu, 9 Feb 2023 03:21:59 +0100 Subject: [PATCH 02/14] Add Carbonyl service --- cc/trees/layer_tree_host.cc | 21 ++ cc/trees/layer_tree_host.h | 8 + content/browser/browser_interface_binders.cc | 4 + .../renderer_host/render_frame_host_impl.cc | 6 + .../renderer_host/render_frame_host_impl.h | 7 + content/renderer/render_frame_impl.cc | 288 ++++++++++++++++++ content/renderer/render_frame_impl.h | 8 + .../blink/public/web/web_frame_widget.h | 7 + .../blink/renderer/platform/fonts/font.cc | 63 +++- 9 files changed, 409 insertions(+), 3 deletions(-) diff --git a/cc/trees/layer_tree_host.cc b/cc/trees/layer_tree_host.cc index 55295cc3f1e2f..20fa1030fb4e8 100644 --- a/cc/trees/layer_tree_host.cc +++ b/cc/trees/layer_tree_host.cc @@ -481,6 +481,24 @@ bool LayerTreeHost::MainFrameUpdatesAreDeferred() const { bool LayerTreeHost::IsUsingLayerLists() const { return settings_.use_layer_lists; } + +void LayerTreeHost::StartTerminalRender() { + DCHECK(IsMainThread()); + + auto it = terminal_render_callbacks_.begin(); + + while (it != terminal_render_callbacks_.end()) { + auto& callback = *it->get(); + + if (callback) { + callback(); + + it++; + } else { + it = terminal_render_callbacks_.erase(it); + } + } +} void LayerTreeHost::CommitComplete(const CommitTimestamps& commit_timestamps) { DCHECK(IsMainThread()); @@ -1648,6 +1666,9 @@ bool LayerTreeHost::PaintContent(const LayerList& update_layer_list) { for (const auto& layer : update_layer_list) { did_paint_content |= layer->Update(); } + + StartTerminalRender(); + return did_paint_content; } diff --git a/cc/trees/layer_tree_host.h b/cc/trees/layer_tree_host.h index c26301594abc2..ed217c22359c4 100644 --- a/cc/trees/layer_tree_host.h +++ b/cc/trees/layer_tree_host.h @@ -167,6 +167,12 @@ class CC_EXPORT LayerTreeHost : public MutatorHostClient { // Returns the process global unique identifier for this LayerTreeHost. int GetId() const; + // Carbonyl + void StartTerminalRender(); + void ObserveTerminalRender(std::shared_ptr> callback) { + terminal_render_callbacks_.push_back(callback); + } + // The commit state for the frame being assembled by the compositor host. const CommitState* pending_commit_state() const { DCHECK(IsMainThread()); @@ -1086,6 +1092,8 @@ class CC_EXPORT LayerTreeHost : public MutatorHostClient { bool syncing_deltas_for_test_ = false; + std::vector>> terminal_render_callbacks_; + base::WeakPtrFactory weak_ptr_factory_{this}; }; diff --git a/content/browser/browser_interface_binders.cc b/content/browser/browser_interface_binders.cc index 6e62b36a76799..e07dcb983698b 100644 --- a/content/browser/browser_interface_binders.cc +++ b/content/browser/browser_interface_binders.cc @@ -887,6 +887,10 @@ void PopulateFrameBinders(RenderFrameHostImpl* host, mojo::BinderMap* map) { base::BindRepeating(&RenderFrameHostImpl::GetVirtualAuthenticatorManager, base::Unretained(host))); + map->Add( + base::BindRepeating(&RenderFrameHostImpl::GetCarbonylRenderService, + base::Unretained(host))); + map->Add( base::BindRepeating(&BindDevicePostureProvider)); diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc index 8ba82589beb32..cc0ad634bcb4c 100644 --- a/content/browser/renderer_host/render_frame_host_impl.cc +++ b/content/browser/renderer_host/render_frame_host_impl.cc @@ -10731,6 +10731,12 @@ void RenderFrameHostImpl::BindTrustTokenQueryAnswerer( std::move(receiver), ComputeTopFrameOrigin(GetLastCommittedOrigin())); } +void RenderFrameHostImpl::GetCarbonylRenderService( + mojo::PendingReceiver receiver) { + carbonyl_render_service_ = std::make_unique( + std::move(receiver)); +} + void RenderFrameHostImpl::GetAudioContextManager( mojo::PendingReceiver receiver) { AudioContextManagerImpl::Create(this, std::move(receiver)); diff --git a/content/browser/renderer_host/render_frame_host_impl.h b/content/browser/renderer_host/render_frame_host_impl.h index db16f07685acf..8c36cce0ec604 100644 --- a/content/browser/renderer_host/render_frame_host_impl.h +++ b/content/browser/renderer_host/render_frame_host_impl.h @@ -176,6 +176,8 @@ #include "media/mojo/mojom/remoting.mojom-forward.h" #endif +#include "carbonyl/src/browser/render_service_impl.h" + namespace blink { class AssociatedInterfaceRegistry; class DocumentPolicy; @@ -1834,6 +1836,9 @@ class CONTENT_EXPORT RenderFrameHostImpl // Set the `frame_` for sending messages to the renderer process. void SetMojomFrameRemote(mojo::PendingAssociatedRemote); + void GetCarbonylRenderService( + mojo::PendingReceiver receiver); + void GetAudioContextManager( mojo::PendingReceiver receiver); @@ -4720,6 +4725,8 @@ class CONTENT_EXPORT RenderFrameHostImpl // The observers watching our state changed event. base::ObserverList observers_; + std::unique_ptr carbonyl_render_service_; + // BrowserInterfaceBroker implementation through which this // RenderFrameHostImpl exposes document-scoped Mojo services to the currently // active document in the corresponding RenderFrame. diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc index 9e09c5342699e..97b61ffb954be 100644 --- a/content/renderer/render_frame_impl.cc +++ b/content/renderer/render_frame_impl.cc @@ -232,6 +232,7 @@ #include "third_party/blink/public/web/web_view.h" #include "third_party/blink/public/web/web_widget.h" #include "third_party/blink/public/web/web_window_features.h" +#include "third_party/skia/include/core/SkPictureRecorder.h" #include "ui/accessibility/ax_tree_update.h" #include "ui/events/base_event_utils.h" #include "url/origin.h" @@ -255,6 +256,39 @@ #include "content/renderer/java/gin_java_bridge_dispatcher.h" #endif +// Carbonyl +#include +#include +#include "cc/paint/paint_recorder.h" +#include "cc/paint/skia_paint_canvas.h" +#include "cc/raster/playback_image_provider.h" +#include "cc/tiles/software_image_decode_cache.h" +#include "cc/trees/layer_tree_host.h" +#include "cc/trees/render_frame_metadata_observer.h" +#include "components/paint_preview/common/paint_preview_tracker.h" +#include "third_party/blink/renderer/core/exported/web_view_impl.h" +#include "third_party/blink/renderer/core/dom/frame_request_callback_collection.h" +#include "third_party/blink/renderer/core/frame/local_frame_view.h" +#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h" +#include "third_party/blink/renderer/core/paint/paint_flags.h" +#include "third_party/blink/renderer/core/layout/layout_view.h" +#include "third_party/blink/renderer/platform/graphics/paint/cull_rect.h" +#include "third_party/blink/renderer/platform/graphics/paint/paint_record_builder.h" +#include "third_party/skia/include/core/SkEncodedImageFormat.h" +#include "third_party/skia/include/core/SkMesh.h" +#include "third_party/skia/include/core/SkStream.h" +#include "third_party/skia/include/core/SkSurface.h" +#include "third_party/skia/include/core/SkVertices.h" +#include "third_party/skia/include/docs/SkPDFDocument.h" +#include "third_party/skia/include/svg/SkSVGCanvas.h" +#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/SkClipStackDevice.h" +#include "third_party/skia/src/core/SkDevice.h" +#include "third_party/skia/src/core/SkFontPriv.h" +#include "third_party/skia/src/utils/SkUTF.h" + using base::Time; using blink::ContextMenuData; using blink::WebContentDecryptionModule; @@ -1912,8 +1946,229 @@ RenderFrameImpl::~RenderFrameImpl() { base::trace_event::TraceLog::GetInstance()->RemoveProcessLabel(routing_id_); g_routing_id_frame_map.Get().erase(routing_id_); agent_scheduling_group_.RemoveRoute(routing_id_); + + if (auto& callback = *render_callback_.get()) { + callback = nullptr; + } } +} // namespace content + +namespace carbonyl { +class TextCaptureDevice: public SkClipStackDevice { +public: + TextCaptureDevice( + const SkImageInfo& info, + const SkSurfaceProps& props + ): + SkClipStackDevice(info, props) + { + clear(SkRect::MakeWH(info.width(), info.height())); + } + + void swap(std::vector& data) { + data.swap(data_); + } + + void clear() { + data_.clear(); + } + + void clear(const SkRect& rect) { + data_.push_back( + carbonyl::mojom::TextData::New( + std::string(), + gfx::SkRectToRectF(rect), + 0 + ) + ); + } + +protected: + SkBaseDevice* onCreateDevice(const CreateInfo& info, const SkPaint*) override { + return new TextCaptureDevice(info.fInfo, SkSurfaceProps(0, info.fPixelGeometry)); + } + + void drawDevice(SkBaseDevice* baseDevice, const SkSamplingOptions&, const SkPaint& paint) override { + if(isUnsupportedPaint(paint)) { + return; + } + + auto blendMode = paint.getBlendMode_or(SkBlendMode::kClear); + + if (blendMode != SkBlendMode::kSrc && blendMode != SkBlendMode::kSrcOver) { + return; + } + + auto* device = static_cast(baseDevice); + SkMatrix transform = device->getRelativeTransform(*this); + + for (auto& data: device->data_) { + data_.push_back( + carbonyl::mojom::TextData::New( + data->contents, + gfx::SkRectToRectF(transform.mapRect(gfx::RectFToSkRect(data->bounds))), + data->color + ) + ); + } + } + + void drawPaint(const SkPaint&) override {} + void drawOval(const SkRect&, const SkPaint&) override {} + void drawPoints(SkCanvas::PointMode, size_t, const SkPoint[], const SkPaint&) override {} + void drawImageRect(const SkImage*, + const SkRect*, + const SkRect& rect, + const SkSamplingOptions&, + const SkPaint&, + SkCanvas::SrcRectConstraint) override { + // clear(scale(rect)); + } + + void drawVertices(const SkVertices* vertices, + sk_sp, + const SkPaint& paint, + bool = false) override { + drawRect(vertices->bounds(), paint); + } + + void drawMesh(const SkMesh& mesh, sk_sp, const SkPaint& paint) override { + drawRect(mesh.bounds(), paint); + } + + void drawPath(const SkPath& path, const SkPaint& paint, bool = false) override { + drawRect(path.getBounds(), paint); + } + + void drawRRect(const SkRRect& rect, const SkPaint& paint) override { + drawRect(rect.rect(), paint); + } + + bool isUnsupportedPaint(const SkPaint& paint) { + return ( + paint.getShader() || + paint.getBlender() || + paint.getPathEffect() || + paint.getMaskFilter() || + paint.getImageFilter() || + paint.getColorFilter() || + paint.getImageFilter() + ); + } + + void drawRect(const SkRect& rect, const SkPaint& paint) override { + if ( + paint.getStyle() == SkPaint::Style::kFill_Style && + paint.getAlphaf() == 1.0 && + !isUnsupportedPaint(paint) + ) { + auto blendMode = paint.getBlendMode_or(SkBlendMode::kClear); + + if (blendMode == SkBlendMode::kSrc || blendMode == SkBlendMode::kSrcOver) { + clear(scale(rect)); + } else { + std::cerr << "Blending mode: " << SkBlendMode_Name(blendMode) << std::endl; + } + } + } + + void onDrawGlyphRunList(SkCanvas*, + const sktext::GlyphRunList& glyphRunList, + const SkPaint&, + const SkPaint& paint) override { + auto position = scale(glyphRunList.origin()); + + for (auto& glyphRun : glyphRunList) { + auto runSize = glyphRun.runSize(); + SkAutoSTArray<64, SkUnichar> unichars(runSize); + SkFontPriv::GlyphsToUnichars(glyphRun.font(), glyphRun.glyphsIDs().data(), + runSize, unichars.get()); + + auto base64_ptr = std::make_unique(runSize + 1); + char* base64 = base64_ptr.get(); + + for (size_t i = 0; i < runSize; ++i) { + base64[i] = unichars[i]; + } + + base64[runSize] = '\0'; + + size_t size = 0; + auto error = SkBase64::Decode(base64, runSize, nullptr, &size); + + if (error != SkBase64::kNoError) { + return; + } + + auto utf8_ptr = std::make_unique(size); + char* utf8 = utf8_ptr.get(); + + error = SkBase64::Decode(base64, runSize, utf8, &size); + + if (error != SkBase64::kNoError) { + return; + } + + data_.push_back( + carbonyl::mojom::TextData::New( + std::string(utf8, size), + gfx::RectF(position.x(), position.y(), 0, 0), + paint.getColor() + ) + ); + } + } + +private: + SkRect scale(const SkRect& rect) { + return localToDevice().mapRect(rect); + } + SkPoint scale(const SkPoint& point) { + return localToDevice().mapPoint(point); + } + + std::vector data_; +}; + +class RendererService { + public: + RendererService() = default; + + SkCanvas* BeginPaint(int width, int height) { + if (width != width_ || height != height_ || !device_) { + width_ = width; + height_ = height; + + device_ = sk_sp( + new TextCaptureDevice( + SkImageInfo::MakeUnknown(width, height), + SkSurfaceProps(0, kUnknown_SkPixelGeometry) + ) + ); + canvas_ = std::make_unique(device_); + } + + device_->clear(); + + return canvas_.get(); + } + + void Swap(std::vector& data) { + device_->swap(data); + } + + private: + int width_ = 0; + int height_ = 0; + sk_sp device_; + std::unique_ptr canvas_; +}; + +} // namespace carbonyl + +namespace content { + void RenderFrameImpl::Initialize(blink::WebFrame* parent) { initialized_ = true; is_main_frame_ = !parent; @@ -1942,6 +2197,8 @@ void RenderFrameImpl::Initialize(blink::WebFrame* parent) { factory.RegisterRemoteFactory(GetWebFrame()->GetLocalFrameToken(), GetBrowserInterfaceBroker()); } + + browser_interface_broker_proxy_.GetInterface(std::move(carbonyl_render_service_receiver_)); frame_request_blocker_ = blink::WebFrameRequestBlocker::Create(); @@ -1954,6 +2211,37 @@ void RenderFrameImpl::Initialize(blink::WebFrame* parent) { agent_scheduling_group_.AddFrameRoute( routing_id_, this, GetTaskRunner(blink::TaskType::kInternalNavigationAssociated)); + + if (base::CommandLine::ForCurrentProcess()->HasSwitch("carbonyl-b64-text")) { + setenv("carbonyl_b64_text", "true", 1); + } + + auto* host = GetLocalRootWebFrameWidget()->LayerTreeHost(); + auto renderer = std::make_shared(); + + render_callback_ = std::make_shared>( + [=]() -> bool { + if (!IsMainFrame() || IsHidden()) { + return false; + } + + size_t width = frame_->DocumentSize().width(); + size_t height = frame_->VisibleContentRect().height(); + auto* view = static_cast(GetWebFrame()->View()); + std::vector data; + + view->MainFrameImpl()->GetFrame()->View()->GetPaintRecord().Playback( + renderer->BeginPaint(width, height) + ); + + renderer->Swap(data); + carbonyl_render_service_->DrawText(std::move(data)); + + return true; + } + ); + + host->ObserveTerminalRender(render_callback_); } void RenderFrameImpl::GetInterface( diff --git a/content/renderer/render_frame_impl.h b/content/renderer/render_frame_impl.h index 3b558293121ce..f996b74bfae7e 100644 --- a/content/renderer/render_frame_impl.h +++ b/content/renderer/render_frame_impl.h @@ -118,6 +118,8 @@ #include "content/common/pepper_plugin.mojom.h" #endif +#include "carbonyl/src/browser/carbonyl.mojom.h" + namespace blink { namespace scheduler { class WebAgentGroupScheduler; @@ -1446,6 +1448,8 @@ class CONTENT_EXPORT RenderFrameImpl std::unique_ptr web_url_loader_factory_override_for_test_; + std::shared_ptr> render_callback_; + // When the browser asks the renderer to commit a navigation, it should always // result in a committed navigation reported via DidCommitProvisionalLoad(). // This is important because DidCommitProvisionalLoad() is responsible for @@ -1522,6 +1526,10 @@ class CONTENT_EXPORT RenderFrameImpl // false, but set to true by some tests. bool send_content_state_immediately_ = false; + mojo::Remote carbonyl_render_service_; + mojo::PendingReceiver carbonyl_render_service_receiver_ = + carbonyl_render_service_.BindNewPipeAndPassReceiver(); + base::WeakPtrFactory weak_factory_{this}; }; diff --git a/third_party/blink/public/web/web_frame_widget.h b/third_party/blink/public/web/web_frame_widget.h index 6264d513b398c..3988df585a159 100644 --- a/third_party/blink/public/web/web_frame_widget.h +++ b/third_party/blink/public/web/web_frame_widget.h @@ -53,6 +53,10 @@ struct ApplyViewportChangesArgs; class LayerTreeHost; } // namespace cc +namespace content { +class RenderFrameImpl; +} // namespace content + namespace gfx { class PointF; class RectF; @@ -227,6 +231,9 @@ class WebFrameWidget : public WebWidget { // GPU benchmarking extension needs access to the LayerTreeHost friend class GpuBenchmarkingContext; + // Allow RenderFrameImpl to access the LayerTreeHost for html2svg + friend class content::RenderFrameImpl; + // This private constructor and the class/friend declaration ensures that // WebFrameWidgetImpl is the only concrete subclass that implements // WebFrameWidget, so that it is safe to downcast to WebFrameWidgetImpl. diff --git a/third_party/blink/renderer/platform/fonts/font.cc b/third_party/blink/renderer/platform/fonts/font.cc index 089a11b156ade..dfdc79eacce3b 100644 --- a/third_party/blink/renderer/platform/fonts/font.cc +++ b/third_party/blink/renderer/platform/fonts/font.cc @@ -24,6 +24,8 @@ #include "third_party/blink/renderer/platform/fonts/font.h" +#include "base/base64.h" + #include "cc/paint/paint_canvas.h" #include "cc/paint/paint_flags.h" #include "third_party/blink/renderer/platform/fonts/character_range.h" @@ -149,11 +151,17 @@ 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) { + cc::NodeId node_id = cc::kInvalidNodeId) { + if (carbonyl_b64_text) { + return; + } + for (const auto& blob_info : blobs) { DCHECK(blob_info.blob); cc::PaintCanvasAutoRestore auto_restore(canvas, false); @@ -198,8 +206,7 @@ void DrawBlobs(cc::PaintCanvas* canvas, } } if (node_id != cc::kInvalidNodeId) { - canvas->drawTextBlob(blob_info.blob, point.x(), point.y(), node_id, - flags); + canvas->drawTextBlob(blob_info.blob, point.x(), point.y(), node_id, flags); } else { canvas->drawTextBlob(blob_info.blob, point.x(), point.y(), flags); } @@ -230,6 +237,31 @@ void Font::DrawText(cc::PaintCanvas* canvas, if (ShouldSkipDrawing()) return; + if (carbonyl_b64_text) { + auto string = StringView( + run_info.run.ToStringView(), + run_info.from, + run_info.to - run_info.from + ).ToString().Utf8(); + auto base64 = base::Base64Encode(base::as_bytes(base::make_span(string))); + + // Bypass HarfBuzz text shaping for the Carbonyl Skia back-end + auto blob = SkTextBlob::MakeFromString( + base64.c_str(), + PrimaryFont()-> + PlatformData(). + CreateSkFont(false, &font_description_) + ); + + if (node_id != cc::kInvalidNodeId) { + canvas->drawTextBlob(blob, point.x(), point.y(), node_id, flags); + } else { + canvas->drawTextBlob(blob, point.x(), point.y(), flags); + } + + return; + } + CachingWordShaper word_shaper(*this); ShapeResultBuffer buffer; word_shaper.FillResultBuffer(run_info, &buffer); @@ -253,6 +285,31 @@ void Font::DrawText(cc::PaintCanvas* canvas, if (ShouldSkipDrawing()) return; + if (carbonyl_b64_text) { + auto string = StringView( + text_info.text, + text_info.from, + text_info.Length() + ).ToString().Utf8(); + auto base64 = base::Base64Encode(base::as_bytes(base::make_span(string))); + + // Bypass HarfBuzz text shaping for the Carbonyl Skia back-end + auto blob = SkTextBlob::MakeFromString( + base64.c_str(), + PrimaryFont()-> + PlatformData(). + CreateSkFont(false, &font_description_) + ); + + if (node_id != cc::kInvalidNodeId) { + canvas->drawTextBlob(blob, point.x(), point.y(), node_id, flags); + } else { + canvas->drawTextBlob(blob, point.x(), point.y(), flags); + } + + return; + } + ShapeResultBloberizer::FillGlyphsNG bloberizer( GetFontDescription(), device_scale_factor > 1.0f, text_info.text, text_info.from, text_info.to, text_info.shape_result, ================================================ FILE: chromium/patches/chromium/0003-Setup-shared-software-rendering-surface.patch ================================================ From eea9662f4ba08a655057143d320e4e692fd92469 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Thu, 9 Feb 2023 03:24:29 +0100 Subject: [PATCH 03/14] Setup shared software rendering surface --- components/viz/host/host_display_client.cc | 4 +++- components/viz/host/host_display_client.h | 6 ++++-- .../viz/host/layered_window_updater_impl.cc | 2 +- .../viz/host/layered_window_updater_impl.h | 2 +- .../output_surface_provider_impl.cc | 16 ++++++++++++++++ .../compositor/viz_process_transport_factory.cc | 4 +++- .../mojom/compositing/display_private.mojom | 1 - .../compositing/layered_window_updater.mojom | 2 +- ui/compositor/compositor.h | 16 ++++++++++++++++ 9 files changed, 45 insertions(+), 8 deletions(-) diff --git a/components/viz/host/host_display_client.cc b/components/viz/host/host_display_client.cc index 6d905b62e6258..bfb803829c73b 100644 --- a/components/viz/host/host_display_client.cc +++ b/components/viz/host/host_display_client.cc @@ -47,9 +47,9 @@ void HostDisplayClient::OnDisplayReceivedCALayerParams( } #endif -#if BUILDFLAG(IS_WIN) void HostDisplayClient::CreateLayeredWindowUpdater( mojo::PendingReceiver receiver) { +#if BUILDFLAG(IS_WIN) if (!NeedsToUseLayerWindow(widget_)) { DLOG(ERROR) << "HWND shouldn't be using a layered window"; return; @@ -57,7 +57,9 @@ void HostDisplayClient::CreateLayeredWindowUpdater( layered_window_updater_ = std::make_unique(widget_, std::move(receiver)); +#endif } +#if BUILDFLAG(IS_WIN) void HostDisplayClient::AddChildWindowToBrowser( gpu::SurfaceHandle child_window) { NOTREACHED(); diff --git a/components/viz/host/host_display_client.h b/components/viz/host/host_display_client.h index 5eeaadec9773f..f3409f0eeda03 100644 --- a/components/viz/host/host_display_client.h +++ b/components/viz/host/host_display_client.h @@ -47,11 +47,13 @@ class VIZ_HOST_EXPORT HostDisplayClient : public mojom::DisplayClient { #endif #if BUILDFLAG(IS_WIN) - void CreateLayeredWindowUpdater( - mojo::PendingReceiver receiver) override; void AddChildWindowToBrowser(gpu::SurfaceHandle child_window) override; #endif + protected: + void CreateLayeredWindowUpdater( + mojo::PendingReceiver receiver) override; + // TODO(crbug.com/1052397): Revisit the macro expression once build flag switch // of lacros-chrome is complete. #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS) diff --git a/components/viz/host/layered_window_updater_impl.cc b/components/viz/host/layered_window_updater_impl.cc index 271486b45dcc8..a62210d8ca3c8 100644 --- a/components/viz/host/layered_window_updater_impl.cc +++ b/components/viz/host/layered_window_updater_impl.cc @@ -44,7 +44,7 @@ void LayeredWindowUpdaterImpl::OnAllocatedSharedMemory( // |region|'s handle will close when it goes out of scope. } -void LayeredWindowUpdaterImpl::Draw(DrawCallback draw_callback) { +void LayeredWindowUpdaterImpl::Draw(const gfx::Rect& damage_rect, DrawCallback draw_callback) { TRACE_EVENT0("viz", "LayeredWindowUpdaterImpl::Draw"); if (!canvas_) { diff --git a/components/viz/host/layered_window_updater_impl.h b/components/viz/host/layered_window_updater_impl.h index 8af69cac78b74..9f74e511c263d 100644 --- a/components/viz/host/layered_window_updater_impl.h +++ b/components/viz/host/layered_window_updater_impl.h @@ -38,7 +38,7 @@ class VIZ_HOST_EXPORT LayeredWindowUpdaterImpl // mojom::LayeredWindowUpdater implementation. void OnAllocatedSharedMemory(const gfx::Size& pixel_size, base::UnsafeSharedMemoryRegion region) override; - void Draw(DrawCallback draw_callback) override; + void Draw(const gfx::Rect& damage_rect, DrawCallback draw_callback) override; private: const HWND hwnd_; diff --git a/components/viz/service/display_embedder/output_surface_provider_impl.cc b/components/viz/service/display_embedder/output_surface_provider_impl.cc index d8f25c1435d4b..2929ebd3887c2 100644 --- a/components/viz/service/display_embedder/output_surface_provider_impl.cc +++ b/components/viz/service/display_embedder/output_surface_provider_impl.cc @@ -16,6 +16,7 @@ #include "build/build_config.h" #include "build/chromecast_buildflags.h" #include "build/chromeos_buildflags.h" +#include "carbonyl/src/browser/software_output_device_proxy.h" #include "cc/base/switches.h" #include "components/viz/common/display/renderer_settings.h" #include "components/viz/common/frame_sinks/begin_frame_source.h" @@ -29,6 +30,7 @@ #include "gpu/command_buffer/service/scheduler_sequence.h" #include "gpu/config/gpu_finch_features.h" #include "gpu/ipc/common/surface_handle.h" +#include "services/viz/privileged/mojom/compositing/layered_window_updater.mojom.h" #include "ui/base/ui_base_switches.h" #if BUILDFLAG(IS_WIN) @@ -134,10 +136,24 @@ std::unique_ptr OutputSurfaceProviderImpl::CreateOutputSurface( } } +namespace { + static const bool use_layered_window = true; +} + std::unique_ptr OutputSurfaceProviderImpl::CreateSoftwareOutputDeviceForPlatform( gpu::SurfaceHandle surface_handle, mojom::DisplayClient* display_client) { +// #if !BUILDFLAG(IS_APPLE) + if (use_layered_window) { + DCHECK(display_client); + mojo::PendingRemote layered_window_updater; + display_client->CreateLayeredWindowUpdater( + layered_window_updater.InitWithNewPipeAndPassReceiver()); + return std::make_unique( + std::move(layered_window_updater)); + } +// #endif if (headless_) return std::make_unique(); diff --git a/content/browser/compositor/viz_process_transport_factory.cc b/content/browser/compositor/viz_process_transport_factory.cc index 3b44531f2618f..fae71c1c19a4f 100644 --- a/content/browser/compositor/viz_process_transport_factory.cc +++ b/content/browser/compositor/viz_process_transport_factory.cc @@ -53,6 +53,8 @@ #include "ui/gfx/win/rendering_window_manager.h" #endif +#include "carbonyl/src/browser/host_display_client.h" + namespace content { namespace { @@ -400,7 +402,7 @@ void VizProcessTransportFactory::OnEstablishedGpuChannel( root_params->display_private = display_private.BindNewEndpointAndPassReceiver(); compositor_data.display_client = - std::make_unique(compositor); + std::make_unique(); root_params->display_client = compositor_data.display_client->GetBoundRemote(resize_task_runner_); mojo::AssociatedRemote diff --git a/services/viz/privileged/mojom/compositing/display_private.mojom b/services/viz/privileged/mojom/compositing/display_private.mojom index 52f44a31de4a8..65b938d76e430 100644 --- a/services/viz/privileged/mojom/compositing/display_private.mojom +++ b/services/viz/privileged/mojom/compositing/display_private.mojom @@ -103,7 +103,6 @@ interface DisplayClient { // Creates a LayeredWindowUpdater implementation to draw into a layered // window. - [EnableIf=is_win] CreateLayeredWindowUpdater(pending_receiver receiver); // Sends the created child window to the browser process so that it can be diff --git a/services/viz/privileged/mojom/compositing/layered_window_updater.mojom b/services/viz/privileged/mojom/compositing/layered_window_updater.mojom index 2f462f0deb5fc..695869b83cefa 100644 --- a/services/viz/privileged/mojom/compositing/layered_window_updater.mojom +++ b/services/viz/privileged/mojom/compositing/layered_window_updater.mojom @@ -26,5 +26,5 @@ interface LayeredWindowUpdater { // Draws to the HWND by copying pixels from shared memory. Callback must be // called after draw operation is complete to signal shared memory can be // modified. - Draw() => (); + Draw(gfx.mojom.Rect damage_rect) => (); }; diff --git a/ui/compositor/compositor.h b/ui/compositor/compositor.h index 50cea82c6b477..f024e6013bfb9 100644 --- a/ui/compositor/compositor.h +++ b/ui/compositor/compositor.h @@ -87,6 +87,7 @@ class DisplayPrivate; class ExternalBeginFrameController; } // namespace mojom class ContextProvider; +class HostDisplayClient; class HostFrameSinkManager; class LocalSurfaceId; class RasterContextProvider; @@ -143,6 +144,16 @@ class COMPOSITOR_EXPORT ContextFactory { virtual viz::HostFrameSinkManager* GetHostFrameSinkManager() = 0; }; +class COMPOSITOR_EXPORT CompositorDelegate { + public: + virtual bool IsOffscreen() const = 0; + virtual std::unique_ptr CreateHostDisplayClient( + ui::Compositor* compositor) = 0; + + protected: + virtual ~CompositorDelegate() {} +}; + // Compositor object to take care of GPU painting. // A Browser compositor object is responsible for generating the final // displayable form of pixels comprising a single widget's contents. It draws an @@ -186,6 +197,9 @@ class COMPOSITOR_EXPORT Compositor : public base::PowerSuspendObserver, // Schedules a redraw of the layer tree associated with this compositor. void ScheduleDraw(); + CompositorDelegate* delegate() const { return delegate_; } + void SetDelegate(CompositorDelegate* delegate) { delegate_ = delegate; } + // Sets the root of the layer tree drawn by this Compositor. The root layer // must have no parent. The compositor's root layer is reset if the root layer // is destroyed. NULL can be passed to reset the root layer, in which case the @@ -503,6 +517,8 @@ class COMPOSITOR_EXPORT Compositor : public base::PowerSuspendObserver, std::unique_ptr pending_begin_frame_args_; + CompositorDelegate* delegate_ = nullptr; + // The root of the Layer tree drawn by this compositor. raw_ptr root_layer_ = nullptr; ================================================ FILE: chromium/patches/chromium/0004-Setup-browser-default-settings.patch ================================================ From c960c9b1f7ef3f16b27e4eaa4896e3563c88ea91 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Thu, 9 Feb 2023 03:27:27 +0100 Subject: [PATCH 04/14] Setup browser default settings --- headless/public/headless_browser.cc | 4 ++-- headless/public/headless_browser.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/headless/public/headless_browser.cc b/headless/public/headless_browser.cc index b6c70ecb0fc23..c836a082d2e68 100644 --- a/headless/public/headless_browser.cc +++ b/headless/public/headless_browser.cc @@ -22,14 +22,14 @@ namespace headless { namespace { // Product name for building the default user agent string. -const char kHeadlessProductName[] = "HeadlessChrome"; +const char kHeadlessProductName[] = "Google Chrome"; constexpr gfx::Size kDefaultWindowSize(800, 600); constexpr gfx::FontRenderParams::Hinting kDefaultFontRenderHinting = gfx::FontRenderParams::Hinting::HINTING_FULL; std::string GetProductNameAndVersion() { - return std::string(kHeadlessProductName) + "/" + PRODUCT_VERSION; + return std::string(kHeadlessProductName) + "/" + PRODUCT_VERSION + " (Carbonyl)"; } } // namespace diff --git a/headless/public/headless_browser.h b/headless/public/headless_browser.h index 48efaa7d57ca2..afc0236147519 100644 --- a/headless/public/headless_browser.h +++ b/headless/public/headless_browser.h @@ -176,10 +176,10 @@ struct HEADLESS_EXPORT HeadlessBrowser::Options { base::FilePath user_data_dir; // Run a browser context in an incognito mode. Enabled by default. - bool incognito_mode = true; + bool incognito_mode = false; // If true, then all pop-ups and calls to window.open will fail. - bool block_new_web_contents = false; + bool block_new_web_contents = true; // Whether or not BeginFrames will be issued over DevTools protocol // (experimental). ================================================ FILE: chromium/patches/chromium/0005-Remove-some-debug-assertions.patch ================================================ From 481ff19118891fe65e80b8be0e1f4498874d3b56 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Thu, 9 Feb 2023 03:28:35 +0100 Subject: [PATCH 05/14] Remove some debug assertions --- .../browser/web_contents/web_contents_impl.cc | 1 - .../core/v8/script_promise_resolver.cc | 44 +++++++++---------- .../compositing/paint_artifact_compositor.cc | 2 - .../platform/graphics/graphics_context.cc | 16 +++---- 4 files changed, 30 insertions(+), 33 deletions(-) diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc index 74749758894a2..4eb891c32b474 100644 --- a/content/browser/web_contents/web_contents_impl.cc +++ b/content/browser/web_contents/web_contents_impl.cc @@ -5988,7 +5988,6 @@ void WebContentsImpl::DidNavigateMainFramePreCommit( if (IsFullscreen()) ExitFullscreen(false); - DCHECK(!IsFullscreen()); // Clean up keyboard lock state when navigating. CancelKeyboardLock(keyboard_lock_widget_); diff --git a/third_party/blink/renderer/bindings/core/v8/script_promise_resolver.cc b/third_party/blink/renderer/bindings/core/v8/script_promise_resolver.cc index c3176f4937c21..56d34529dedfa 100644 --- a/third_party/blink/renderer/bindings/core/v8/script_promise_resolver.cc +++ b/third_party/blink/renderer/bindings/core/v8/script_promise_resolver.cc @@ -58,28 +58,28 @@ ScriptPromiseResolver::ScriptPromiseResolver( ScriptPromiseResolver::~ScriptPromiseResolver() = default; void ScriptPromiseResolver::Dispose() { -#if DCHECK_IS_ON() - // This assertion fails if: - // - promise() is called at least once and - // - this resolver is destructed before it is resolved, rejected, - // detached, the V8 isolate is terminated or the associated - // ExecutionContext is stopped. - const bool is_properly_detached = - state_ == kDetached || !is_promise_called_ || - !GetScriptState()->ContextIsValid() || !GetExecutionContext() || - GetExecutionContext()->IsContextDestroyed(); - if (!is_properly_detached && !suppress_detach_check_) { - // This is here to make it easier to track down which promise resolvers are - // being abandoned. See https://crbug.com/873980. - static crash_reporter::CrashKeyString<1024> trace_key( - "scriptpromiseresolver-trace"); - crash_reporter::SetCrashKeyStringToStackTrace(&trace_key, - create_stack_trace_); - DCHECK(false) - << "ScriptPromiseResolver was not properly detached; created at\n" - << create_stack_trace_.ToString(); - } -#endif +// #if DCHECK_IS_ON() +// // This assertion fails if: +// // - promise() is called at least once and +// // - this resolver is destructed before it is resolved, rejected, +// // detached, the V8 isolate is terminated or the associated +// // ExecutionContext is stopped. +// const bool is_properly_detached = +// state_ == kDetached || !is_promise_called_ || +// !GetScriptState()->ContextIsValid() || !GetExecutionContext() || +// GetExecutionContext()->IsContextDestroyed(); +// if (!is_properly_detached && !suppress_detach_check_) { +// // This is here to make it easier to track down which promise resolvers are +// // being abandoned. See https://crbug.com/873980. +// static crash_reporter::CrashKeyString<1024> trace_key( +// "scriptpromiseresolver-trace"); +// crash_reporter::SetCrashKeyStringToStackTrace(&trace_key, +// create_stack_trace_); +// DCHECK(false) +// << "ScriptPromiseResolver was not properly detached; created at\n" +// << create_stack_trace_.ToString(); +// } +// #endif deferred_resolve_task_.Cancel(); } diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc index d3131a4e07ece..a9464abd86a69 100644 --- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc +++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc @@ -196,7 +196,6 @@ bool NeedsFullUpdateAfterPaintingChunk( // properties are changed, which would indicate a missing call to // SetNeedsUpdate. if (previous.properties != repainted.properties) { - NOTREACHED(); return true; } @@ -253,7 +252,6 @@ bool NeedsFullUpdateAfterPaintingChunk( // properties are changed, which would indicate a missing call to // SetNeedsUpdate. if (previous.properties != repainted.properties) { - NOTREACHED(); return true; } diff --git a/third_party/blink/renderer/platform/graphics/graphics_context.cc b/third_party/blink/renderer/platform/graphics/graphics_context.cc index 2518b71275670..3a1b8e6646c43 100644 --- a/third_party/blink/renderer/platform/graphics/graphics_context.cc +++ b/third_party/blink/renderer/platform/graphics/graphics_context.cc @@ -146,14 +146,14 @@ GraphicsContext::GraphicsContext(PaintController& paint_controller) } GraphicsContext::~GraphicsContext() { -#if DCHECK_IS_ON() - if (!disable_destruction_checks_) { - DCHECK(!paint_state_index_); - DCHECK(!paint_state_->SaveCount()); - DCHECK(!layer_count_); - DCHECK(!SaveCount()); - } -#endif +// #if DCHECK_IS_ON() +// if (!disable_destruction_checks_) { +// DCHECK(!paint_state_index_); +// DCHECK(!paint_state_->SaveCount()); +// DCHECK(!layer_count_); +// DCHECK(!SaveCount()); +// } +// #endif } void GraphicsContext::CopyConfigFrom(GraphicsContext& other) { ================================================ FILE: chromium/patches/chromium/0006-Setup-display-DPI.patch ================================================ From cc9c37adb3ad2613a114bd37e1fde43f83951d88 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Sun, 12 Feb 2023 01:00:43 +0100 Subject: [PATCH 06/14] Setup display DPI --- .../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..508660db32151 100644 --- a/headless/lib/browser/headless_browser_impl_aura.cc +++ b/headless/lib/browser/headless_browser_impl_aura.cc @@ -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. - gfx::Rect old_host_bounds = - web_contents->window_tree_host()->GetBoundsInPixels(); - gfx::Rect new_host_bounds( - 0, 0, std::max(old_host_bounds.width(), bounds.x() + bounds.width()), - 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, 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..8bf00ef5e036a 100644 --- a/headless/lib/browser/headless_screen.cc +++ b/headless/lib/browser/headless_screen.cc @@ -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); + 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..1d71f3b4c9857 100644 --- a/ui/display/display.cc +++ b/ui/display/display.cc @@ -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() { - return base::CommandLine::ForCurrentProcess()->HasSwitch( - switches::kForceDeviceScaleFactor); + // return base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kForceDeviceScaleFactor); + return true; } float GetForcedDeviceScaleFactorImpl() { - double scale_in_double = 1.0; - if (HasForceDeviceScaleFactorImpl()) { - std::string value = - base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( - switches::kForceDeviceScaleFactor); - if (!base::StringToDouble(value, &scale_in_double)) { - LOG(ERROR) << "Failed to parse the default device scale factor:" << value; - scale_in_double = 1.0; - } - } - return static_cast(scale_in_double); + // double scale_in_double = 1.0; + // if (HasForceDeviceScaleFactorImpl()) { + // std::string value = + // base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + // switches::kForceDeviceScaleFactor); + // if (!base::StringToDouble(value, &scale_in_double)) { + // LOG(ERROR) << "Failed to parse the default device scale factor:" << value; + // scale_in_double = 1.0; + // } + // } + return carbonyl::Bridge::GetCurrent()->GetDPI(); } const char* ToRotationString(display::Display::Rotation rotation) { @@ -97,11 +99,11 @@ void Display::ResetForceDeviceScaleFactorForTesting() { // static void Display::SetForceDeviceScaleFactor(double dsf) { // Reset any previously set values and unset the flag. - g_has_forced_device_scale_factor = -1; - g_forced_device_scale_factor = -1.0; + // g_has_forced_device_scale_factor = -1; + // g_forced_device_scale_factor = -1.0; - base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( - switches::kForceDeviceScaleFactor, base::StringPrintf("%.2f", dsf)); + // base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( + // switches::kForceDeviceScaleFactor, base::StringPrintf("%.2f", dsf)); } // static @@ -273,15 +275,15 @@ void Display::SetScaleAndBounds(float device_scale_factor, } void Display::SetScale(float device_scale_factor) { - if (!HasForceDeviceScaleFactor()) { -#if BUILDFLAG(IS_APPLE) - // Unless an explicit scale factor was provided for testing, ensure the - // scale is integral. - device_scale_factor = static_cast(device_scale_factor); -#endif +// if (!HasForceDeviceScaleFactor()) { +// #if BUILDFLAG(IS_APPLE) +// // Unless an explicit scale factor was provided for testing, ensure the +// // scale is integral. +// device_scale_factor = static_cast(device_scale_factor); +// #endif device_scale_factor_ = device_scale_factor; - } - device_scale_factor_ = std::max(0.5f, device_scale_factor_); + // } + // device_scale_factor_ = std::max(0.5f, device_scale_factor_); } void Display::SetSize(const gfx::Size& size_in_pixel) { ================================================ FILE: chromium/patches/chromium/0007-Disable-text-effects.patch ================================================ From 022ed4d808369659eab4e83cd677eb974215c58c Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Thu, 9 Feb 2023 03:31:17 +0100 Subject: [PATCH 07/14] Disable text effects --- .../core/paint/ng/ng_text_painter_base.cc | 30 ++++++++-------- ui/gfx/render_text.cc | 34 +++++++++---------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/third_party/blink/renderer/core/paint/ng/ng_text_painter_base.cc b/third_party/blink/renderer/core/paint/ng/ng_text_painter_base.cc index 008d80040e719..c11da51d0e906 100644 --- a/third_party/blink/renderer/core/paint/ng/ng_text_painter_base.cc +++ b/third_party/blink/renderer/core/paint/ng/ng_text_painter_base.cc @@ -123,21 +123,21 @@ void NGTextPainterBase::PaintUnderOrOverLineDecorations( continue; } - if (decoration_info.HasUnderline() && decoration_info.FontData() && - EnumHasFlags(lines_to_paint, TextDecorationLine::kUnderline)) { - decoration_info.SetUnderlineLineData(decoration_offset); - PaintDecorationUnderOrOverLine(fragment_paint_info, context, - decoration_info, - TextDecorationLine::kUnderline, flags); - } - - if (decoration_info.HasOverline() && decoration_info.FontData() && - EnumHasFlags(lines_to_paint, TextDecorationLine::kOverline)) { - decoration_info.SetOverlineLineData(decoration_offset); - PaintDecorationUnderOrOverLine(fragment_paint_info, context, - decoration_info, - TextDecorationLine::kOverline, flags); - } + // if (decoration_info.HasUnderline() && decoration_info.FontData() && + // EnumHasFlags(lines_to_paint, TextDecorationLine::kUnderline)) { + // decoration_info.SetUnderlineLineData(decoration_offset); + // PaintDecorationUnderOrOverLine(fragment_paint_info, context, + // decoration_info, + // TextDecorationLine::kUnderline, flags); + // } + + // if (decoration_info.HasOverline() && decoration_info.FontData() && + // EnumHasFlags(lines_to_paint, TextDecorationLine::kOverline)) { + // decoration_info.SetOverlineLineData(decoration_offset); + // PaintDecorationUnderOrOverLine(fragment_paint_info, context, + // decoration_info, + // TextDecorationLine::kOverline, flags); + // } } } diff --git a/ui/gfx/render_text.cc b/ui/gfx/render_text.cc index 67fbf128ea158..a645ba61c8597 100644 --- a/ui/gfx/render_text.cc +++ b/ui/gfx/render_text.cc @@ -55,9 +55,9 @@ constexpr char16_t kEllipsisCodepoint = 0x2026; // Fraction of the text size to raise the center of a strike-through line above // the baseline. -const SkScalar kStrikeThroughOffset = (SK_Scalar1 * 65 / 252); +// const SkScalar kStrikeThroughOffset = (SK_Scalar1 * 65 / 252); // Fraction of the text size to lower an underline below the baseline. -const SkScalar kUnderlineOffset = (SK_Scalar1 / 9); +// const SkScalar kUnderlineOffset = (SK_Scalar1 / 9); // Float comparison needs epsilon to consider rounding errors in float // arithmetic. Epsilon should be dependent on the context and here, we are @@ -374,27 +374,27 @@ void SkiaTextRenderer::DrawUnderline(int x, int y, int width, SkScalar thickness_factor) { - SkScalar x_scalar = SkIntToScalar(x); - const SkScalar text_size = font_.getSize(); - SkRect r = SkRect::MakeLTRB( - x_scalar, y + text_size * kUnderlineOffset, x_scalar + width, - y + (text_size * - (kUnderlineOffset + - (thickness_factor * RenderText::kLineThicknessFactor)))); - canvas_skia_->drawRect(r, flags_); + // SkScalar x_scalar = SkIntToScalar(x); + // const SkScalar text_size = font_.getSize(); + // SkRect r = SkRect::MakeLTRB( + // x_scalar, y + text_size * kUnderlineOffset, x_scalar + width, + // y + (text_size * + // (kUnderlineOffset + + // (thickness_factor * RenderText::kLineThicknessFactor)))); + // canvas_skia_->drawRect(r, flags_); } void SkiaTextRenderer::DrawStrike(int x, int y, int width, SkScalar thickness_factor) { - const SkScalar text_size = font_.getSize(); - const SkScalar height = text_size * thickness_factor; - const SkScalar top = y - text_size * kStrikeThroughOffset - height / 2; - SkScalar x_scalar = SkIntToScalar(x); - const SkRect r = - SkRect::MakeLTRB(x_scalar, top, x_scalar + width, top + height); - canvas_skia_->drawRect(r, flags_); + // const SkScalar text_size = font_.getSize(); + // const SkScalar height = text_size * thickness_factor; + // const SkScalar top = y - text_size * kStrikeThroughOffset - height / 2; + // SkScalar x_scalar = SkIntToScalar(x); + // const SkRect r = + // SkRect::MakeLTRB(x_scalar, top, x_scalar + width, top + height); + // canvas_skia_->drawRect(r, flags_); } StyleIterator::StyleIterator(const BreakList* colors, ================================================ FILE: chromium/patches/chromium/0008-Fix-text-layout.patch ================================================ From 7b1f72900f704ffecc48c66da7ccd6de205b88f7 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Thu, 9 Feb 2023 03:32:14 +0100 Subject: [PATCH 08/14] Fix text layout --- .../core/css/resolver/style_resolver.cc | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) 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 6207b72d17cb9..79cb8c85b697f 100644 --- a/third_party/blink/renderer/core/css/resolver/style_resolver.cc +++ b/third_party/blink/renderer/core/css/resolver/style_resolver.cc @@ -281,7 +281,9 @@ String ComputeBaseComputedStyleDiff(const ComputedStyle* base_computed_style, return g_null_atom; } - return String("Field diff: ") + builder.ReleaseString(); + // TODO(fathy): Carbonyl should properly set the computed style + // return String("Field diff: ") + builder.ReleaseString(); + return g_null_atom; } #endif // DCHECK_IS_ON() @@ -1039,6 +1041,19 @@ scoped_refptr StyleResolver::ResolveStyle( UseCounter::Count(GetDocument(), WebFeature::kHasGlyphRelativeUnits); } + 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)); + state.LoadPendingResources(); // Now return the style. ================================================ FILE: chromium/patches/chromium/0009-Bridge-browser-into-Carbonyl-library.patch ================================================ From 792e123bb57b1b379b0367b2568302e2cb0dc3c9 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Thu, 9 Feb 2023 03:32:30 +0100 Subject: [PATCH 09/14] Bridge browser into Carbonyl library --- headless/app/headless_shell.cc | 33 +- headless/app/headless_shell_main.cc | 5 + headless/lib/browser/headless_browser_impl.cc | 406 +++++++++++++++++- headless/lib/browser/headless_browser_impl.h | 16 + .../lib/browser/headless_web_contents_impl.cc | 28 ++ .../lib/browser/headless_web_contents_impl.h | 2 + 6 files changed, 462 insertions(+), 28 deletions(-) diff --git a/headless/app/headless_shell.cc b/headless/app/headless_shell.cc index e08385b9cf740..5b51c22ae1da3 100644 --- a/headless/app/headless_shell.cc +++ b/headless/app/headless_shell.cc @@ -4,6 +4,8 @@ #include "headless/app/headless_shell.h" +#include "carbonyl/src/browser/bridge.h" + #include #include "base/base_switches.h" @@ -90,6 +92,8 @@ void HeadlessShell::OnBrowserStart(HeadlessBrowser* browser) { HeadlessBrowserContext::Builder context_builder = browser_->CreateBrowserContextBuilder(); + context_builder.SetWindowSize(carbonyl::Renderer::Main()->GetSize()); + // Retrieve the locale set by InitApplicationLocale() in // headless_content_main_delegate.cc in a way that is free of side-effects. context_builder.SetAcceptLanguage(base::i18n::GetConfiguredLocale()); @@ -113,39 +117,14 @@ void HeadlessShell::OnBrowserStart(HeadlessBrowser* browser) { GURL target_url = ConvertArgumentToURL(args.front()); - // If driven by a debugger just open the target page and - // leave expecting the debugger will do what they need. - if (IsRemoteDebuggingEnabled()) { - HeadlessWebContents::Builder builder( - browser_context_->CreateWebContentsBuilder()); - HeadlessWebContents* web_contents = - builder.SetInitialURL(target_url).Build(); - if (!web_contents) { - LOG(ERROR) << "Navigation to " << target_url << " failed."; - ShutdownSoon(); - } - return; - } - - // Otherwise instantiate headless shell command handler that will - // execute the commands against the target page. -#if defined(HEADLESS_ENABLE_COMMANDS) - GURL handler_url = HeadlessCommandHandler::GetHandlerUrl(); HeadlessWebContents::Builder builder( browser_context_->CreateWebContentsBuilder()); HeadlessWebContents* web_contents = - builder.SetInitialURL(handler_url).Build(); + builder.SetInitialURL(target_url).Build(); if (!web_contents) { - LOG(ERROR) << "Navigation to " << handler_url << " failed."; + LOG(ERROR) << "Navigation to " << target_url << " failed."; ShutdownSoon(); - return; } - - HeadlessCommandHandler::ProcessCommands( - HeadlessWebContentsImpl::From(web_contents)->web_contents(), - std::move(target_url), - base::BindOnce(&HeadlessShell::ShutdownSoon, weak_factory_.GetWeakPtr())); -#endif } void HeadlessShell::ShutdownSoon() { diff --git a/headless/app/headless_shell_main.cc b/headless/app/headless_shell_main.cc index 35736145f5caf..f9b8bac5c18a5 100644 --- a/headless/app/headless_shell_main.cc +++ b/headless/app/headless_shell_main.cc @@ -13,7 +13,12 @@ #include "sandbox/mac/seatbelt_exec.h" #endif +#include "base/at_exit.h" +#include "carbonyl/src/browser/bridge.h" + int main(int argc, const char** argv) { + carbonyl_shell_main(); + #if BUILDFLAG(IS_WIN) sandbox::SandboxInterfaceInfo sandbox_info = {nullptr}; content::InitializeSandboxInfo(&sandbox_info); diff --git a/headless/lib/browser/headless_browser_impl.cc b/headless/lib/browser/headless_browser_impl.cc index 1a1223108be6d..fd45d215479ab 100644 --- a/headless/lib/browser/headless_browser_impl.cc +++ b/headless/lib/browser/headless_browser_impl.cc @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include "base/callback_helpers.h" #include "base/command_line.h" @@ -27,6 +29,23 @@ #include "services/network/public/cpp/network_switches.h" #include "ui/events/devices/device_data_manager.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/render_widget_host.h" +#include "content/public/browser/web_contents.h" +#include "carbonyl/src/browser/bridge.h" +#include "third_party/blink/public/common/input/web_mouse_event.h" +#include "third_party/blink/public/common/input/web_mouse_wheel_event.h" +#include "ui/events/keycodes/keyboard_codes.h" + +namespace carbonyl { + +static unsigned int current_mouse_x = 0; +static unsigned int current_mouse_y = 0; +static headless::HeadlessBrowserImpl* browser = nullptr; + +} + namespace headless { HeadlessBrowserImpl::HeadlessBrowserImpl( @@ -38,7 +57,15 @@ HeadlessBrowserImpl::HeadlessBrowserImpl( default_browser_context_(nullptr), agent_host_(nullptr) {} -HeadlessBrowserImpl::~HeadlessBrowserImpl() = default; +HeadlessBrowserImpl::~HeadlessBrowserImpl() { + if (carbonyl::browser == this) { + carbonyl::browser = nullptr; + } + + if (input_thread_.joinable()) { + input_thread_.join(); + } +} HeadlessBrowserContext::Builder HeadlessBrowserImpl::CreateBrowserContextBuilder() { @@ -91,6 +118,307 @@ void HeadlessBrowserImpl::set_browser_main_parts( browser_main_parts_ = browser_main_parts; } +void HeadlessBrowserImpl::Resize() { + auto size = carbonyl::Renderer::GetSize(); + auto rect = gfx::Rect(0, 0, size.width(), size.height()); + + for (auto* ctx: GetAllBrowserContexts()) { + for (auto* contents: ctx->GetAllWebContents()) { + auto* impl = HeadlessWebContentsImpl::From(contents); + + if (!impl) { + continue; + } + + PlatformSetWebContentsBounds(impl, rect); + } + } + + carbonyl::Renderer::Main()->Resize(); +} + +void HeadlessBrowserImpl::OnShutdownInput() { + Shutdown(); +} +void HeadlessBrowserImpl::OnRefreshInput() { + for (auto* ctx: GetAllBrowserContexts()) { + for (auto* contents: ctx->GetAllWebContents()) { + auto* impl = HeadlessWebContentsImpl::From(contents); + + if (!impl) { + continue; + } + + auto& nav = impl->web_contents()->GetController(); + + nav.Reload(content::ReloadType::NORMAL, true); + } + } +} + +void HeadlessBrowserImpl::OnGoToInput(const char* url_str) { + if (!carbonyl::browser) { + return; + } + + auto ctxs = GetAllBrowserContexts(); + + if (ctxs.empty()) { + return; + } + + auto url = GURL(std::string(url_str)); + + if (!url.is_valid() || url.spec().size() > url::kMaxURLChars) { + return; + } + + auto* ctx = ctxs[0]; + auto contents = ctx->GetAllWebContents(); + + if (contents.empty()) { + ctx->CreateWebContentsBuilder().SetInitialURL(url).Build(); + } else { + HeadlessWebContentsImpl::From(contents[0])->OpenURL(url); + } +} + +void HeadlessBrowserImpl::OnGoBackInput() { + for (auto* ctx: GetAllBrowserContexts()) { + for (auto* contents: ctx->GetAllWebContents()) { + auto* impl = HeadlessWebContentsImpl::From(contents); + + if (!impl) { + continue; + } + + auto& nav = impl->web_contents()->GetController(); + + if (nav.CanGoBack()) { + nav.GoBack(); + } + } + } +} +void HeadlessBrowserImpl::OnGoForwardInput() { + for (auto* ctx: GetAllBrowserContexts()) { + for (auto* contents: ctx->GetAllWebContents()) { + auto* impl = HeadlessWebContentsImpl::From(contents); + + if (!impl) { + continue; + } + + auto& nav = impl->web_contents()->GetController(); + + if (nav.CanGoForward()) { + nav.GoForward(); + } + } + } +} + +void HeadlessBrowserImpl::OnScrollInput(int delta) { + blink::WebMouseWheelEvent event; + + event.SetType(blink::WebInputEvent::Type::kMouseWheel); + event.SetTimeStamp(base::TimeTicks::Now()); + event.SetPositionInWidget(carbonyl::current_mouse_x, carbonyl::current_mouse_y); + event.SetPositionInScreen(carbonyl::current_mouse_x, carbonyl::current_mouse_y); + + event.delta_y = delta; + event.phase = blink::WebMouseWheelEvent::kPhaseBegan; + event.dispatch_type = blink::WebInputEvent::DispatchType::kBlocking; + + for (auto* ctx: GetAllBrowserContexts()) { + for (auto* contents: ctx->GetAllWebContents()) { + auto* impl = HeadlessWebContentsImpl::From(contents); + + if (!impl) { + continue; + } + + auto *host = impl->web_contents()->GetRenderViewHost()->GetWidget(); + + if (!host) { + continue; + } + + host->ForwardWheelEvent(event); + } + } + + // Send a synthetic wheel event with phaseEnded to finish scrolling. + event.delta_y = 0; + event.phase = blink::WebMouseWheelEvent::kPhaseEnded; + event.dispatch_type = blink::WebInputEvent::DispatchType::kEventNonBlocking; + event.has_synthetic_phase = true; + + for (auto* ctx: GetAllBrowserContexts()) { + for (auto* contents: ctx->GetAllWebContents()) { + auto* impl = HeadlessWebContentsImpl::From(contents); + + if (!impl) { + continue; + } + + auto *host = impl->web_contents()->GetRenderViewHost()->GetWidget(); + + if (!host) { + continue; + } + + host->ForwardWheelEvent(event); + } + } +} + +void HeadlessBrowserImpl::OnKeyPressInput(char key) { + bool raw = true; + content::NativeWebKeyboardEvent event( + blink::WebKeyboardEvent::Type::kRawKeyDown, + blink::WebInputEvent::kNoModifiers, + base::TimeTicks::Now()); + + // TODO(fathy): support IME + switch (key) { + case 0x11: + event.windows_key_code = ui::KeyboardCode::VKEY_UP; + break; + case 0x12: + event.windows_key_code = ui::KeyboardCode::VKEY_DOWN; + break; + case 0x13: + event.windows_key_code = ui::KeyboardCode::VKEY_RIGHT; + break; + case 0x14: + event.windows_key_code = ui::KeyboardCode::VKEY_LEFT; + break; + case 0x7f: + event.windows_key_code = ui::KeyboardCode::VKEY_BACK; + break; + default: + raw = false; + + event.text[0] = key; + } + + for (auto* ctx: GetAllBrowserContexts()) { + for (auto* contents: ctx->GetAllWebContents()) { + auto* impl = HeadlessWebContentsImpl::From(contents); + + if (!impl) { + continue; + } + + auto *host = impl->web_contents()->GetRenderViewHost()->GetWidget(); + + if (!host) { + continue; + } + + event.SetType( + raw + ? blink::WebKeyboardEvent::Type::kRawKeyDown + : blink::WebKeyboardEvent::Type::kKeyDown + ); + host->ForwardKeyboardEvent(event); + + event.SetType(blink::WebKeyboardEvent::Type::kKeyUp); + host->ForwardKeyboardEvent(event); + } + } +} + +void HeadlessBrowserImpl::OnMouseUpInput(unsigned int x, unsigned int y) { + for (auto* ctx: GetAllBrowserContexts()) { + for (auto* contents: ctx->GetAllWebContents()) { + auto* impl = HeadlessWebContentsImpl::From(contents); + + if (!impl) { + continue; + } + + auto *host = impl->web_contents()->GetRenderViewHost()->GetWidget(); + + if (!host) { + continue; + } + + blink::WebMouseEvent event; + + event.button = blink::WebMouseEvent::Button::kLeft; + event.click_count = 1; + event.SetType(blink::WebInputEvent::Type::kMouseUp); + event.SetTimeStamp(base::TimeTicks::Now()); + event.SetPositionInWidget(x, y); + event.SetPositionInScreen(x, y); + + host->ForwardMouseEvent(event); + } + } +} + +void HeadlessBrowserImpl::OnMouseDownInput(unsigned int x, unsigned int y) { + for (auto* ctx: GetAllBrowserContexts()) { + for (auto* contents: ctx->GetAllWebContents()) { + auto* impl = HeadlessWebContentsImpl::From(contents); + + if (!impl) { + continue; + } + + auto *host = impl->web_contents()->GetRenderViewHost()->GetWidget(); + + if (!host) { + continue; + } + + blink::WebMouseEvent event; + + event.button = blink::WebMouseEvent::Button::kLeft; + event.click_count = 1; + event.SetType(blink::WebInputEvent::Type::kMouseDown); + event.SetTimeStamp(base::TimeTicks::Now()); + event.SetPositionInWidget(x, y); + event.SetPositionInScreen(x, y); + + host->ForwardMouseEvent(event); + } + } +} + +void HeadlessBrowserImpl::OnMouseMoveInput(unsigned int x, unsigned int y) { + for (auto* ctx: GetAllBrowserContexts()) { + for (auto* contents: ctx->GetAllWebContents()) { + auto* impl = HeadlessWebContentsImpl::From(contents); + + if (!impl) { + continue; + } + + auto *host = impl->web_contents()->GetRenderViewHost()->GetWidget(); + + if (!host) { + continue; + } + + blink::WebMouseEvent event; + + carbonyl::current_mouse_x = x; + carbonyl::current_mouse_y = y; + + event.click_count = 1; + event.SetType(blink::WebInputEvent::Type::kMouseMove); + event.SetTimeStamp(base::TimeTicks::Now()); + event.SetPositionInWidget(x, y); + event.SetPositionInScreen(x, y); + + host->ForwardMouseEvent(event); + } + } +} + void HeadlessBrowserImpl::RunOnStartCallback() { // We don't support the tethering domain on this agent host. agent_host_ = content::DevToolsAgentHost::CreateForBrowser( @@ -98,6 +426,82 @@ void HeadlessBrowserImpl::RunOnStartCallback() { PlatformStart(); std::move(on_start_callback_).Run(this); + + signal(SIGWINCH, [](int signal) { + if (carbonyl::browser) { + carbonyl::browser->Resize(); + } + }); + + input_thread_ = std::thread([=]() { + carbonyl::browser = this; + + carbonyl_bridge_browser_delegate delegate = { + .shutdown = []() { + if (carbonyl::browser) { + carbonyl::browser->OnShutdownInput(); + } + }, + .refresh = []() { + if (carbonyl::browser) { + carbonyl::browser->OnRefreshInput(); + } + }, + .go_to = [](const char* url) { + if (carbonyl::browser) { + carbonyl::browser->OnGoToInput(url); + } + }, + .go_back = []() { + if (carbonyl::browser) { + carbonyl::browser->OnGoBackInput(); + } + }, + .go_forward = []() { + if (carbonyl::browser) { + carbonyl::browser->OnGoForwardInput(); + } + }, + .scroll = [](int delta) { + if (carbonyl::browser) { + carbonyl::browser->OnScrollInput(delta); + } + }, + .key_press = [](char key) { + if (carbonyl::browser) { + carbonyl::browser->OnKeyPressInput(key); + } + }, + .mouse_up = [](unsigned int x, unsigned int y) { + if (carbonyl::browser) { + carbonyl::browser->OnMouseUpInput(x, y); + } + }, + .mouse_down = [](unsigned int x, unsigned int y) { + if (carbonyl::browser) { + carbonyl::browser->OnMouseDownInput(x, y); + } + }, + .mouse_move = [](unsigned int x, unsigned int y) { + if (carbonyl::browser) { + carbonyl::browser->OnMouseMoveInput(x, y); + } + }, + .post_task = [](void (*fn)(void*), void* data) { + if (carbonyl::browser) { + carbonyl::browser->BrowserMainThread()->PostTask( + FROM_HERE, + base::BindOnce( + fn, + data + ) + ); + } + } + }; + + carbonyl::Renderer::Main()->Listen(&delegate); + }); } HeadlessBrowserContext* HeadlessBrowserImpl::CreateBrowserContext( diff --git a/headless/lib/browser/headless_browser_impl.h b/headless/lib/browser/headless_browser_impl.h index a2d531ab32ff5..963808352c0c4 100644 --- a/headless/lib/browser/headless_browser_impl.h +++ b/headless/lib/browser/headless_browser_impl.h @@ -11,6 +11,7 @@ #include #include #include +#include #include "base/memory/weak_ptr.h" #include "base/task/single_thread_task_runner.h" @@ -121,9 +122,24 @@ class HEADLESS_EXPORT HeadlessBrowserImpl : public HeadlessBrowser, policy::PolicyService* GetPolicyService(); #endif + void Resize(); + void OnShutdownInput(); + void OnRefreshInput(); + void OnGoToInput(const char* url); + void OnGoBackInput(); + void OnGoForwardInput(); + void OnScrollInput(int delta); + void OnKeyPressInput(char key); + void OnMouseUpInput(unsigned int x, unsigned int y); + void OnMouseDownInput(unsigned int x, unsigned int y); + void OnMouseMoveInput(unsigned int x, unsigned int y); + bool did_shutdown() const { return did_shutdown_; } protected: + // TODO: use base::TaskRunner + std::thread input_thread_; + #if BUILDFLAG(IS_MAC) std::unique_ptr screen_; #endif diff --git a/headless/lib/browser/headless_web_contents_impl.cc b/headless/lib/browser/headless_web_contents_impl.cc index 010ff2c94287e..fad8c3fdd2bfe 100644 --- a/headless/lib/browser/headless_web_contents_impl.cc +++ b/headless/lib/browser/headless_web_contents_impl.cc @@ -8,6 +8,7 @@ #include #include #include +#include #include "base/bind.h" #include "base/command_line.h" @@ -21,10 +22,13 @@ #include "base/values.h" #include "build/build_config.h" #include "build/chromeos_buildflags.h" +#include "carbonyl/src/browser/bridge.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/child_process_termination_info.h" #include "content/public/browser/devtools_agent_host.h" #include "content/public/browser/navigation_controller.h" +#include "content/public/browser/navigation_entry.h" +#include "content/public/browser/navigation_details.h" #include "content/public/browser/navigation_handle.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" @@ -390,6 +394,30 @@ void HeadlessWebContentsImpl::RenderViewReady() { devtools_target_ready_notification_sent_ = true; } +void HeadlessWebContentsImpl::TitleWasSet(content::NavigationEntry* entry) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + if (!web_contents() || !web_contents()->GetPrimaryMainFrame()->IsActive()) + return; + + carbonyl::Renderer::Main()->SetTitle(base::UTF16ToUTF8(entry->GetTitleForDisplay())); +} + +void HeadlessWebContentsImpl::DidFinishNavigation(content::NavigationHandle* handle) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + if (!handle->IsInMainFrame() || !web_contents() || !web_contents()->GetPrimaryMainFrame()->IsActive()) + return; + + auto& nav = web_contents()->GetController(); + + carbonyl::Renderer::Main()->PushNav( + handle->GetURL().spec(), + nav.CanGoBack(), + nav.CanGoForward() + ); +} + int HeadlessWebContentsImpl::GetMainFrameRenderProcessId() const { if (!web_contents() || !web_contents()->GetPrimaryMainFrame()) return -1; diff --git a/headless/lib/browser/headless_web_contents_impl.h b/headless/lib/browser/headless_web_contents_impl.h index b80147fd06be8..09773596aa5ce 100644 --- a/headless/lib/browser/headless_web_contents_impl.h +++ b/headless/lib/browser/headless_web_contents_impl.h @@ -91,6 +91,8 @@ class HEADLESS_EXPORT HeadlessWebContentsImpl void RenderFrameCreated(content::RenderFrameHost* render_frame_host) override; void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override; void RenderViewReady() override; + void TitleWasSet(content::NavigationEntry* entry) override; + void DidFinishNavigation(content::NavigationHandle* navigation_handle) override; content::WebContents* web_contents() const; bool OpenURL(const GURL& url); ================================================ FILE: chromium/patches/chromium/0010-Conditionally-enable-text-rendering.patch ================================================ From bdc80f35a7113b7523c4d992edc9170db082deb0 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj 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 #include +#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>( [=]() -> 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 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, ================================================ FILE: chromium/patches/chromium/0011-Rename-carbonyl-Renderer-to-carbonyl-Bridge.patch ================================================ From fa52dbb68b7822ee4c01a697197e68ef1ab4a19c Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj 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() ================================================ FILE: chromium/patches/chromium/0012-Create-separate-bridge-for-Blink.patch ================================================ From 6862e372717eff278470453e800dc693f33b873c Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj 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 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: chromium/patches/chromium/0013-Refactor-rendering-bridge.patch ================================================ From 3b67b346ec26a47b50178124d715a8320f612d4d Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Mon, 13 Feb 2023 16:28:50 +0100 Subject: [PATCH 13/14] Refactor rendering bridge --- components/viz/service/BUILD.gn | 3 + .../output_surface_provider_impl.cc | 2 +- .../software_output_device_proxy.cc | 155 ++++++++++++++++++ .../software_output_device_proxy.h | 91 ++++++++++ content/browser/BUILD.gn | 2 + .../browser/web_contents/web_contents_impl.cc | 4 +- .../browser/web_contents/web_contents_impl.h | 2 +- content/public/browser/web_contents.h | 3 + content/renderer/BUILD.gn | 3 + content/renderer/render_frame_impl.cc | 5 + headless/BUILD.gn | 10 +- headless/app/headless_shell.cc | 6 +- headless/app/headless_shell_main.cc | 4 +- headless/lib/browser/headless_browser_impl.cc | 12 +- .../lib/browser/headless_browser_impl_aura.cc | 2 +- .../lib/browser/headless_browser_impl_mac.mm | 9 +- .../headless_browser_main_parts_mac.mm | 9 + headless/lib/browser/headless_screen.cc | 28 +++- headless/lib/browser/headless_screen.h | 8 +- .../lib/browser/headless_web_contents_impl.cc | 8 +- printing/printing_context_mac.mm | 2 + third_party/blink/renderer/core/BUILD.gn | 1 + .../core/css/resolver/style_resolver.cc | 4 +- third_party/blink/renderer/platform/BUILD.gn | 2 +- .../blink/renderer/platform/fonts/font.cc | 8 +- ui/display/BUILD.gn | 1 + ui/display/display.cc | 51 +----- 27 files changed, 351 insertions(+), 84 deletions(-) create mode 100644 components/viz/service/display_embedder/software_output_device_proxy.cc create mode 100644 components/viz/service/display_embedder/software_output_device_proxy.h diff --git a/components/viz/service/BUILD.gn b/components/viz/service/BUILD.gn index fb793e98d5939..e1b4dfa4cd49f 100644 --- a/components/viz/service/BUILD.gn +++ b/components/viz/service/BUILD.gn @@ -139,6 +139,8 @@ viz_component("service") { "display_embedder/skia_render_copy_results.h", "display_embedder/software_output_surface.cc", "display_embedder/software_output_surface.h", + "display_embedder/software_output_device_proxy.cc", + "display_embedder/software_output_device_proxy.h", "display_embedder/vsync_parameter_listener.cc", "display_embedder/vsync_parameter_listener.h", "frame_sinks/begin_frame_tracker.cc", @@ -229,6 +231,7 @@ viz_component("service") { "//build:chromeos_buildflags", "//cc/base", "//cc/paint", + "//carbonyl/src/browser:viz", "//components/crash/core/common:crash_key", "//components/power_scheduler", diff --git a/components/viz/service/display_embedder/output_surface_provider_impl.cc b/components/viz/service/display_embedder/output_surface_provider_impl.cc index 2929ebd3887c2..cd6f81dfa8b1d 100644 --- a/components/viz/service/display_embedder/output_surface_provider_impl.cc +++ b/components/viz/service/display_embedder/output_surface_provider_impl.cc @@ -16,7 +16,6 @@ #include "build/build_config.h" #include "build/chromecast_buildflags.h" #include "build/chromeos_buildflags.h" -#include "carbonyl/src/browser/software_output_device_proxy.h" #include "cc/base/switches.h" #include "components/viz/common/display/renderer_settings.h" #include "components/viz/common/frame_sinks/begin_frame_source.h" @@ -24,6 +23,7 @@ #include "components/viz/service/display_embedder/server_shared_bitmap_manager.h" #include "components/viz/service/display_embedder/skia_output_surface_dependency_impl.h" #include "components/viz/service/display_embedder/skia_output_surface_impl.h" +#include "components/viz/service/display_embedder/software_output_device_proxy.h" #include "components/viz/service/display_embedder/software_output_surface.h" #include "components/viz/service/gl/gpu_service_impl.h" #include "gpu/command_buffer/client/shared_memory_limits.h" diff --git a/components/viz/service/display_embedder/software_output_device_proxy.cc b/components/viz/service/display_embedder/software_output_device_proxy.cc new file mode 100644 index 0000000000000..a61668050cf0e --- /dev/null +++ b/components/viz/service/display_embedder/software_output_device_proxy.cc @@ -0,0 +1,155 @@ +#include "components/viz/service/display_embedder/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 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(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 diff --git a/components/viz/service/display_embedder/software_output_device_proxy.h b/components/viz/service/display_embedder/software_output_device_proxy.h new file mode 100644 index 0000000000000..4f0a64830b18b --- /dev/null +++ b/components/viz/service/display_embedder/software_output_device_proxy.h @@ -0,0 +1,91 @@ +#ifndef CARBONYL_SRC_BROWSER_SOFTWARE_OUTPUT_DEVICE_PROXY_H_ +#define CARBONYL_SRC_BROWSER_SOFTWARE_OUTPUT_DEVICE_PROXY_H_ + +#include + +#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 +#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 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 layered_window_updater_; + + std::unique_ptr 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_ diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn index aff547e8e5ed0..d2645a0be3008 100644 --- a/content/browser/BUILD.gn +++ b/content/browser/BUILD.gn @@ -69,6 +69,8 @@ source_set("browser") { "//build:chromecast_buildflags", "//build:chromeos_buildflags", "//build/config/compiler:compiler_buildflags", + "//carbonyl/src/browser:renderer", + "//carbonyl/src/browser:viz", "//cc", "//cc/animation", "//cc/mojo_embedder", diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc index 4eb891c32b474..6ae4005040371 100644 --- a/content/browser/web_contents/web_contents_impl.cc +++ b/content/browser/web_contents/web_contents_impl.cc @@ -1650,7 +1650,7 @@ void WebContentsImpl::OnScreensChange(bool is_multi_screen_changed) { // Mac display info may originate from a remote process hosting the NSWindow; // this local process display::Screen signal should not trigger updates. // TODO(crbug.com/1169291): Unify screen info plumbing, caching, etc. -#if !BUILDFLAG(IS_MAC) +// #if !BUILDFLAG(IS_MAC) // This updates Screen attributes and fires Screen.change events as needed, // propagating to all widgets through the VisualProperties update waterfall. // This is triggered by system changes, not renderer IPC, so explicitly check @@ -1662,7 +1662,7 @@ void WebContentsImpl::OnScreensChange(bool is_multi_screen_changed) { if (!view->IsRenderWidgetHostViewChildFrame()) view->UpdateScreenInfo(); } -#endif // !BUILDFLAG(IS_MAC) +// #endif // !BUILDFLAG(IS_MAC) } void WebContentsImpl::OnScreenOrientationChange() { diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h index 3f0ecf8749e44..196c3174e20f1 100644 --- a/content/browser/web_contents/web_contents_impl.h +++ b/content/browser/web_contents/web_contents_impl.h @@ -265,7 +265,7 @@ class CONTENT_EXPORT WebContentsImpl : public WebContents, // Called on screen information changes; |is_multi_screen_changed| is true iff // the plurality of connected screens changed (e.g. 1 screen <-> 2 screens). - void OnScreensChange(bool is_multi_screen_changed); + void OnScreensChange(bool is_multi_screen_changed) override; void OnScreenOrientationChange(); diff --git a/content/public/browser/web_contents.h b/content/public/browser/web_contents.h index 1fd156150b6b5..60be0bf1bda48 100644 --- a/content/public/browser/web_contents.h +++ b/content/public/browser/web_contents.h @@ -344,6 +344,9 @@ class WebContents : public PageNavigator, ~WebContents() override = default; + // Carbonyl patches + virtual void OnScreensChange(bool is_multi_screen_changed) = 0; + // Intrinsic tab state ------------------------------------------------------- // Gets/Sets the delegate. diff --git a/content/renderer/BUILD.gn b/content/renderer/BUILD.gn index 2a2410d4a46c1..1900c294f2f65 100644 --- a/content/renderer/BUILD.gn +++ b/content/renderer/BUILD.gn @@ -229,6 +229,9 @@ target(link_target_type, "renderer") { "//base:i18n", "//build:chromecast_buildflags", "//build:chromeos_buildflags", + "//carbonyl/src/browser:bridge", + "//carbonyl/src/browser:mojom", + "//carbonyl/src/browser:renderer", "//cc", "//cc/animation", "//cc/mojo_embedder", diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc index 891efd6a9d796..379cf6c58b2b0 100644 --- a/content/renderer/render_frame_impl.cc +++ b/content/renderer/render_frame_impl.cc @@ -285,6 +285,7 @@ #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" @@ -2243,6 +2244,10 @@ void RenderFrameImpl::Initialize(blink::WebFrame* parent) { ); host->ObserveTerminalRender(render_callback_); + + if (!carbonyl::Bridge::BitmapMode()) { + SkBitmapDevice::DisableTextRendering(); + } } void RenderFrameImpl::GetInterface( diff --git a/headless/BUILD.gn b/headless/BUILD.gn index 8018111ed9898..17120c83ee13e 100644 --- a/headless/BUILD.gn +++ b/headless/BUILD.gn @@ -349,6 +349,8 @@ component("headless_non_renderer") { "lib/browser/headless_platform_event_source.h", "lib/browser/headless_request_context_manager.cc", "lib/browser/headless_request_context_manager.h", + "lib/browser/headless_screen.cc", + "lib/browser/headless_screen.h", "lib/browser/headless_select_file_dialog_factory.cc", "lib/browser/headless_select_file_dialog_factory.h", "lib/browser/headless_web_contents_impl.cc", @@ -416,8 +418,6 @@ component("headless_non_renderer") { "lib/browser/headless_clipboard.h", "lib/browser/headless_focus_client.cc", "lib/browser/headless_focus_client.h", - "lib/browser/headless_screen.cc", - "lib/browser/headless_screen.h", "lib/browser/headless_window_parenting_client.cc", "lib/browser/headless_window_parenting_client.h", "lib/browser/headless_window_tree_host.cc", @@ -453,7 +453,8 @@ component("headless_non_renderer") { "//build:branding_buildflags", "//build:branding_buildflags", "//build:chromeos_buildflags", - "//carbonyl/src/browser:carbonyl", + "//carbonyl/src/browser:bridge", + "//carbonyl/src/browser:renderer", "//components/cookie_config", "//components/crash/core/common:common", "//components/embedder_support", @@ -474,6 +475,7 @@ component("headless_non_renderer") { "//components/profile_metrics", "//components/profile_metrics:profile_metrics", "//components/security_state/core", + "//components/zoom", "//content/public/app", "//content/public/app:app", "//content/public/browser", @@ -1011,7 +1013,7 @@ executable("headless_shell") { deps = [ ":headless_shell_lib", - "//carbonyl/src/browser:carbonyl", + "//carbonyl/src/browser:renderer", ] if (!headless_use_embedded_resources) { diff --git a/headless/app/headless_shell.cc b/headless/app/headless_shell.cc index b6a52857e8f90..6aed55bd6062d 100644 --- a/headless/app/headless_shell.cc +++ b/headless/app/headless_shell.cc @@ -4,7 +4,7 @@ #include "headless/app/headless_shell.h" -#include "carbonyl/src/browser/bridge.h" +#include "carbonyl/src/browser/renderer.h" #include @@ -93,9 +93,9 @@ void HeadlessShell::OnBrowserStart(HeadlessBrowser* browser) { HeadlessBrowserContext::Builder context_builder = browser_->CreateBrowserContextBuilder(); - carbonyl::Bridge::GetCurrent()->StartRenderer(); + carbonyl::Renderer::GetCurrent()->StartRenderer(); - context_builder.SetWindowSize(carbonyl::Bridge::GetCurrent()->GetSize()); + context_builder.SetWindowSize(carbonyl::Renderer::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 739df1ae1bd58..1f6184af60ef0 100644 --- a/headless/app/headless_shell_main.cc +++ b/headless/app/headless_shell_main.cc @@ -14,10 +14,10 @@ #endif #include "base/at_exit.h" -#include "carbonyl/src/browser/bridge.h" +#include "carbonyl/src/browser/renderer.h" int main(int argc, const char** argv) { - carbonyl::Bridge::Main(); + carbonyl::Renderer::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 1df3ffe72c93d..5aa0bdc25e409 100644 --- a/headless/lib/browser/headless_browser_impl.cc +++ b/headless/lib/browser/headless_browser_impl.cc @@ -15,6 +15,7 @@ #include "base/memory/ptr_util.h" #include "base/run_loop.h" #include "base/threading/thread_task_runner_handle.h" +#include "components/zoom/zoom_controller.h" #include "content/public/app/content_main.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" @@ -24,9 +25,11 @@ #include "headless/lib/browser/headless_browser_context_impl.h" #include "headless/lib/browser/headless_browser_main_parts.h" #include "headless/lib/browser/headless_devtools_agent_host_client.h" +#include "headless/lib/browser/headless_screen.h" #include "headless/lib/browser/headless_web_contents_impl.h" #include "net/http/http_util.h" #include "services/network/public/cpp/network_switches.h" +#include "ui/compositor/compositor.h" #include "ui/events/devices/device_data_manager.h" #include "content/public/browser/render_frame_host.h" @@ -34,6 +37,7 @@ #include "content/public/browser/render_widget_host.h" #include "content/public/browser/web_contents.h" #include "carbonyl/src/browser/bridge.h" +#include "carbonyl/src/browser/renderer.h" #include "third_party/blink/public/common/input/web_mouse_event.h" #include "third_party/blink/public/common/input/web_mouse_wheel_event.h" #include "ui/events/keycodes/keyboard_codes.h" @@ -119,7 +123,7 @@ void HeadlessBrowserImpl::set_browser_main_parts( } void HeadlessBrowserImpl::Resize() { - auto size = carbonyl::Bridge::GetCurrent()->Resize(); + auto size = carbonyl::Renderer::GetCurrent()->Resize(); auto rect = gfx::Rect(0, 0, size.width(), size.height()); for (auto* ctx: GetAllBrowserContexts()) { @@ -133,8 +137,6 @@ void HeadlessBrowserImpl::Resize() { PlatformSetWebContentsBounds(impl, rect); } } - - carbonyl::Bridge::GetCurrent()->Resize(); } void HeadlessBrowserImpl::OnShutdownInput() { @@ -436,7 +438,7 @@ void HeadlessBrowserImpl::RunOnStartCallback() { input_thread_ = std::thread([=]() { carbonyl::browser = this; - carbonyl_bridge_browser_delegate delegate = { + carbonyl_renderer_browser_delegate delegate = { .shutdown = []() { if (carbonyl::browser) { carbonyl::browser->OnShutdownInput(); @@ -500,7 +502,7 @@ void HeadlessBrowserImpl::RunOnStartCallback() { } }; - carbonyl::Bridge::GetCurrent()->Listen(&delegate); + carbonyl::Renderer::GetCurrent()->Listen(&delegate); }); } diff --git a/headless/lib/browser/headless_browser_impl_aura.cc b/headless/lib/browser/headless_browser_impl_aura.cc index 80340d9f1b3b3..91be528753cdd 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::Bridge::GetCurrent()->GetDPI())); + web_contents->window_tree_host()->SetBoundsInPixels(ScaleToEnclosedRect(bounds, carbonyl::Bridge::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_browser_impl_mac.mm b/headless/lib/browser/headless_browser_impl_mac.mm index e2cb88fbcf708..397b2585f3d0f 100644 --- a/headless/lib/browser/headless_browser_impl_mac.mm +++ b/headless/lib/browser/headless_browser_impl_mac.mm @@ -6,6 +6,8 @@ #import "base/mac/scoped_objc_class_swizzler.h" #include "base/no_destructor.h" +#include "carbonyl/src/browser/bridge.h" +#include "content/browser/renderer_host/render_widget_host_view_mac.h" #include "content/public/browser/render_widget_host_view.h" #include "content/public/browser/web_contents.h" #include "headless/lib/browser/headless_web_contents_impl.h" @@ -95,8 +97,13 @@ void HeadlessBrowserImpl::PlatformSetWebContentsBounds( content::RenderWidgetHostView* host_view = web_contents->web_contents()->GetRenderWidgetHostView(); - if (host_view) + if (host_view) { host_view->SetWindowFrameInScreen(bounds); + + static_cast(host_view)->SetCurrentDeviceScaleFactor( + carbonyl::Bridge::GetDPI() + ); + } } ui::Compositor* HeadlessBrowserImpl::PlatformGetCompositor( diff --git a/headless/lib/browser/headless_browser_main_parts_mac.mm b/headless/lib/browser/headless_browser_main_parts_mac.mm index 718e37ef8bd3e..8ca30b9d88d5b 100644 --- a/headless/lib/browser/headless_browser_main_parts_mac.mm +++ b/headless/lib/browser/headless_browser_main_parts_mac.mm @@ -6,6 +6,14 @@ #import +#include "base/command_line.h" +#include "build/build_config.h" +#include "build/chromeos_buildflags.h" +#include "components/os_crypt/os_crypt.h" +#include "content/public/browser/browser_task_traits.h" +#include "content/public/browser/browser_thread.h" +#include "device/bluetooth/dbus/bluez_dbus_manager.h" +#include "headless/app/headless_shell_switches.h" #include "headless/lib/browser/headless_shell_application_mac.h" #include "services/device/public/cpp/geolocation/geolocation_manager_impl_mac.h" @@ -16,6 +24,7 @@ void HeadlessBrowserMainParts::PreCreateMainMessageLoop() { [NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory]; if (!geolocation_manager_) geolocation_manager_ = device::GeolocationManagerImpl::Create(); + } } // namespace headless diff --git a/headless/lib/browser/headless_screen.cc b/headless/lib/browser/headless_screen.cc index 89c5ccc8d7759..047d741638df2 100644 --- a/headless/lib/browser/headless_screen.cc +++ b/headless/lib/browser/headless_screen.cc @@ -6,14 +6,18 @@ #include -#include "ui/aura/env.h" -#include "ui/aura/window.h" #include "ui/base/ime/input_method.h" #include "ui/gfx/geometry/rect_conversions.h" #include "ui/gfx/geometry/size_conversions.h" #include "ui/gfx/native_widget_types.h" +#if !BUILDFLAG(IS_MAC) +#include "ui/aura/env.h" +#include "ui/aura/window.h" +#endif + #include "carbonyl/src/browser/bridge.h" +#include "carbonyl/src/browser/renderer.h" namespace headless { @@ -24,6 +28,7 @@ HeadlessScreen* HeadlessScreen::Create(const gfx::Size& size) { HeadlessScreen::~HeadlessScreen() = default; +#if !BUILDFLAG(IS_MAC) gfx::Point HeadlessScreen::GetCursorScreenPoint() { return aura::Env::GetInstance()->last_mouse_location(); } @@ -31,6 +36,7 @@ gfx::Point HeadlessScreen::GetCursorScreenPoint() { bool HeadlessScreen::IsWindowUnderCursor(gfx::NativeWindow window) { return GetWindowAtScreenPoint(GetCursorScreenPoint()) == window; } +#endif gfx::NativeWindow HeadlessScreen::GetWindowAtScreenPoint( const gfx::Point& point) { @@ -48,12 +54,22 @@ display::Display HeadlessScreen::GetDisplayNearestWindow( return GetPrimaryDisplay(); } +void HeadlessScreen::Resize() { + float dpi = carbonyl::Bridge::GetDPI(); + auto size = carbonyl::Renderer::GetCurrent()->GetSize(); + auto rect = gfx::Rect(0, 0, size.width() * dpi, size.height() * dpi); + + display_.SetScaleAndBounds(dpi, rect); + ProcessDisplayChanged(display_, true /* is_primary */); +} + HeadlessScreen::HeadlessScreen(const gfx::Rect& screen_bounds) { + float dpi = carbonyl::Bridge::GetDPI(); static int64_t synthesized_display_id = 2000; - display::Display display(synthesized_display_id++); - float dpi = carbonyl::Bridge::GetCurrent()->GetDPI(); - display.SetScaleAndBounds(dpi, ScaleToEnclosedRect(screen_bounds, dpi)); - ProcessDisplayChanged(display, true /* is_primary */); + + display_ = display::Display(synthesized_display_id++); + display_.SetScaleAndBounds(dpi, ScaleToEnclosedRect(screen_bounds, dpi)); + ProcessDisplayChanged(display_, true /* is_primary */); } } // namespace headless diff --git a/headless/lib/browser/headless_screen.h b/headless/lib/browser/headless_screen.h index 5ec78e5fe4685..23c073b1c86b3 100644 --- a/headless/lib/browser/headless_screen.h +++ b/headless/lib/browser/headless_screen.h @@ -6,7 +6,6 @@ #define HEADLESS_LIB_BROWSER_HEADLESS_SCREEN_H_ #include "base/compiler_specific.h" -#include "ui/aura/window_observer.h" #include "ui/display/display.h" #include "ui/display/screen_base.h" @@ -26,10 +25,15 @@ class HeadlessScreen : public display::ScreenBase { ~HeadlessScreen() override; + void Resize(); + protected: // display::Screen overrides: +#if !BUILDFLAG(IS_MAC) gfx::Point GetCursorScreenPoint() override; bool IsWindowUnderCursor(gfx::NativeWindow window) override; +#endif + gfx::NativeWindow GetWindowAtScreenPoint(const gfx::Point& point) override; gfx::NativeWindow GetLocalProcessWindowAtPoint( const gfx::Point& point, @@ -39,6 +43,8 @@ class HeadlessScreen : public display::ScreenBase { private: explicit HeadlessScreen(const gfx::Rect& screen_bounds); + + display::Display display_; }; } // namespace headless diff --git a/headless/lib/browser/headless_web_contents_impl.cc b/headless/lib/browser/headless_web_contents_impl.cc index a166a08f6ea15..091bde787d47c 100644 --- a/headless/lib/browser/headless_web_contents_impl.cc +++ b/headless/lib/browser/headless_web_contents_impl.cc @@ -22,7 +22,8 @@ #include "base/values.h" #include "build/build_config.h" #include "build/chromeos_buildflags.h" -#include "carbonyl/src/browser/bridge.h" +#include "carbonyl/src/browser/renderer.h" +#include "components/zoom/zoom_controller.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/child_process_termination_info.h" #include "content/public/browser/devtools_agent_host.h" @@ -335,6 +336,7 @@ HeadlessWebContentsImpl::HeadlessWebContentsImpl( #if BUILDFLAG(ENABLE_PRINTING) HeadlessPrintManager::CreateForWebContents(web_contents_.get()); #endif + zoom::ZoomController::CreateForWebContents(web_contents_.get()); UpdatePrefsFromSystemSettings(web_contents_->GetMutableRendererPrefs()); web_contents_->GetMutableRendererPrefs()->accept_languages = browser_context->options()->accept_language(); @@ -400,7 +402,7 @@ void HeadlessWebContentsImpl::TitleWasSet(content::NavigationEntry* entry) { if (!web_contents() || !web_contents()->GetPrimaryMainFrame()->IsActive()) return; - carbonyl::Bridge::GetCurrent()->SetTitle(base::UTF16ToUTF8(entry->GetTitleForDisplay())); + carbonyl::Renderer::GetCurrent()->SetTitle(base::UTF16ToUTF8(entry->GetTitleForDisplay())); } void HeadlessWebContentsImpl::DidFinishNavigation(content::NavigationHandle* handle) { @@ -411,7 +413,7 @@ void HeadlessWebContentsImpl::DidFinishNavigation(content::NavigationHandle* han auto& nav = web_contents()->GetController(); - carbonyl::Bridge::GetCurrent()->PushNav( + carbonyl::Renderer::GetCurrent()->PushNav( handle->GetURL().spec(), nav.CanGoBack(), nav.CanGoForward() diff --git a/printing/printing_context_mac.mm b/printing/printing_context_mac.mm index 9dd650de9a9a3..8cfb6e9015c3f 100644 --- a/printing/printing_context_mac.mm +++ b/printing/printing_context_mac.mm @@ -400,6 +400,7 @@ bool PrintingContextMac::SetDuplexModeInPrintSettings(mojom::DuplexMode mode) { bool PrintingContextMac::SetOutputColor(int color_mode) { const mojom::ColorModel color_model = ColorModeToColorModel(color_mode); +#if BUILDFLAG(USE_CUPS) if (!base::FeatureList::IsEnabled(features::kCupsIppPrintingBackend)) { std::string color_setting_name; std::string color_value; @@ -412,6 +413,7 @@ bool PrintingContextMac::SetOutputColor(int color_mode) { GetIppColorModelForModel(color_model))) { return false; } +#endif struct PpdColorSetting { constexpr PpdColorSetting(base::StringPiece name, diff --git a/third_party/blink/renderer/core/BUILD.gn b/third_party/blink/renderer/core/BUILD.gn index c29495a7060d7..654438b8d7424 100644 --- a/third_party/blink/renderer/core/BUILD.gn +++ b/third_party/blink/renderer/core/BUILD.gn @@ -312,6 +312,7 @@ component("core") { ":generate_eventhandler_names", ":generated_settings_macros", "//build:chromeos_buildflags", + "//carbonyl/src/browser:bridge", "//components/attribution_reporting", "//components/attribution_reporting:mojom_blink", "//components/paint_preview/common", 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 cb116ee07c8f6..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,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/blink.h" +#include "carbonyl/src/browser/bridge.h" namespace blink { @@ -1043,7 +1043,7 @@ scoped_refptr StyleResolver::ResolveStyle( UseCounter::Count(GetDocument(), WebFeature::kHasGlyphRelativeUnits); } - if (!carbonyl::blink::BitmapMode()) { + if (!carbonyl::Bridge::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 ceb41d781acf6..bc6428f4f5c0d 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:blink", + "//carbonyl/src/browser:bridge", "//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 3d1b463e9651c..4625300729523 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/blink.h" +#include "carbonyl/src/browser/bridge.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::blink::BitmapMode()) { + if (!carbonyl::Bridge::BitmapMode()) { return; } @@ -237,7 +237,7 @@ void Font::DrawText(cc::PaintCanvas* canvas, if (ShouldSkipDrawing()) return; - if (!carbonyl::blink::BitmapMode()) { + 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::blink::BitmapMode()) { + if (!carbonyl::Bridge::BitmapMode()) { auto string = StringView( text_info.text, text_info.from, diff --git a/ui/display/BUILD.gn b/ui/display/BUILD.gn index aadf9f2a52fd6..9b3aefc4d3b04 100644 --- a/ui/display/BUILD.gn +++ b/ui/display/BUILD.gn @@ -101,6 +101,7 @@ component("display") { "//base", "//base:i18n", "//build:chromeos_buildflags", + "//carbonyl/src/browser:bridge", "//mojo/public/cpp/bindings:struct_traits", "//ui/display/mojom:mojom_shared_cpp_sources", "//ui/display/util", diff --git a/ui/display/display.cc b/ui/display/display.cc index 1d71f3b4c9857..d670831de4ca4 100644 --- a/ui/display/display.cc +++ b/ui/display/display.cc @@ -26,39 +26,10 @@ namespace display { namespace { -// This variable tracks whether the forced device scale factor switch needs to -// be read from the command line, i.e. if it is set to -1 then the command line -// is checked. -int g_has_forced_device_scale_factor = -1; - -// This variable caches the forced device scale factor value which is read off -// the command line. If the cache is invalidated by setting this variable to -// -1.0, we read the forced device scale factor again. -float g_forced_device_scale_factor = -1.0; - // An allowance error epsilon caused by fractional scale factor to produce // expected DP display size. constexpr float kDisplaySizeAllowanceEpsilon = 0.01f; -bool HasForceDeviceScaleFactorImpl() { - // return base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kForceDeviceScaleFactor); - return true; -} - -float GetForcedDeviceScaleFactorImpl() { - // double scale_in_double = 1.0; - // if (HasForceDeviceScaleFactorImpl()) { - // std::string value = - // base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( - // switches::kForceDeviceScaleFactor); - // if (!base::StringToDouble(value, &scale_in_double)) { - // LOG(ERROR) << "Failed to parse the default device scale factor:" << value; - // scale_in_double = 1.0; - // } - // } - return carbonyl::Bridge::GetCurrent()->GetDPI(); -} - const char* ToRotationString(display::Display::Rotation rotation) { switch (rotation) { case display::Display::ROTATE_0: @@ -78,33 +49,19 @@ const char* ToRotationString(display::Display::Rotation rotation) { // static float Display::GetForcedDeviceScaleFactor() { - if (g_forced_device_scale_factor < 0) - g_forced_device_scale_factor = GetForcedDeviceScaleFactorImpl(); - return g_forced_device_scale_factor; + return carbonyl::Bridge::GetDPI(); } // static bool Display::HasForceDeviceScaleFactor() { - if (g_has_forced_device_scale_factor == -1) - g_has_forced_device_scale_factor = HasForceDeviceScaleFactorImpl(); - return !!g_has_forced_device_scale_factor; + return true; } // static -void Display::ResetForceDeviceScaleFactorForTesting() { - g_has_forced_device_scale_factor = -1; - g_forced_device_scale_factor = -1.0; -} +void Display::ResetForceDeviceScaleFactorForTesting() {} // static -void Display::SetForceDeviceScaleFactor(double dsf) { - // Reset any previously set values and unset the flag. - // g_has_forced_device_scale_factor = -1; - // g_forced_device_scale_factor = -1.0; - - // base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( - // switches::kForceDeviceScaleFactor, base::StringPrintf("%.2f", dsf)); -} +void Display::SetForceDeviceScaleFactor(double dsf) {} // static gfx::ColorSpace Display::GetForcedRasterColorProfile() { ================================================ FILE: chromium/patches/chromium/0014-Move-Skia-text-rendering-control-to-bridge.patch ================================================ From 2275364ee7e16ba6b46f0f339e34326d4a8c7584 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj 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", ================================================ FILE: chromium/patches/skia/0001-Disable-text-rendering.patch ================================================ From 218fbf4bba772c465712c4ea442adb57968e9c22 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Mon, 13 Feb 2023 17:18:18 +0100 Subject: [PATCH 1/2] Disable text rendering --- 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..9631f47967 100644 --- a/src/core/SkBitmapDevice.cpp +++ b/src/core/SkBitmapDevice.cpp @@ -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 ) + if (carbonyl::Bridge::BitmapMode()) { + SkASSERT(!glyphRunList.hasRSXForm()); + LOOP_TILER( drawGlyphRunList(canvas, &fGlyphPainter, glyphRunList, drawingPaint), nullptr ) + } } void SkBitmapDevice::drawVertices(const SkVertices* vertices, ================================================ FILE: chromium/patches/skia/0002-Export-some-private-APIs.patch ================================================ From a271b203a2b60f0cd450bda0fa2cc14885f1d9a8 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Thu, 9 Feb 2023 03:38:05 +0100 Subject: [PATCH 2/2] Export some private APIs Temporary until TextCaptureDevice moves here --- include/utils/SkBase64.h | 2 +- src/core/SkClipStack.h | 2 +- src/core/SkClipStackDevice.h | 2 +- src/core/SkDevice.h | 2 +- src/core/SkFontPriv.h | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/utils/SkBase64.h b/include/utils/SkBase64.h index e01028543a..beddbd2c95 100644 --- a/include/utils/SkBase64.h +++ b/include/utils/SkBase64.h @@ -12,7 +12,7 @@ #include -struct SkBase64 { +struct SK_API SkBase64 { public: enum Error { kNoError, diff --git a/src/core/SkClipStack.h b/src/core/SkClipStack.h index c325d2c619..d93b9cf37f 100644 --- a/src/core/SkClipStack.h +++ b/src/core/SkClipStack.h @@ -30,7 +30,7 @@ class GrProxyProvider; // (i.e., the fSaveCount in force when it was added). Restores are thus // implemented by removing clips from fDeque that have an fSaveCount larger // then the freshly decremented count. -class SkClipStack { +class SK_API SkClipStack { public: enum BoundsType { // The bounding box contains all the pixels that can be written to diff --git a/src/core/SkClipStackDevice.h b/src/core/SkClipStackDevice.h index eff1f1a440..a8d6b4fe07 100644 --- a/src/core/SkClipStackDevice.h +++ b/src/core/SkClipStackDevice.h @@ -11,7 +11,7 @@ #include "src/core/SkClipStack.h" #include "src/core/SkDevice.h" -class SkClipStackDevice : public SkBaseDevice { +class SK_API SkClipStackDevice : public SkBaseDevice { public: SkClipStackDevice(const SkImageInfo& info, const SkSurfaceProps& props) : SkBaseDevice(info, props) diff --git a/src/core/SkDevice.h b/src/core/SkDevice.h index e0fed94b9b..c7194f9c1c 100644 --- a/src/core/SkDevice.h +++ b/src/core/SkDevice.h @@ -54,7 +54,7 @@ struct SkStrikeDeviceInfo { const sktext::gpu::SDFTControl* const fSDFTControl; }; -class SkBaseDevice : public SkRefCnt, public SkMatrixProvider { +class SK_API SkBaseDevice : public SkRefCnt, public SkMatrixProvider { public: SkBaseDevice(const SkImageInfo&, const SkSurfaceProps&); diff --git a/src/core/SkFontPriv.h b/src/core/SkFontPriv.h index 95ca905bf1..a31aba8e2b 100644 --- a/src/core/SkFontPriv.h +++ b/src/core/SkFontPriv.h @@ -16,7 +16,7 @@ class SkReadBuffer; class SkWriteBuffer; -class SkFontPriv { +class SK_API SkFontPriv { 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. ================================================ FILE: chromium/patches/webrtc/0001-Disable-GIO-on-Linux.patch ================================================ From 5ad57c96f23739717bcea018baf2bc8f4157b01d Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj 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" ] } } ================================================ FILE: cliff.toml ================================================ [changelog] header = """ # Changelog\n All notable changes to this project will be documented in this file.\n """ body = """ {% if version %}\ ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} {% else %}\ ## [unreleased] {% endif %}\ {% for group, commits in commits | group_by(attribute="group") %} ### {{ group | striptags | trim | upper_first }} {% for commit in commits %} - {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}\ {%- if commit.links %} ({% for link in commit.links %}[{{link.text}}]({{link.href}}){% endfor -%}){% endif %}\ {% endfor %} {% endfor %}\n """ trim = true footer = "" [git] conventional_commits = true filter_unconventional = true split_commits = false commit_preprocessors = [ { pattern = "#([0-9]+)", replace = "[#${1}](https://github.com/fathyb/carbonyl/issues/${1})" } ] commit_parsers = [ { message = "^feat", group = "🚀 Features" }, { message = "^fix", group = "🐛 Bug Fixes" }, { message = "^doc", group = "📖 Documentation" }, { message = "^perf", group = "⚡ Performance"}, { message = "^chore", skip = true }, { body = ".*security", group = "🔐 Security"}, ] protect_breaking_commits = false filter_commits = false tag_pattern = "v[0-9]*" skip_tags = "" ignore_tags = "" topo_order = true sort_commits = "oldest" ================================================ FILE: license.md ================================================ _Copyright © 2023, Fathy Boundjadj_ _All rights reserved._ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: package.json ================================================ { "name": "carbonyl", "version": "0.0.3", "license": "BSD-3-Clause" } ================================================ FILE: readme.md ================================================

   O    O
    \  /
O —— Cr —— O
    /  \
   O    O

Carbonyl

Carbonyl is a Chromium based browser built to run in a terminal. [Read the blog post](https://fathy.fr/carbonyl). It supports pretty much all Web APIs including WebGL, WebGPU, audio and video playback, animations, etc.. It's snappy, starts in less than a second, runs at 60 FPS, and idles at 0% CPU usage. It does not require a window server (i.e. works in a safe-mode console), and even runs through SSH. Carbonyl originally started as [`html2svg`](https://github.com/fathyb/html2svg) and is now the runtime behind it. ## Usage > Carbonyl on Linux without Docker requires the same dependencies as Chromium. ### Docker ```shell $ docker run --rm -ti fathyb/carbonyl https://youtube.com ``` ### npm ```console $ npm install --global carbonyl $ carbonyl https://github.com ``` ### Binaries - [macOS amd64](https://github.com/fathyb/carbonyl/releases/download/v0.0.3/carbonyl.macos-amd64.zip) - [macOS arm64](https://github.com/fathyb/carbonyl/releases/download/v0.0.3/carbonyl.macos-arm64.zip) - [Linux amd64](https://github.com/fathyb/carbonyl/releases/download/v0.0.3/carbonyl.linux-amd64.zip) - [Linux arm64](https://github.com/fathyb/carbonyl/releases/download/v0.0.3/carbonyl.linux-arm64.zip) ## Demo
## Known issues - Fullscreen mode not supported yet ## Comparisons ### Lynx Lynx is the original terminal web browser, and the oldest one still maintained. #### Pros - When it understands a page, Lynx has the best layout, fully optimized for the terminal #### Cons > Some might sound like pluses, but Browsh and Carbonyl let you disable most of those if you'd like - Does not support a lot of modern web standards - Cannot run JavaScript/WebAssembly - Cannot view or play media (audio, video, DOOM) ### Browsh Browsh is the original "normal browser into a terminal" project. It starts Firefox in headless mode and connects to it through an automation protocol. #### Pro - It's easier to update the underlying browser: just update Firefox - This makes development easier: just install Firefox and compile the Go code in a few seconds - As of today, Browsh supports extensions while Carbonyl doesn't, although it's on our roadmap #### Cons - It runs slower and requires more resources than Carbonyl. 50x more CPU power is needed for the same content in average, that's because Carbonyl does not downscale or copy the window framebuffer, it natively renders to the terminal resolution. - It uses custom stylesheets to fix the layout, which is less reliable than Carbonyl's changes to its HTML engine (Blink). ## Operating System Support As far as tested, the operating systems under are supported: - Linux (Debian, Ubuntu and Arch tested) - MacOS - Windows 11 and WSL ## Contributing Carbonyl is split in two parts: the "core" which is built into a shared library (`libcarbonyl`), and the "runtime" which dynamically loads the core (`carbonyl` executable). The core is written in Rust and takes a few seconds to build from scratch. The runtime is a modified version of the Chromium headless shell and takes more than an hour to build from scratch. If you're just making changes to the Rust code, build `libcarbonyl` and replace it in a release version of Carbonyl. ### Core ```console $ cargo build ``` ### Runtime Few notes: - Building the runtime is almost the same as building Chromium with extra steps to patch and bundle the Rust library. Scripts in the `scripts/` directory are simple wrappers around `gn`, `ninja`, etc.. - Building Chromium for arm64 on Linux requires an amd64 processor - Carbonyl is only tested on Linux and macOS, other platforms likely require code changes to Chromium - Chromium is huge and takes a long time to build, making your computer mostly unresponsive. An 8-core CPU such as an M1 Max or an i9 9900k with 10 Gbps fiber takes around ~1 hour to fetch and build. It requires around 100 GB of disk space. #### Fetch > Fetch Chromium's code. ```console $ ./scripts/gclient.sh sync ``` #### Apply patches > Any changes made to Chromium will be reverted, make sure to save any changes you made. ```console $ ./scripts/patches.sh apply ``` #### Configure ```console $ ./scripts/gn.sh args out/Default ``` > `Default` is the target name, you can use multiple ones and pick any name you'd like, i.e.: > > ```console > $ ./scripts/gn.sh args out/release > $ ./scripts/gn.sh args out/debug > # or if you'd like to build a multi-platform image > $ ./scripts/gn.sh args out/arm64 > $ ./scripts/gn.sh args out/amd64 > ``` When prompted, enter the following arguments: ```gn import("//carbonyl/src/browser/args.gn") # uncomment this to build for arm64 # target_cpu = "arm64" # comment this to disable ccache cc_wrapper = "env CCACHE_SLOPPINESS=time_macros ccache" # comment this for a debug build is_debug = false symbol_level = 0 is_official_build = true ``` #### Build binaries ```console $ ./scripts/build.sh Default ``` This should produce the following outputs: - `out/Default/headless_shell`: browser binary - `out/Default/icudtl.dat` - `out/Default/libEGL.so` - `out/Default/libGLESv2.so` - `out/Default/v8_context_snapshot.bin` #### Build Docker image ```console # Build arm64 Docker image using binaries from the Default target $ ./scripts/docker-build.sh Default arm64 # Build amd64 Docker image using binaries from the Default target $ ./scripts/docker-build.sh Default amd64 ``` #### Run ``` $ ./scripts/run.sh Default https://wikipedia.org ``` ================================================ FILE: scripts/build.sh ================================================ #!/usr/bin/env bash export CARBONYL_ROOT=$(cd $(dirname -- "$0") && dirname -- "$(pwd)") export INSTALL_DEPOT_TOOLS="true" cd "$CARBONYL_ROOT" source scripts/env.sh target="$1" cpu="$2" if [ ! -z "$target" ]; then shift fi if [ ! -z "$cpu" ]; then shift fi 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/$target" ninja headless:headless_shell "$@" ================================================ FILE: scripts/changelog.sh ================================================ #!/usr/bin/env bash export CARBONYL_ROOT=$(cd $(dirname -- "$0") && dirname -- $(pwd)) cd "$CARBONYL_ROOT" source "scripts/env.sh" git cliff a69e8b609625b67a3e52e18f73ba5d0f49ceb7c3..HEAD "$@" > changelog.md ================================================ FILE: scripts/copy-binaries.sh ================================================ #!/usr/bin/env bash export CARBONYL_ROOT=$(cd $(dirname -- "$0") && dirname -- $(pwd)) cd "$CARBONYL_ROOT" source "scripts/env.sh" target="$1" cpu="$2" triple=$(scripts/platform-triple.sh "$cpu") dest="build/pre-built/$triple" src="$CHROMIUM_SRC/out/$target" lib_ext="so" if [ -f "$src"/libEGL.dylib ]; then lib_ext="dylib" fi rm -rf "$dest" mkdir -p "$dest" cd "$dest" cp "$src/headless_shell" carbonyl cp "$src/icudtl.dat" . cp "$src/libEGL.$lib_ext" . cp "$src/libGLESv2.$lib_ext" . cp "$src"/v8_context_snapshot*.bin . cp "$CARBONYL_ROOT/build/$triple/release/libcarbonyl.$lib_ext" . files="carbonyl " if [ "$lib_ext" == "so" ]; then cp "$src/libvk_swiftshader.so" . cp "$src/libvulkan.so.1" . cp "$src/vk_swiftshader_icd.json" . files+=$(echo *.so *.so.1) fi if [[ "$cpu" == "arm64" ]] && command -v aarch64-linux-gnu-strip; then aarch64-linux-gnu-strip $files else strip $files fi echo "Binaries copied to $dest" ================================================ FILE: scripts/docker-build.sh ================================================ #!/usr/bin/env bash export CARBONYL_ROOT=$(cd $(dirname -- "$0") && dirname -- "$(pwd)") cd "$CARBONYL_ROOT" source "scripts/env.sh" cpu="$1" 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" tag="fathyb/carbonyl:$cpu" docker buildx build "$build_dir" --load --platform "linux/$cpu" --tag "$tag" echo "Image tag: $tag" ================================================ FILE: scripts/docker-push.sh ================================================ #!/usr/bin/env bash export CARBONYL_ROOT=$(cd $(dirname -- "$0") && dirname -- "$(pwd)") source "$CARBONYL_ROOT/scripts/env.sh" tag="fathyb/carbonyl" version="$1" echo "Pushing arm64 image as $tag:$version-arm64" docker tag "$tag:arm64" "$tag:$version-arm64" docker push "$tag:$version-arm64" echo "Pushing amd64 image as $tag:$version-amd64" docker tag "$tag:amd64" "$tag:$version-amd64" docker push "$tag:$version-amd64" docker manifest create "$tag:$version" \ --amend "$tag:$version-arm64" \ --amend "$tag:$version-amd64" docker manifest push "$tag:$version" --purge ================================================ FILE: scripts/env.sh ================================================ #!/usr/bin/env bash set -eo pipefail if [ -z "$CARBONYL_ROOT" ]; then echo "CARBONYL_ROOT should be defined" exit 2 fi if [ -z "$CHROMIUM_ROOT" ]; then export CHROMIUM_ROOT="$CARBONYL_ROOT/chromium" fi if [ -z "$CHROMIUM_SRC" ]; then export CHROMIUM_SRC="$CHROMIUM_ROOT/src" fi if [ -z "$DEPOT_TOOLS_ROOT" ]; then export DEPOT_TOOLS_ROOT="$CHROMIUM_ROOT/depot_tools" fi export PATH="$PATH:$DEPOT_TOOLS_ROOT" 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 fi ================================================ FILE: scripts/gclient.sh ================================================ #!/usr/bin/env bash export CARBONYL_ROOT=$(cd $(dirname -- "$0") && dirname -- "$(pwd)") export INSTALL_DEPOT_TOOLS="true" source "$CARBONYL_ROOT/scripts/env.sh" ( cd "$CHROMIUM_ROOT" && gclient "$@" ) ================================================ FILE: scripts/gn.sh ================================================ #!/usr/bin/env bash export CARBONYL_ROOT=$(cd $(dirname -- "$0") && dirname -- "$(pwd)") export INSTALL_DEPOT_TOOLS="true" source "$CARBONYL_ROOT/scripts/env.sh" ( cd "$CHROMIUM_SRC" && gn "$@" ) ================================================ FILE: scripts/npm-package.mjs ================================================ 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)) ) ) ), ]); ================================================ FILE: scripts/npm-package.sh ================================================ #!/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" ================================================ FILE: scripts/npm-publish.sh ================================================ #!/usr/bin/env bash export CARBONYL_ROOT=$(cd $(dirname -- "$0") && dirname -- $(pwd)) cd "$CARBONYL_ROOT" source "scripts/env.sh" cd "build/packages" if [ -z "$CARBONYL_PUBLISH_PLATFORM" ] && [ -z "$CARBONYL_PUBLISH_ARCH" ]; then cd "carbonyl" else cd "carbonyl-$CARBONYL_PUBLISH_PLATFORM-$CARBONYL_PUBLISH_ARCH" fi yarn publish --non-interactive --access public "$@" ================================================ FILE: scripts/patches.sh ================================================ #!/usr/bin/env bash export CARBONYL_ROOT=$(cd $(dirname -- "$0") && dirname -- "$(pwd)") source "$CARBONYL_ROOT/scripts/env.sh" cd "$CHROMIUM_SRC" chromium_upstream="92da8189788b1b373cbd3348f73d695dfdc521b6" skia_upstream="486deb23bc2a4d3d09c66fef52c2ad64d8b4f761" webrtc_upstream="727080cbacd58a2f303ed8a03f0264fe1493e47a" if [[ "$1" == "apply" ]]; then echo "Stashing Chromium changes.." git add -A . git stash echo "Applying Chromium patches.." git checkout "$chromium_upstream" git am --committer-date-is-author-date "$CARBONYL_ROOT/chromium/patches/chromium"/* "$CARBONYL_ROOT/scripts/restore-mtime.sh" "$chromium_upstream" echo "Stashing Skia changes.." cd "$CHROMIUM_SRC/third_party/skia" git add -A . git stash echo "Applying Skia patches.." git checkout "$skia_upstream" 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 git add -A carbonyl fi echo "Updating Chromium patches.." 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 "$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 echo "Unknown argument: $1" exit 2 fi ================================================ FILE: scripts/platform-triple.sh ================================================ #!/usr/bin/env bash export CARBONYL_ROOT=$(cd $(dirname -- "$0") && dirname -- $(pwd)) source "$CARBONYL_ROOT/scripts/env.sh" cpu="$1" platform="$2" if [ -z "$platform" ]; then if [[ "$OSTYPE" == "linux-gnu"* ]]; then platform="linux" elif [[ "$OSTYPE" == "darwin"* ]]; then platform="macos" else echo "Unsupported platform: $OSTYPE" exit 2 fi fi if [ "$platform" == "linux" ]; then platform="unknown-linux-gnu" elif [ "$platform" == "macos" ]; then platform="apple-darwin" fi if [ -z "$cpu" ]; then cpu="$(uname -m)" fi if [[ "$cpu" == "arm64" ]]; then cpu="aarch64" elif [[ "$cpu" == "amd64" ]]; then cpu="x86_64" fi echo -n "$cpu-$platform" ================================================ FILE: scripts/release.sh ================================================ #!/usr/bin/env bash export CARBONYL_ROOT=$(cd $(dirname -- "$0") && dirname -- $(pwd)) cd "$CARBONYL_ROOT" source "scripts/env.sh" npm version "$1" --no-git-tag-version "$CARBONYL_ROOT/scripts/changelog.sh" --tag "$1" git add -A . git commit -m "chore(release): $1" git tag -a "v$1" -m "chore(release): $1" ================================================ FILE: scripts/restore-mtime.sh ================================================ #!/usr/bin/env bash export CARBONYL_ROOT=$(cd $(dirname -- "$0") && dirname -- $(pwd)) source "$CARBONYL_ROOT/scripts/env.sh" for file in $(git diff --name-only HEAD "$1"); do mtime="$(git log --pretty=format:%cd -n 1 --date=format:%Y%m%d%H%M.%S "$file")" touch -t "$mtime" "$file" done ================================================ FILE: scripts/run.sh ================================================ #!/usr/bin/env bash export CARBONYL_ROOT=$(cd $(dirname -- "$0") && dirname -- "$(pwd)") source "$CARBONYL_ROOT/scripts/env.sh" target="$1" shift "$CHROMIUM_SRC/out/$target/headless_shell" "$@" ================================================ FILE: scripts/runtime-hash.sh ================================================ #!/usr/bin/env bash export CARBONYL_ROOT=$(cd $(dirname -- "$0") && dirname -- $(pwd)) cd "$CARBONYL_ROOT" source "scripts/env.sh" for file in chromium/.gclient chromium/patches/*/*.patch src/browser/*.{cc,h,gn,mojom}; do file_sha=$(cat "$file" | openssl sha256) result=$(echo -n "$sha/${file_sha: -64}" | openssl sha256) sha+="${file_sha: -64} ${file}"$'\n' done hash=$(echo "$sha" | sort | openssl sha256) echo -n "${hash: -16}" ================================================ FILE: scripts/runtime-pull.sh ================================================ #!/usr/bin/env bash export CARBONYL_ROOT=$(cd $(dirname -- "$0") && dirname -- $(pwd)) cd "$CARBONYL_ROOT" source "scripts/env.sh" echo "Computing Chromium patches sha.." sha="$(scripts/runtime-hash.sh)" triple="$(scripts/platform-triple.sh "$@")" if [ ! -f "build/pre-built/$triple.tgz" ]; then url="https://carbonyl.fathy.fr/runtime/$sha/$triple.tgz" echo "Downloading pre-built binaries from $url" mkdir -p build/pre-built if ! curl --silent --fail --output "build/pre-built/$triple.tgz" "$url"; then echo "Pre-built binaries not available" exit 1 fi fi echo "Pre-build binaries available, extracting.." cd build/pre-built rm -rf "$triple" tar -xvzf "$triple.tgz" ================================================ FILE: scripts/runtime-push.sh ================================================ #!/usr/bin/env bash export CARBONYL_ROOT=$(cd $(dirname -- "$0") && dirname -- $(pwd)) cd "$CARBONYL_ROOT" source "scripts/env.sh" echo "Computing Chromium patches hash.." hash=$(scripts/runtime-hash.sh) triple=$(scripts/platform-triple.sh "$1") echo "Archiving binaries.." cd build/pre-built tar cvzf "$triple.tgz" "$triple" echo "Binaries archived to build/pre-built/$triple.tgz" echo "Pushing $triple.tgz to object storage.." AWS_PAGER="" \ AWS_ACCESS_KEY_ID="$CDN_ACCESS_KEY_ID" \ AWS_SECRET_ACCESS_KEY="$CDN_SECRET_ACCESS_KEY" \ aws s3api put-object \ --endpoint-url "https://7985f304d3a79d71fb63aeb17a31fe30.r2.cloudflarestorage.com" \ --bucket "carbonyl-runtime" \ --key "runtime/$hash/$triple.tgz" \ --body "$triple.tgz" ================================================ FILE: src/browser/BUILD.gn ================================================ import("//build/config/compiler/compiler.gni") import("//mojo/public/tools/bindings/mojom.gni") mojom("mojom") { sources = [ "carbonyl.mojom" ] deps = [ "//ui/gfx/geometry/mojom", "//skia/public/mojom", ] } 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", ] deps = [ ":renderer", "//base", "//components/viz/host", "//services/viz/privileged/mojom", ] } config("lib") { target = "" if (current_cpu == "x64") { target += "x86_64-" } else if (current_cpu == "arm64") { target += "aarch64-" } if (is_mac) { target += "apple-darwin" } else if (is_linux) { target += "unknown-linux-gnu" } 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" ] } ================================================ FILE: src/browser/args.gn ================================================ headless_enable_commands = false headless_use_embedded_resources = true # Enable proprietary codecs such as H.264 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 enable_media_remoting = false enable_speech_service = false enable_component_updater = false enable_screen_ai_service = false enable_system_notifications = false enable_browser_speech_service = false enable_webui_certificate_viewer = false ================================================ FILE: src/browser/bridge.cc ================================================ #include "carbonyl/src/browser/bridge.h" namespace { float dpi_ = 0.0; bool bitmap_mode_ = false; } namespace carbonyl { void Bridge::Resize() {} float Bridge::GetDPI() { return dpi_; } bool Bridge::BitmapMode() { return bitmap_mode_; } void Bridge::Configure(float dpi, bool bitmap_mode) { dpi_ = dpi; bitmap_mode_ = bitmap_mode; } } ================================================ FILE: src/browser/bridge.h ================================================ #ifndef CARBONYL_SRC_BROWSER_BRIDGE_H_ #define CARBONYL_SRC_BROWSER_BRIDGE_H_ #include "carbonyl/src/browser/export.h" namespace carbonyl { class Renderer; class CARBONYL_BRIDGE_EXPORT Bridge { public: static float GetDPI(); static bool BitmapMode(); private: friend class Renderer; static void Resize(); static void Configure(float dpi, bool bitmap_mode); }; } #endif // CARBONYL_SRC_BROWSER_BRIDGE_H_ ================================================ FILE: src/browser/bridge.rs ================================================ use std::ffi::{CStr, CString}; use std::io::Write; use std::process::{Command, Stdio}; use std::sync::{mpsc, Mutex}; use std::{env, io, thread}; use libc::{c_char, c_float, c_int, c_uchar, c_uint, c_void, size_t}; use crate::cli::{CommandLine, CommandLineProgram, EnvVar}; use crate::gfx::{Cast, Color, Point, Rect, Size}; use crate::output::{RenderThread, Window}; use crate::ui::navigation::NavigationAction; 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, } #[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; impl From for Point where c_uint: Cast, { fn from(value: CPoint) -> Self { Point::new(value.x, value.y).cast() } } impl From> for CSize { fn from(value: Size) -> Self { Self { width: value.width, height: value.height, } } } impl From for Size where c_uint: Cast, { fn from(value: CSize) -> Self { Size::new(value.width, value.height).cast() } } impl From 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(), go_to: extern "C" fn(*const c_char), go_back: extern "C" fn(), go_forward: extern "C" fn(), scroll: extern "C" fn(c_int), key_press: extern "C" fn(c_char), mouse_up: extern "C" fn(c_uint, c_uint), mouse_down: extern "C" fn(c_uint, c_uint), mouse_move: extern "C" fn(c_uint, c_uint), post_task: extern "C" fn(extern "C" fn(*mut c_void), *mut c_void), } fn main() -> io::Result> { 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 mut command = Command::new(env::current_exe()?); if !cmd.bitmap { command .arg("--disable-threaded-scrolling") .arg("--disable-threaded-animation"); } let output = command .args(cmd.args) .env(EnvVar::ShellMode, "1") .stdin(Stdio::inherit()) .stdout(Stdio::inherit()) .stderr(Stdio::piped()) .output()?; terminal.teardown(); let code = output.status.code().unwrap_or(127); if code != 0 || cmd.debug { io::stderr().write_all(&output.stderr)?; } Ok(Some(code)) } #[no_mangle] pub extern "C" fn carbonyl_bridge_main() { if let Some(code) = main().unwrap() { std::process::exit(code) } } #[no_mangle] pub extern "C" fn carbonyl_bridge_bitmap_mode() -> bool { CommandLine::parse().bitmap } #[no_mangle] pub extern "C" fn carbonyl_bridge_get_dpi() -> c_float { Window::read().dpi } #[no_mangle] pub extern "C" fn carbonyl_renderer_create() -> RendererPtr { let bridge = RendererBridge { cmd: CommandLine::parse(), window: Window::read(), renderer: RenderThread::new(), }; 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( bridge: RendererPtr, url: *const c_char, can_go_back: bool, can_go_forward: bool, ) { let (bridge, url) = unsafe { (bridge.as_ref(), CStr::from_ptr(url)) }; let (mut bridge, url) = (bridge.unwrap().lock().unwrap(), url.to_owned()); 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(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()); bridge .renderer .render(move |renderer| renderer.set_title(title.to_str().unwrap()).unwrap()); } #[no_mangle] pub extern "C" fn carbonyl_renderer_draw_text( bridge: RendererPtr, text: *const CText, text_size: size_t, ) { 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) }; ( str.to_str().unwrap().to_owned(), text.rect.origin.into(), text.rect.size.into(), text.color.into(), ) }) .collect::>(); 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) } }); } #[derive(Clone, Copy)] struct CallbackData(*const c_void); 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 { size: rect.size.into(), origin: rect.origin.into(), }, ); callback(callback_data.as_ptr()); }); } #[no_mangle] 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: {:?}", bridge.window.browser); 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) }; closure() } unsafe fn post_task(handle: extern "C" fn(extern "C" fn(*mut c_void), *mut c_void), run: F) where F: FnMut() + Send + 'static, { let closure: *mut Box = Box::into_raw(Box::new(Box::new(run))); handle(post_task_handler, closure as *mut c_void); } /// Function called by the C++ code to listen for input events. /// /// 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_renderer_listen(bridge: RendererPtr, delegate: *mut BrowserDelegate) { let bridge = unsafe { &*bridge }; let delegate = unsafe { *delegate }; use input::*; thread::spawn(move || { macro_rules! emit { ($event:ident($($args:expr),*) => $closure:expr) => {{ let run = move || { (delegate.$event)($($args),*); $closure }; unsafe { post_task(delegate.post_task, run) } }}; ($event:ident($($args:expr),*)) => {{ emit!($event($($args),*) => {}) }}; } 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(); // Setup single-use channel let (tx, rx) = mpsc::channel(); // Signal the browser to shutdown and notify our thread emit!(shutdown() => tx.send(()).unwrap()); rx.recv().unwrap(); // Shutdown rendering thread // if let Some(handle) = { bridge.lock().unwrap().renderer().stop() } { // handle.join().unwrap() // } }); } ================================================ FILE: src/browser/carbonyl.mojom ================================================ // Our C++ bindings will be in the carbonyl::mojom namespace module carbonyl.mojom; // Import existing bindings to common structures import "ui/gfx/geometry/mojom/geometry.mojom"; import "skia/public/mojom/skcolor.mojom"; // Define a structure to hold text to render struct TextData { // An UTF-8 string with the contents string contents; // Bounds, size only defined for clearing gfx.mojom.RectF bounds; // Color of the text skia.mojom.SkColor color; }; // The browser process runs this service interface CarbonylRenderService { // The renderer process calls this method DrawText(array data); }; ================================================ FILE: src/browser/export.h ================================================ #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_ ================================================ FILE: src/browser/host_display_client.cc ================================================ #include "carbonyl/src/browser/host_display_client.h" #include #include "components/viz/common/resources/resource_format.h" #include "components/viz/common/resources/resource_sizes.h" #include "mojo/public/cpp/system/platform_handle.h" #include "skia/ext/platform_canvas.h" #include "third_party/skia/include/core/SkColor.h" #include "third_party/skia/include/core/SkRect.h" #include "third_party/skia/src/core/SkDevice.h" #include "ui/gfx/skia_util.h" #if BUILDFLAG(IS_WIN) #include "skia/ext/skia_utils_win.h" #endif #include "carbonyl/src/browser/renderer.h" namespace carbonyl { LayeredWindowUpdater::LayeredWindowUpdater( mojo::PendingReceiver receiver ) : receiver_(this, std::move(receiver)), task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()) {} LayeredWindowUpdater::~LayeredWindowUpdater() = default; void LayeredWindowUpdater::OnAllocatedSharedMemory( const gfx::Size& pixel_size, base::UnsafeSharedMemoryRegion region) { if (region.IsValid()) shm_mapping_ = region.Map(); pixel_size_ = pixel_size; } void LayeredWindowUpdater::Draw(const gfx::Rect& damage_rect, DrawCallback callback) { Renderer::GetCurrent()->DrawBitmap( shm_mapping_.GetMemoryAs(), pixel_size_, damage_rect, base::BindOnce( []( scoped_refptr task_runner, DrawCallback callback ) { task_runner->PostTask(FROM_HERE, std::move(callback)); }, task_runner_, std::move(callback) ) ); } HostDisplayClient::HostDisplayClient() : viz::HostDisplayClient(gfx::kNullAcceleratedWidget) {} HostDisplayClient::~HostDisplayClient() = default; void HostDisplayClient::CreateLayeredWindowUpdater( mojo::PendingReceiver receiver) { layered_window_updater_ = std::make_unique(std::move(receiver)); } #if BUILDFLAG(IS_MAC) void HostDisplayClient::OnDisplayReceivedCALayerParams( const gfx::CALayerParams& ca_layer_params) {} #endif #if BUILDFLAG(IS_LINUX) && !BUILDFLAG(IS_CHROMEOS) void HostDisplayClient::DidCompleteSwapWithNewSize( const gfx::Size& size) {} #endif } // namespace carbonyl ================================================ FILE: src/browser/host_display_client.h ================================================ #ifndef CARBONYL_SRC_BROWSER_HOST_DISPLAY_CLIENT_H_ #define CARBONYL_SRC_BROWSER_HOST_DISPLAY_CLIENT_H_ #include #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" namespace carbonyl { typedef base::RepeatingCallback OnPaintCallback; class CARBONYL_VIZ_EXPORT LayeredWindowUpdater : public viz::mojom::LayeredWindowUpdater { public: explicit LayeredWindowUpdater( mojo::PendingReceiver receiver); ~LayeredWindowUpdater() override; // disable copy LayeredWindowUpdater(const LayeredWindowUpdater&) = delete; LayeredWindowUpdater& operator=(const LayeredWindowUpdater&) = delete; // viz::mojom::LayeredWindowUpdater implementation. void OnAllocatedSharedMemory(const gfx::Size& pixel_size, base::UnsafeSharedMemoryRegion region) override; void Draw(const gfx::Rect& damage_rect, DrawCallback draw_callback) override; private: mojo::Receiver receiver_; base::WritableSharedMemoryMapping shm_mapping_; gfx::Size pixel_size_; DrawCallback callback_; scoped_refptr task_runner_; base::WeakPtrFactory weak_ptr_factory_ { this }; }; class CARBONYL_VIZ_EXPORT HostDisplayClient : public viz::HostDisplayClient { public: explicit HostDisplayClient(); ~HostDisplayClient() override; // disable copy HostDisplayClient(const HostDisplayClient&) = delete; HostDisplayClient& operator=(const HostDisplayClient&) = delete; private: #if BUILDFLAG(IS_MAC) void OnDisplayReceivedCALayerParams( const gfx::CALayerParams& ca_layer_params) override; #endif void CreateLayeredWindowUpdater( mojo::PendingReceiver receiver) override; #if BUILDFLAG(IS_LINUX) && !BUILDFLAG(IS_CHROMEOS) void DidCompleteSwapWithNewSize(const gfx::Size& size) override; #endif std::unique_ptr layered_window_updater_; OnPaintCallback callback_; }; } // namespace carbonyl #endif // CARBONYL_SRC_BROWSER_HOST_DISPLAY_CLIENT_H_ ================================================ FILE: src/browser/render_service_impl.cc ================================================ #include "carbonyl/src/browser/render_service_impl.h" #include #include "carbonyl/src/browser/renderer.h" namespace carbonyl { CarbonylRenderServiceImpl::CarbonylRenderServiceImpl( mojo::PendingReceiver receiver): receiver_(this, std::move(receiver)) {} CarbonylRenderServiceImpl::~CarbonylRenderServiceImpl() = default; void CarbonylRenderServiceImpl::DrawText(std::vector data) { std::vector mapped; for (auto& text: data) { mapped.emplace_back(text->contents, text->bounds, text->color); } Renderer::GetCurrent()->DrawText(mapped); } } ================================================ FILE: src/browser/render_service_impl.h ================================================ #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 CARBONYL_RENDERER_EXPORT CarbonylRenderServiceImpl: public mojom::CarbonylRenderService { public: explicit CarbonylRenderServiceImpl(mojo::PendingReceiver receiver); CarbonylRenderServiceImpl(const CarbonylRenderServiceImpl&) = delete; CarbonylRenderServiceImpl& operator=(const CarbonylRenderServiceImpl&) = delete; ~CarbonylRenderServiceImpl() override; // carbonyl::mojom::CarbonylRenderService: void DrawText(std::vector data) override; private: mojo::Receiver receiver_; }; } // namespace carbonyl #endif // CARBONYL_SRC_BROWSER_RENDER_SERVICE_IMPL_H_ ================================================ FILE: src/browser/renderer.cc ================================================ #include "carbonyl/src/browser/renderer.h" #include #include #include #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 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( 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) { 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 callback ) { auto* box = new base::OnceCallback(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*>(box); std::move(*ptr).Run(); delete ptr; }, box ); } } ================================================ FILE: src/browser/renderer.h ================================================ #ifndef CARBONYL_SRC_BROWSER_RENDERER_H_ #define CARBONYL_SRC_BROWSER_RENDERER_H_ #include #include #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); void DrawBitmap( const unsigned char* pixels, const gfx::Size& size, const gfx::Rect& damage, base::OnceCallback callback ); private: Renderer(struct carbonyl_renderer* ptr); struct carbonyl_renderer* ptr_; }; } #endif // CARBONYL_SRC_BROWSER_RENDERER_H_ ================================================ FILE: src/browser.rs ================================================ mod bridge; pub use bridge::*; ================================================ FILE: src/cli/cli.rs ================================================ use std::{env, ffi::OsStr}; use super::CommandLineProgram; #[derive(Clone, Debug)] pub struct CommandLine { pub args: Vec, 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 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::>(); 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::().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, } } } ================================================ FILE: src/cli/program.rs ================================================ use super::CommandLine; #[derive(Clone, Debug)] pub enum CommandLineProgram { Main, Help, Version, } impl CommandLineProgram { pub fn parse_or_run() -> Option { 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 } } ================================================ FILE: src/cli/usage.txt ================================================ O O Carbonyl is a Chromium based browser for the terminal. \ / O —— Cr —— O In addition to the following options, / \ Carbonyl also supports most Chromium options. O O Usage: carbonyl [options] [url] Options: -f, --fps= set the maximum number of frames per second (default: 60) -z, --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 ================================================ FILE: src/cli.rs ================================================ mod cli; mod program; pub use cli::*; pub use program::*; ================================================ FILE: src/gfx/color.rs ================================================ use super::Vector3; use crate::impl_vector_overload; #[derive(Clone, Copy, Debug, PartialEq)] pub struct Color { pub r: T, pub g: T, pub b: T, } impl Color { pub fn from_iter<'a, T>(iter: &mut T) -> Option where T: Iterator, { let (b, g, r, _) = (iter.next(), iter.next(), iter.next(), iter.next()); Some(Color::::new(*r?, *g?, *b?)) } pub fn black() -> Color { Color::::new(0, 0, 0) } } impl_vector_overload!(Color r g b); ================================================ FILE: src/gfx/point.rs ================================================ use super::{Rect, Vector2}; use crate::impl_vector_overload; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Point { pub x: T, pub y: T, } impl Point { pub fn inside(&self, rect: Rect) -> bool { self.x >= rect.origin.x && self.y >= rect.origin.y && self.x < rect.origin.x + rect.size.width as i32 && self.y < rect.origin.y + rect.size.height as i32 } } impl_vector_overload!(Point x y); ================================================ FILE: src/gfx/rect.rs ================================================ use super::{Point, Size}; #[derive(Clone, Copy, Debug, PartialEq)] pub struct Rect { pub origin: Point

, pub size: Size, } impl Rect { pub fn new(x: P, y: P, width: S, height: S) -> Self { Self { origin: Point::new(x, y), size: Size::new(width, height), } } } ================================================ FILE: src/gfx/size.rs ================================================ use super::Vector2; use crate::impl_vector_overload; #[derive(Clone, Copy, Debug, PartialEq, Default)] pub struct Size { pub width: T, pub height: T, } impl_vector_overload!(Size width height); ================================================ FILE: src/gfx/vector.rs ================================================ pub trait Vector2 where T: Copy, { fn x(&self) -> T; fn y(&self) -> T; } pub trait Vector3 where T: Copy, { fn x(&self) -> T; fn y(&self) -> T; fn z(&self) -> T; } pub trait Cast { fn cast(self) -> T; } pub trait ToIntUnchecked { unsafe fn to_int_unchecked(self) -> T; } macro_rules! impl_cast_trait { () => { impl_cast_trait!(u8 as int); impl_cast_trait!(i8 as int); impl_cast_trait!(u16 as int); impl_cast_trait!(i16 as int); impl_cast_trait!(u32 as int); impl_cast_trait!(i32 as int); impl_cast_trait!(u64 as int); impl_cast_trait!(i64 as int); impl_cast_trait!(f32 as float); impl_cast_trait!(f64 as float); impl_cast_trait!(usize as int); impl_cast_trait!(isize as int); }; ($from:ty as int) => { impl_cast_trait!($from); impl ToIntUnchecked<$from> for f32 { unsafe fn to_int_unchecked(self) -> $from { f32::to_int_unchecked(self) } } impl ToIntUnchecked<$from> for f64 { unsafe fn to_int_unchecked(self) -> $from { f64::to_int_unchecked(self) } } }; ($from:ty as float) => { impl_cast_trait!($from); }; ($from:ty) => { impl_cast_trait!($from => u8); impl_cast_trait!($from => i8); impl_cast_trait!($from => u16); impl_cast_trait!($from => i16); impl_cast_trait!($from => u32); impl_cast_trait!($from => i32); impl_cast_trait!($from => u64); impl_cast_trait!($from => i64); impl_cast_trait!($from => f32); impl_cast_trait!($from => f64); impl_cast_trait!($from => usize); impl_cast_trait!($from => isize); }; ($from:ty => $to:ty) => { impl Cast<$to> for $from { fn cast(self) -> $to { self as $to } } }; } impl_cast_trait!(); #[macro_export] macro_rules! impl_vector_overload { ($struct:ident $x:ident $y:ident) => ( impl $struct { pub const fn new($x: T, $y: T) -> Self { Self { $x, $y } } pub const fn splat(value: T) -> Self { Self::new(value, value) } pub const fn to_array(&self) -> [T; 2] { [self.$x, self.$y] } pub fn iter(&self) -> std::array::IntoIter { self.to_array().into_iter() } pub fn reduce(&self, mut f: F) -> T where F: FnMut(T, T) -> T { f(self.$x, self.$y) } } impl Vector2 for $struct { fn x(&self) -> T { self.$x } fn y(&self) -> T { self.$y } } impl std::iter::FromIterator for $struct { fn from_iter(iter: I) -> Self where I: IntoIterator { let mut iter = iter.into_iter(); let expect = "initialized a vector with a small iter"; Self::new( iter.next().expect(expect), iter.next().expect(expect) ) } } impl From for $struct { fn from(value: T) -> Self { Self::new(value, value) } } impl From<$struct> for (T, T) { fn from(vector: $struct) -> Self { (vector.$x, vector.$y) } } impl From<(T, T)> for $struct { fn from((x, y): (T, T)) -> Self { Self::new(x, y) } } impl From<[T; 2]> for $struct { fn from(array: [T; 2]) -> Self { Self::new(array[0], array[1]) } } crate::impl_vector_traits!($struct Vector2); ); ($struct:ident $x:ident $y:ident $z:ident) => ( impl $struct { pub const fn new($x: T, $y: T, $z: T) -> Self { $struct { $x, $y, $z } } pub const fn splat(value: T) -> Self { Self::new(value, value, value) } pub const fn to_array(&self) -> [T; 3] { [self.$x, self.$y, self.$z] } pub fn iter(&self) -> std::array::IntoIter { self.to_array().into_iter() } pub fn reduce(&self, mut f: F) -> T where F: FnMut(T, T) -> T { let a = f(self.$x, self.$y); f(a, self.$z) } } impl Vector3 for $struct { fn x(&self) -> T { self.$x } fn y(&self) -> T { self.$y } fn z(&self) -> T { self.$z } } impl std::iter::FromIterator for $struct { fn from_iter(iter: I) -> Self where I: IntoIterator { let mut iter = iter.into_iter(); let expect = "initialized a vector with a small iter"; Self::new( iter.next().expect(expect), iter.next().expect(expect), iter.next().expect(expect) ) } } impl From for $struct { fn from(value: T) -> Self { Self::new(value, value, value) } } impl From<(T, T, T)> for $struct { fn from((x, y, z): (T, T, T)) -> Self { Self::new(x, y, z) } } impl From<$struct> for (T, T, T) { fn from(vector: $struct) -> Self { (vector.$x, vector.$y, vector.$z) } } impl From<[T; 3]> for $struct { fn from(array: [T; 3]) -> Self { Self::new(array[0], array[1], array[2]) } } crate::impl_vector_traits!($struct Vector3); ); } #[macro_export] macro_rules! impl_vector_traits { ($struct:ident $vector:ident) => { impl $struct { pub fn dot(&self, rhs: U) -> T where U: Into<$struct>, T: std::ops::Mul + std::iter::Sum, { (self * rhs.into()).sum() } pub fn sum(&self) -> T where T: std::iter::Sum, { self.iter().sum::() } pub fn cast(&self) -> $struct where T: super::Cast, U: Copy { self.map(|v| v.cast()) } pub fn map(&self, f: F) -> $struct where U: Copy, F: FnMut(T) -> U { self.iter().map(f).collect() } pub fn min_val(&self) -> T where T: Default + Ord { self.reduce(|a, b| a.min(b)) } pub fn max_val(&self) -> T where T: Default + Ord { self.reduce(|a, b| a.max(b)) } } crate::impl_vector_traits!($struct $vector i8); crate::impl_vector_traits!($struct $vector u8); crate::impl_vector_traits!($struct $vector i16); crate::impl_vector_traits!($struct $vector u16); crate::impl_vector_traits!($struct $vector i32); crate::impl_vector_traits!($struct $vector u32); crate::impl_vector_traits!($struct $vector i64); crate::impl_vector_traits!($struct $vector u64); crate::impl_vector_traits!($struct $vector isize); crate::impl_vector_traits!($struct $vector usize); crate::impl_vector_traits!($struct $vector f32 float); crate::impl_vector_traits!($struct $vector f64 float); crate::impl_vector_traits!($struct $vector Add add); crate::impl_vector_traits!($struct $vector Sub sub); crate::impl_vector_traits!($struct $vector Mul mul); crate::impl_vector_traits!($struct $vector Div div); crate::impl_vector_traits!($struct $vector BitOr bitor); crate::impl_vector_traits!($struct $vector BitXor bitxor); crate::impl_vector_traits!($struct $vector BitAnd bitand); }; ($struct:ident $vector:ident $type:ident) => ( impl $struct<$type> { pub fn avg_with(&self, rhs: T) -> Self where T: Into { let rhs = rhs.into(); (self & rhs) + (self ^ rhs) / 2 } } ); ($struct:ident $vector:ident $type:ident float) => ( impl $struct<$type> { pub unsafe fn to_int_unchecked(&self) -> $struct where $type: super::ToIntUnchecked, U: Copy { self.map(|x| <$type as super::ToIntUnchecked>::to_int_unchecked(x)) } pub fn mul_add(&self, mul: M, add: A) -> Self where M: Into, A: Into, { self.iter() .zip(mul.into().iter()) .zip(add.into().iter()) .map(|((x, y), z)| x.mul_add(y, z)) .collect() } pub fn round(&self) -> Self { 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(&self, min: U) -> Self where U: Into { self.iter() .zip(min.into().iter()) .map(|(x, y)| x.min(y)) .collect() } pub fn max(&self, max: U) -> Self where U: Into { self.iter() .zip(max.into().iter()) .map(|(x, y)| x.max(y)) .collect() } pub fn clamp(&self, min: U, max: U) -> Self where U: Into { self.iter() .zip(min.into().iter()) .zip(max.into().iter()) .map(|((x, y), z)| x.clamp(y, z)) .collect() } } ); ($struct:ident $vector:ident $trait:ident $name:ident) => { impl $struct { pub fn $name(&self, rhs: U) -> Self where T: std::ops::$trait, U: Copy + Into { self.iter() .zip(rhs.into().iter()) .map(|(x, y)| x.$name(y)) .collect() } } impl std::ops::$trait for $struct where T: Copy + std::ops::$trait, U: Copy + Into<$struct>, { type Output = $struct; fn $name(self, rhs: U) -> Self::Output { $struct::$name(&self, rhs) } } impl<'a, T, U> std::ops::$trait for &'a $struct where T: Copy + std::ops::$trait, U: Copy + Into<$struct>, { type Output = $struct; fn $name(self, rhs: U) -> Self::Output { $struct::$name(&self, rhs) } } }; } ================================================ FILE: src/gfx.rs ================================================ mod color; mod point; mod rect; mod size; mod vector; pub use color::*; pub use point::*; pub use rect::*; pub use size::*; pub use vector::*; ================================================ FILE: src/input/dcs/control_flow.rs ================================================ #[macro_export] macro_rules! control_flow { (break) => { std::ops::ControlFlow::Break(None) }; ($expr:expr; break) => {{ $expr; std::ops::ControlFlow::Break(None) }}; (break $expr:expr) => { std::ops::ControlFlow::Break($expr.into()) }; (continue) => { std::ops::ControlFlow::Continue(None) }; ($expr:expr; continue) => {{ $expr; std::ops::ControlFlow::Continue(None) }}; (continue $expr:expr) => { std::ops::ControlFlow::Continue($expr.into()) }; } ================================================ FILE: src/input/dcs/parser.rs ================================================ use crate::{control_flow, input::ParseControlFlow}; use super::{resource::*, status::*}; #[derive(Default, Clone)] enum Sequence { #[default] Code, Type(u8), Status(StatusParser), Resource(ResourceParser), } #[derive(Default, Clone)] pub struct DeviceControl { sequence: Sequence, } impl DeviceControl { pub fn new() -> Self { Self::default() } pub fn parse(&mut self, key: u8) -> ParseControlFlow { use Sequence::*; self.sequence = match self.sequence { Code => match key { b'0' | b'1' => Type(key), _ => control_flow!(break)?, }, Type(code) => match key { b'$' => Status(StatusParser::new(code)), b'+' => Resource(ResourceParser::new(code)), _ => control_flow!(break)?, }, Status(ref mut status) => return status.parse(key), Resource(ref mut resource) => return resource.parse(key), }; control_flow!(continue) } } ================================================ FILE: src/input/dcs/resource.rs ================================================ use crate::{ control_flow, input::{Event, ParseControlFlow, TerminalEvent}, }; #[derive(Copy, Clone)] enum Sequence { Start, Name, Value, Terminator, } #[derive(Clone)] pub struct ResourceParser { code: u8, sequence: Sequence, name: Vec, value: Vec, } impl ResourceParser { pub fn new(code: u8) -> Self { Self { code, sequence: Sequence::Start, name: Vec::new(), value: Vec::new(), } } pub fn parse(&mut self, key: u8) -> ParseControlFlow { use Sequence::*; self.sequence = match self.sequence { Start => match key { b'r' => Name, _ => control_flow!(break)?, }, Name => match key { 0x1b => Terminator, b'=' => Value, key => self.push_char(key), }, Value => match key { 0x1b => Terminator, key => self.push_char(key), }, Terminator => control_flow!(break self.parse_event(key))?, }; control_flow!(continue) } fn push_char(&mut self, key: u8) -> Sequence { match self.sequence { Sequence::Name => self.name.push(key), Sequence::Value => self.value.push(key), _ => (), } self.sequence } fn parse_event(&self, key: u8) -> Option { if key == b'\\' && self.code == b'1' { let name = read_hex_string(self.name.as_slice()); let value = read_hex_string(self.value.as_slice()); if let (Some(name), Some(value)) = (name, value) { if name == "TN" { return Some(Event::Terminal(TerminalEvent::Name(value))); } } } None } } fn read_hex_string(str: &[u8]) -> Option { let mut iter = str.into_iter(); let mut vec = Vec::with_capacity(str.len() / 2); loop { match (iter.next(), iter.next()) { (Some(left), Some(right)) => { let chunk = [*left, *right]; let hex = std::str::from_utf8(&chunk).ok()?; vec.push(u8::from_str_radix(hex, 16).ok()?) } _ => break, } } Some(std::str::from_utf8(&vec).ok()?.to_owned()) } ================================================ FILE: src/input/dcs/status.rs ================================================ use crate::{ control_flow, input::{Event, ParseControlFlow, TerminalEvent}, }; #[derive(Default, Clone)] enum Sequence { #[default] Start, Value, Terminator, } #[derive(Default, Clone)] pub struct StatusParser { code: u8, op: Option, sequence: Sequence, buffer: Vec, values: Vec, } impl StatusParser { pub fn new(code: u8) -> Self { let mut parser = Self::default(); parser.code = code; parser } pub fn parse(&mut self, key: u8) -> ParseControlFlow { use Sequence::*; self.sequence = match self.sequence { Start => match key { b'r' => Value, _ => control_flow!(break)?, }, Value => match key { 0x1b => self.terminate(), b';' => self.push_value(), char => self.push_char(char), }, Terminator => control_flow!(break self.parse_event(key))?, }; control_flow!(continue) } fn terminate(&mut self) -> Sequence { self.op = self.buffer.pop(); self.push_value(); Sequence::Terminator } fn push_char(&mut self, key: u8) -> Sequence { self.buffer.push(key); Sequence::Value } fn push_value(&mut self) -> Sequence { if let Ok(str) = String::from_utf8(std::mem::take(&mut self.buffer)) { self.values.push(str); } Sequence::Value } fn parse_event(&self, key: u8) -> Option { if key == b'\\' && self.code == b'1' && self.op == Some(b'm') { for value in &self.values { let mut val = 0; let mut set = Vec::new(); for &char in value.as_bytes() { match char { b'0'..=b'9' => val = val * 10 + char - b'0', b':' => set.push(std::mem::take(&mut val)), _ => break, } } set.push(val); if set.len() > 4 && set[1] == 2 && (set[0] == 38 || set[0] == 48) { return Some(Event::Terminal(TerminalEvent::TrueColorSupported)); } } } None } } ================================================ FILE: src/input/dcs.rs ================================================ mod control_flow; mod parser; mod resource; mod status; pub use parser::*; ================================================ FILE: src/input/keyboard.rs ================================================ use crate::control_flow; use super::{Event, ParseControlFlow}; pub struct Keyboard { state: State, } #[derive(Clone, Debug)] pub struct Key { pub char: u8, pub modifiers: KeyModifiers, } #[derive(Clone, Debug, Default)] pub struct KeyModifiers { pub alt: bool, pub meta: bool, pub shift: bool, pub control: bool, } enum State { Separator, Modifier(u8), } impl Keyboard { pub fn new() -> Self { Self { state: State::Separator, } } pub fn key(key: u8, modifiers: u8) -> Option { let modifiers = KeyModifiers::parse(modifiers); let char = match key { // Up b'A' => 0x11, // Down b'B' => 0x12, // Right b'C' => 0x13, // Left b'D' => 0x14, _ => return None, }; Some(Event::KeyPress { key: Key { char, modifiers }, }) } pub fn parse(&mut self, key: u8) -> ParseControlFlow { self.state = match self.state { State::Separator => match key { b';' => State::Modifier(0), _ => control_flow!(break)?, }, State::Modifier(code) => match key { b'0'..=b'9' => State::Modifier(code * 10 + key - b'0'), key => control_flow!(break Self::key(key, code))?, }, }; control_flow!(continue) } } impl From for Key { fn from(char: u8) -> Self { Self { char, modifiers: KeyModifiers::default(), } } } impl KeyModifiers { pub fn parse(key: u8) -> Self { let (alt, meta, shift, control) = (0b1000, 0b0100, 0b0010, 0b0001); let mask = match key { 2 => shift, 3 => alt, 4 => shift | alt, 5 => control, 6 => shift | control, 7 => alt | control, 8 => shift | alt | control, 9 => meta, 10 => meta | shift, 11 => meta | alt, 12 => meta | alt | shift, 13 => meta | control, 14 => meta | control | shift, 15 => meta | control | alt, 16 => meta | control | alt | shift, _ => 0, }; KeyModifiers { alt: alt & mask != 0, meta: meta & mask != 0, shift: shift & mask != 0, control: control & mask != 0, } } } ================================================ FILE: src/input/listen.rs ================================================ use std::io::{self, Read}; use crate::input::*; /// Listen for input events in stdin. /// This will block, so it should run from a dedicated thread. pub fn listen(mut callback: F) -> io::Result<()> where F: FnMut(Vec), { let mut buf = [0u8; 1024]; let mut stdin = io::stdin(); let mut parser = Parser::new(); 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()); for event in read { match event { Event::Exit => return Ok(()), Event::Scroll { delta } => scroll += delta, event => events.push(event), } } if scroll != 0 { events.push(Event::Scroll { delta: scroll }) } callback(events) } } ================================================ FILE: src/input/mouse.rs ================================================ use std::ops::BitAnd; use crate::{control_flow, utils::log}; use super::{Event, ParseControlFlow}; #[derive(Default, Clone, Debug)] pub struct Mouse { buf: Vec, btn: Option, col: Option, row: Option, } impl Mouse { pub fn new() -> Self { Self::default() } pub fn parse(&mut self, key: u8) -> ParseControlFlow { match key { b'm' | b'M' => control_flow!(break self.get(key)), b';' => match self.read() { None => control_flow!(break), Some(()) => control_flow!(continue), }, key => control_flow!(self.buf.push(key); continue), } } fn read(&mut self) -> Option<()> { let buf = std::mem::take(&mut self.buf); let str = std::str::from_utf8(&buf).ok()?; let num = Some(str.parse().ok()?); match (self.btn, self.col, self.row) { (None, _, _) => self.btn = num, (_, None, _) => self.col = num, (_, _, None) => self.row = num, _ => { log::warning!("Malformed mouse sequence"); return None; } } return Some(()); } fn get(&mut self, key: u8) -> Option { let (btn, col, row) = { self.read()?; (self.btn?, self.col?, self.row?) }; Some({ if Mask::ScrollDown & btn { Event::Scroll { delta: -1 } } else if Mask::ScrollUp & btn { Event::Scroll { delta: 1 } } else { let col = col as usize - 1; let row = row as usize - 1; if key == b'm' { Event::MouseUp { row, col } } else if Mask::MouseMove & btn { Event::MouseMove { row, col } } else { Event::MouseDown { row, col } } } }) } } enum Mask { MouseMove = 0x20, ScrollUp = 0x40, ScrollDown = 0x41, } impl BitAnd for Mask { type Output = bool; fn bitand(self, rhs: u32) -> bool { let mask = self as u32; mask & rhs == mask } } ================================================ FILE: src/input/parser.rs ================================================ use std::ops::ControlFlow; use crate::input::*; #[derive(Default)] pub struct Parser { events: Vec, sequence: Sequence, } #[derive(Default)] enum Sequence { #[default] Char, Escape, Control, Mouse(Mouse), Keyboard(Keyboard), DeviceControl(DeviceControl), } #[derive(Clone, Debug)] pub enum TerminalEvent { Name(String), TrueColorSupported, } #[derive(Clone, Debug)] pub enum Event { KeyPress { key: Key }, MouseUp { row: usize, col: usize }, MouseDown { row: usize, col: usize }, MouseMove { row: usize, col: usize }, Scroll { delta: isize }, Terminal(TerminalEvent), Exit, } pub type ParseControlFlow = ControlFlow, Option>; impl Parser { pub fn new() -> Parser { Self::default() } pub fn parse(&mut self, input: &[u8]) -> Vec { let mut sequence = std::mem::take(&mut self.sequence); macro_rules! emit { ($event:expr) => {{ if let Some(event) = $event.into() { self.events.push(event); } Sequence::Char }}; ($event:expr; continue) => {{ if let Some(event) = $event.into() { self.events.push(event); } continue; }}; } macro_rules! parse { ($parser:expr, $key:expr) => ( match $parser.parse($key) { ControlFlow::Break(None) => Sequence::Char, ControlFlow::Break(Some(event)) => emit!(event), ControlFlow::Continue(None) => continue, ControlFlow::Continue(Some(event)) => emit!(event; continue), } ); } for &key in input { sequence = match sequence { Sequence::Char => match key { 0x1b => Sequence::Escape, 0x03 => emit!(Event::Exit), key => emit!(Event::KeyPress { key: key.into() }), }, Sequence::Escape => match key { b'[' => Sequence::Control, b'P' => Sequence::DeviceControl(DeviceControl::new()), 0x1b => emit!(Event::KeyPress { key: 0x1b.into() }; continue), key => { emit!(Event::KeyPress { key: 0x1b.into() }); emit!(Event::KeyPress { key: key.into() }) } }, Sequence::Control => match key { b'<' => Sequence::Mouse(Mouse::new()), b'1' => Sequence::Keyboard(Keyboard::new()), key => emit!(Keyboard::key(key, 0)), }, Sequence::Mouse(ref mut mouse) => parse!(mouse, key), Sequence::Keyboard(ref mut keyboard) => parse!(keyboard, key), Sequence::DeviceControl(ref mut dcs) => parse!(dcs, key), } } self.sequence = sequence; std::mem::take(&mut self.events) } } ================================================ FILE: src/input/tty.rs ================================================ use std::fs::File; use std::io; use std::io::Write; use std::mem::MaybeUninit; use std::os::fd::RawFd; use std::os::unix::prelude::AsRawFd; use crate::utils::log; pub struct Terminal { settings: Option, alt_screen: bool, } impl Drop for Terminal { fn drop(&mut self) { self.teardown() } } impl Terminal { /// Setup the input stream to operate in raw mode. /// Returns an object that'll revert terminal settings. pub fn setup() -> Self { Self { settings: match TerminalSettings::open_raw() { Ok(settings) => Some(settings), Err(error) => { log::error!("Failed to setup terminal: {error}"); None } }, alt_screen: if let Err(error) = TTY::enter_alt_screen() { log::error!("Failed to enter alternative screen: {error}"); false } else { true }, } } pub fn teardown(&mut self) { if let Some(ref settings) = self.settings { if let Err(error) = settings.apply() { log::error!("Failed to revert terminal settings: {error}"); } self.settings = None; } if self.alt_screen { if let Err(error) = TTY::quit_alt_screen() { log::error!("Failed to quit alternative screen: {error}"); } self.alt_screen = false; } } } enum TTY { Raw(RawFd), File(File), } const SEQUENCES: [(u32, bool); 4] = [(1049, true), (1003, true), (1006, true), (25, false)]; impl TTY { fn stdin() -> TTY { let isatty = unsafe { libc::isatty(libc::STDIN_FILENO) }; if isatty != 1 { if let Ok(file) = File::open("/dev/tty") { return TTY::File(file); } } TTY::Raw(libc::STDIN_FILENO) } fn enter_alt_screen() -> io::Result<()> { let mut out = io::stdout(); for (sequence, enable) in SEQUENCES { write!(out, "\x1b[?{}{}", sequence, if enable { "h" } else { "l" })?; } // Set the current foreground color to black write!(out, "\x1b[48;2;0;0;0m")?; // Query current foreground color to for true-color support detection write!(out, "\x1bP$qm\x1b\\")?; // Query current terminal name write!(out, "\x1bP+q544e\x1b\\")?; out.flush() } fn quit_alt_screen() -> io::Result<()> { let mut out = io::stdout(); for (sequence, enable) in SEQUENCES { write!(out, "\x1b[?{}{}", sequence, if enable { "l" } else { "h" })?; } out.flush() } fn as_raw_fd(self) -> RawFd { match self { TTY::Raw(fd) => fd, TTY::File(file) => file.as_raw_fd(), } } } trait ToErr { fn to_err(self) -> io::Result<()>; } impl ToErr for libc::c_int { fn to_err(self) -> io::Result<()> { if self == 0 { Ok(()) } else { Err(io::Error::last_os_error()) } } } /// Safe wrapper around libc::termios #[derive(Clone)] struct TerminalSettings { data: libc::termios, } impl TerminalSettings { /// Fetch settings from the current TTY fn open() -> io::Result { let tty = TTY::stdin(); let mut term = MaybeUninit::uninit(); let data = unsafe { libc::tcgetattr(tty.as_raw_fd(), term.as_mut_ptr()).to_err()?; term.assume_init() }; Ok(Self { data }) } fn open_raw() -> io::Result { let mut raw = Self::open()?; let settings = raw.clone(); raw.make_raw(); raw.apply()?; Ok(settings) } /// Enable raw input fn make_raw(&mut self) { let c_oflag = self.data.c_oflag; // Set the terminal to raw mode unsafe { libc::cfmakeraw(&mut self.data) } // Restore output flags, ensures carriage returns are consistent self.data.c_oflag = c_oflag; } /// Apply the settings to the current TTY fn apply(&self) -> io::Result<()> { let tty = TTY::stdin(); unsafe { libc::tcsetattr(tty.as_raw_fd(), libc::TCSANOW, &self.data).to_err() } } } ================================================ FILE: src/input.rs ================================================ mod dcs; mod keyboard; mod listen; mod mouse; mod parser; mod tty; pub use dcs::*; pub use keyboard::*; pub use listen::*; pub use mouse::*; pub use parser::*; pub use tty::*; ================================================ FILE: src/lib.rs ================================================ pub mod browser; pub mod cli; pub mod gfx; pub mod input; pub mod output; pub mod ui; mod utils; ================================================ FILE: src/output/cell.rs ================================================ use std::rc::Rc; use crate::gfx::{Color, Point}; #[derive(Clone, PartialEq)] pub struct Grapheme { /// Unicode character in UTF-8, might contain multiple code points (Emoji, CJK). pub char: String, pub index: usize, pub width: usize, pub color: Color, } /// Terminal cell with `height = width * 2` #[derive(PartialEq)] pub struct Cell { pub cursor: Point, /// Text grapheme if any pub grapheme: Option>, pub quadrant: (Color, Color, Color, Color), } impl Cell { pub fn new(x: u32, y: u32) -> Cell { Cell { cursor: Point::new(x, y), grapheme: None, quadrant: ( Color::black(), Color::black(), Color::black(), Color::black(), ), } } } ================================================ FILE: src/output/frame_sync.rs ================================================ use std::time::{Duration, Instant}; /// A utility to synchronize rendering with a given FPS pub struct FrameSync { render_start: Option, 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, } } } ================================================ FILE: src/output/kd_tree.rs ================================================ use std::ops::Mul; use crate::gfx::Color; struct KDNode { left: Option>, right: Option>, normal: Color, middle: (usize, Color), } impl KDNode { fn new(colors: &[Color]) { let (sum, sum_squared) = colors.iter().fold( (Color::black(), Color::black()), |(sum, sum_squared), color| (sum + color, sum_squared + color * color), ); } fn nearest(&self, color: Color, mut limit: f64) -> Option<(usize, f64)> { let diff = color - self.middle.1; let distance = diff.mul(&diff).sum().sqrt(); let mut result = None; if distance < limit { limit = distance; } let dot = diff.mul(self.normal).sum(); if dot <= 0.0 { if let Some(ref left) = self.left { if let Some(nearest) = left.nearest(color, limit) { limit = nearest.1; result = Some(nearest); } } if -dot < limit { if let Some(ref right) = self.right { if let Some(nearest) = right.nearest(color, limit) { result = Some(nearest); } } } } else { if let Some(ref right) = self.right { if let Some(nearest) = right.nearest(color, limit) { limit = nearest.1; result = Some(nearest); } } if dot < limit { if let Some(ref left) = self.left { if let Some(nearest) = left.nearest(color, limit) { result = Some(nearest); } } } } result } } ================================================ FILE: src/output/painter.rs ================================================ use std::io::{self, Stdout, Write}; use crate::gfx::{Color, Point}; use super::{binarize_quandrant, Cell}; pub struct Painter { output: Stdout, buffer: Vec, cursor: Option>, true_color: bool, background: Option, foreground: Option, background_code: Option, foreground_code: Option, } impl Painter { pub fn new() -> Painter { Painter { buffer: Vec::new(), cursor: None, output: io::stdout(), background: None, foreground: None, background_code: None, foreground_code: None, true_color: match std::env::var("COLORTERM").unwrap_or_default().as_str() { "truecolor" | "24bit" => true, _ => false, }, } } pub fn true_color(&self) -> bool { self.true_color } pub fn set_true_color(&mut self, true_color: bool) { self.true_color = true_color } pub fn begin(&mut self) -> io::Result<()> { write!(self.buffer, "\x1b[?25l\x1b[?12l") } pub fn end(&mut self, cursor: Option) -> io::Result<()> { if let Some(cursor) = cursor { write!( self.buffer, "\x1b[{};{}H\x1b[?25h\x1b[?12h", cursor.y + 1, cursor.x + 1 )?; } self.output.write(self.buffer.as_slice())?; self.output.flush()?; self.buffer.clear(); self.cursor = None; Ok(()) } pub fn paint(&mut self, cell: &Cell) -> io::Result<()> { let &Cell { cursor, quadrant, ref grapheme, } = cell; let (char, background, foreground, width) = if let Some(grapheme) = grapheme { if grapheme.index > 0 { return Ok(()); } ( grapheme.char.as_str(), quadrant .0 .avg_with(quadrant.1) .avg_with(quadrant.2) .avg_with(quadrant.3), grapheme.color, grapheme.width as u32, ) } else { let (char, background, foreground) = binarize_quandrant(quadrant); (char, background, foreground, 1) }; if self.cursor != Some(cursor) { write!(self.buffer, "\x1b[{};{}H", cursor.y + 1, cursor.x + 1)?; }; self.cursor = Some(cursor + Point::new(width, 0)); if self.background != Some(background) { self.background = Some(background); if self.true_color { write!( self.buffer, "\x1b[48;2;{};{};{}m", background.r, background.g, background.b, )? } else { let code = background.to_xterm(); if self.background_code != Some(code) { self.background_code = Some(code); write!(self.buffer, "\x1b[48;5;{code}m")? } } } if self.foreground != Some(foreground) { self.foreground = Some(foreground); if self.true_color { write!( self.buffer, "\x1b[38;2;{};{};{}m", foreground.r, foreground.g, foreground.b, )? } else { let code = foreground.to_xterm(); if self.foreground_code != Some(code) { self.foreground_code = Some(code); write!(self.buffer, "\x1b[38;5;{code}m")? } } } self.buffer.write_all(char.as_bytes())?; Ok(()) } } ================================================ FILE: src/output/quad.rs ================================================ 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 = 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)), } } ================================================ FILE: src/output/quantizer.rs ================================================ use crate::gfx::Color; #[derive(Clone, Copy)] enum Channel { R, G, B, } const COLOR_BUCKETS: usize = 8; const COLORS: usize = 2_usize.pow(COLOR_BUCKETS as u32); /// Find the closest color to `color` on `palette` using a binary search pub fn palette_color(palette: &[Color; COLORS], color: Color) { let mut size = palette.len() / 2; let mut iter = palette.iter(); let mut prev = iter.next(); } fn distance(a: Color, b: Color) {} pub fn quantize(pixels: &[u8]) -> [Color; COLORS] { let mut min = Color::black(); let mut max = Color::black(); let mut bucket = Vec::::new(); let mut pixels_iter = pixels.iter(); let mut bucket_iter = bucket.iter_mut(); // Step 1: find the dominant channel loop { match ( bucket_iter.next(), pixels_iter.next(), pixels_iter.next(), pixels_iter.next(), pixels_iter.next(), ) { (Some(color), Some(r), Some(g), Some(b), Some(_)) => { // Save the color in a bucket color.set_r(*r); color.set_g(*g); color.set_b(*b); min.set_r(min.r().min(color.r())); min.set_g(min.g().min(color.g())); min.set_b(min.b().min(color.b())); max.set_r(max.r().max(color.r())); max.set_g(max.g().max(color.g())); max.set_b(max.b().max(color.b())); } _ => break, } } let ranges = [ (Channel::R, max.r() - min.r()), (Channel::G, max.g() - min.g()), (Channel::B, max.b() - min.b()), ]; let (channel, _) = ranges .iter() .reduce(|a, b| if a.1 > b.1 { a } else { b }) .unwrap(); // Step 2: perform median-cut for i in 1..=COLOR_BUCKETS { let buckets = 2_usize.pow(i as u32); let size = bucket.len() / buckets; for j in 0..buckets { let start = j * size; let end = start + size; let slice = &mut bucket[start..end]; slice.sort_unstable_by(match channel { Channel::R => |a: &Color, b: &Color| a.r().cmp(&b.r()), Channel::G => |a: &Color, b: &Color| a.g().cmp(&b.g()), Channel::B => |a: &Color, b: &Color| a.b().cmp(&b.b()), }); } } // Step 3: get the average color in each bucket let mut palette = [Color::black(); COLORS]; let size = bucket.len() / palette.len(); for (i, color) in palette.iter_mut().enumerate() { let start = i * size; let end = start + size; let slice = &bucket[start..end]; let mut sum = None; for color in slice.into_iter() { sum = Some(match sum { None => color.cast(), Some(sum) => color.cast() + sum, }) } if let Some(sum) = sum { let avg = sum / size as u32; color.set_r(avg.r() as u8); color.set_g(avg.g() as u8); color.set_b(avg.b() as u8); } } palette } ================================================ FILE: src/output/render_thread.rs ================================================ 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, JoinHandle<()>)>, enabled: bool, } type RenderClosure = Box; 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> { self.enabled = false; self.send(Message::Shutdown); let (_, handle) = self.thread.take()?; Some(handle) } /// Run a closure on the rendering thread. pub fn render(&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) { let cmd = CommandLine::parse(); let mut sync = FrameSync::new(cmd.fps); let mut renderer = Renderer::new(); let mut needs_render = false; loop { // Get a deadline for the next frame let deadline = sync.deadline(); let mut wait = true; loop { let message = if wait { // On the first iteration, we want to block indefinitely // until we get a message, after which we schedule a render. wait = false; rx.recv().ok() } else { // On subsequence iterations, we want to process a maximum // number of events until the deadline for the next frame. rx.recv_timeout(deadline - Instant::now()).ok() }; match message { // 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)))); } } } ================================================ FILE: src/output/renderer.rs ================================================ use std::{ io::{self, Write}, rc::Rc, }; use unicode_segmentation::UnicodeSegmentation; use unicode_width::UnicodeWidthStr; use crate::{ gfx::{Color, Point, Rect, Size}, input::Key, ui::navigation::{Navigation, NavigationAction}, utils::log, }; use super::{Cell, Grapheme, Painter}; pub struct Renderer { nav: Navigation, cells: Vec<(Cell, Cell)>, painter: Painter, size: Size, } impl Renderer { pub fn new() -> Renderer { Renderer { nav: Navigation::new(), cells: Vec::with_capacity(0), painter: Painter::new(), size: Size::new(0, 0), } } pub fn enable_true_color(&mut self) { self.painter.set_true_color(true) } pub fn keypress(&mut self, key: &Key) -> io::Result { let action = self.nav.keypress(key); Ok(action) } pub fn mouse_up(&mut self, origin: Point) -> io::Result { let action = self.nav.mouse_up(origin); Ok(action) } pub fn mouse_down(&mut self, origin: Point) -> io::Result { let action = self.nav.mouse_down(origin); Ok(action) } pub fn mouse_move(&mut self, origin: Point) -> io::Result { let action = self.nav.mouse_move(origin); Ok(action) } pub fn push_nav(&mut self, url: &str, can_go_back: bool, can_go_forward: bool) { self.nav.push(url, can_go_back, can_go_forward) } pub fn get_size(&self) -> Size { self.size } 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 = size.width - 1; let cells = (size.width + size.width * size.height) as usize; self.cells.clear(); self.cells.resize_with(cells, || { let cell = (Cell::new(x, y), Cell::new(x, y)); if x < bound { x += 1; } else { x = 0; y += 1; } cell }); } pub fn render(&mut self) -> io::Result<()> { 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 * (2, 1), Size::splat(0), element.foreground, ); } self.painter.begin()?; for (previous, current) in self.cells.iter_mut() { if current == previous { continue; } previous.quadrant = current.quadrant; previous.grapheme = current.grapheme.clone(); self.painter.paint(current)?; } self.painter.end(self.nav.cursor())?; Ok(()) } /// Draw the background from a pixel array encoded in RGBA8888 pub fn draw_background(&mut self, pixels: &[u8], pixels_size: Size, rect: Rect) { let viewport = self.size.cast::(); 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 origin = rect.origin.cast::().max(0.0) / (2.0, 4.0); let size = rect.size.cast::().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)); 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); 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; } } } pub fn clear_text(&mut self) { for (_, cell) in self.cells.iter_mut() { cell.grapheme = None } } pub fn set_title(&self, title: &str) -> io::Result<()> { let mut stdout = io::stdout(); write!(stdout, "\x1b]0;{title}\x07")?; write!(stdout, "\x1b]1;{title}\x07")?; write!(stdout, "\x1b]2;{title}\x07")?; stdout.flush() } pub fn fill_rect(&mut self, rect: Rect, color: Color) { self.draw(rect, |cell| { cell.grapheme = None; cell.quadrant = (color, color, color, color); }) } pub fn draw(&mut self, bounds: Rect, mut draw: F) where F: FnMut(&mut Cell), { let origin = bounds.origin.cast::(); let size = bounds.size.cast::(); let viewport_width = self.size.width as usize; let top = origin.y; let bottom = top + size.height; // Iterate over each row for y in top..bottom { let left = y * viewport_width + origin.x; let right = left + size.width; for (_, current) in self.cells[left..right].iter_mut() { draw(current) } } } /// Render some text into the terminal output 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.size.cast::(); if size.width > 2 && size.height > 2 { let origin = (origin.cast::() / (2.0, 4.0) + (0.0, 1.0)).round(); let size = (size.cast::() / (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 = y * viewport.width; let start = index + left; let end = index + right; 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 / 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 for grapheme in UnicodeSegmentation::graphemes(string, true) { let width = grapheme.width(); for index in 0..width { // Get the next terminal cell at the given position match iter.next() { // Stop if we're at the end of the buffer None => return, // Set the cell to the current grapheme Some((_, cell)) => { let next = Grapheme { // Create a new shared reference to the text color, index, width, // Export the set of unicode code points for this graphene into an UTF-8 string char: grapheme.to_string(), }; if match cell.grapheme { None => true, Some(ref previous) => { previous.color != next.color || previous.char != next.char } } { cell.grapheme = Some(Rc::new(next)) } } } } } } } } ================================================ FILE: src/output/window.rs ================================================ 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, /// 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::::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::().mul(self.scale).ceil().cast(); self } } fn parse_var(var: &str) -> Option { std::env::var(var).ok()?.parse().ok() } ================================================ FILE: src/output/xterm.rs ================================================ use crate::gfx::Color; impl Color { pub fn to_xterm(&self) -> u8 { if self.max_val() - self.min_val() < 8 { match self.r { 0..=4 => 16, 5..=8 => 232, 238..=246 => 255, 247..=255 => 231, r => 232 + (r - 8 + 5) / 10, } } else { let scale = 5.0 / 200.0; (16.0 + self .cast::() .mul_add(scale, scale * -55.0) .max(0.0) .round() .dot((36.0, 6.0, 1.0))) as u8 } } } ================================================ FILE: src/output.rs ================================================ // mod kd_tree; // mod quantizer; mod cell; mod frame_sync; mod painter; mod quad; mod render_thread; mod renderer; 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 window::*; ================================================ FILE: src/ui/navigation.rs ================================================ use std::env; use unicode_width::UnicodeWidthStr; use crate::{ gfx::{Color, Point, Size}, input::Key, utils::log, }; pub enum NavigationAction { Ignore, Forward, GoTo(String), GoBack(), GoForward(), Refresh(), } #[derive(Debug)] pub struct NavigationElement { pub text: String, pub background: Color, pub foreground: Color, } pub struct Navigation { url: Option, size: Size, cursor: Option, can_go_back: bool, can_go_forward: bool, } impl Navigation { pub fn new() -> Self { Self { url: None, size: (0, 0).into(), cursor: None, can_go_back: false, can_go_forward: false, } } pub fn cursor(&self) -> Option { Some((11 + self.cursor? as i32, 0).into()) } pub fn keypress(&mut self, key: &Key) -> NavigationAction { let modifier_key = match env::consts::OS { "macos" => key.modifiers.meta, _ => key.modifiers.alt, }; match self.cursor { None => match (modifier_key, key.char) { (true, 0x14) => NavigationAction::GoBack(), (true, 0x13) => NavigationAction::GoForward(), _ => NavigationAction::Forward, }, Some(cursor) => { if let Some(url) = &mut self.url { // TODO: Unicode match key.char { // Return 0x0d => return NavigationAction::GoTo(url.clone()), // Up 0x11 => self.cursor = Some(0), // Down 0x12 => self.cursor = Some(url.width()), // Right 0x13 => self.cursor = Some((cursor + 1).min(url.width())), // Left 0x14 => self.cursor = Some(if cursor > 0 { cursor - 1 } else { 0 }), // Backspace 0x7f => { if cursor > 0 { url.remove(cursor - 1); self.cursor = Some(cursor - 1); } } key => { url.insert(cursor, key as char); self.cursor = Some((cursor + 1).min(url.width())) } } NavigationAction::Ignore } else { NavigationAction::Forward } } } } pub fn display_url(&self) -> &str { match &self.url { None => "about:blank", Some(url) => url, } } pub fn url_size(&self) -> usize { self.display_url().width() } pub fn mouse_up(&mut self, origin: Point) -> NavigationAction { if origin.y != 0 { self.cursor = None; NavigationAction::Forward } else { NavigationAction::Ignore } } pub fn mouse_down(&mut self, origin: Point) -> NavigationAction { if origin.y != 0 { self.cursor = None; return NavigationAction::Forward; } self.cursor = None; return match origin.x { 0..=2 => NavigationAction::GoBack(), 3..=5 => NavigationAction::GoForward(), 6..=8 => NavigationAction::Refresh(), 11.. => { self.cursor = Some(self.url_size().min(origin.x as usize - 11)); log::debug!("setting cursor to {:?}", self.cursor); NavigationAction::Ignore } _ => NavigationAction::Ignore, }; } pub fn mouse_move(&mut self, _origin: Point) -> NavigationAction { NavigationAction::Forward } pub fn push(&mut self, url: &str, can_go_back: bool, can_go_forward: bool) { if match (self.cursor, &self.url) { (None, _) => false, (_, None) => true, (_, Some(current)) => current != url, } { self.cursor = Some(url.len()) } self.url = Some(url.to_owned()); self.can_go_back = can_go_back; self.can_go_forward = can_go_forward; } pub fn set_size(&mut self, size: Size) { self.size = size } pub fn render_btn(&self, icon: &str, enabled: bool) -> [NavigationElement; 3] { let background = Color::splat(255); let foreground = Color::splat(0); [ NavigationElement { text: "[".to_owned(), background, foreground, }, NavigationElement { text: icon.to_owned(), background, foreground: if enabled { foreground } else { Color::splat(200) }, }, NavigationElement { text: "]".to_owned(), background, foreground, }, ] } pub fn render(&self, size: Size) -> Vec<(Point, NavigationElement)> { let ui_elements = 13; let space = if size.width >= ui_elements { (size.width - ui_elements) as usize } else { 0 }; let url: String = self.display_url().chars().take(space).collect(); let width = url.width(); let padded = format!(" {}{} ", url, " ".repeat(space - width)); let mut elements = Vec::new(); let mut point = Point::splat(0); for list in [ self.render_btn("\u{276e}", self.can_go_back), self.render_btn("\u{276f}", self.can_go_forward), self.render_btn("↻", true), self.render_btn(&padded, true), ] { for element in list { let width = element.text.width() as i32; elements.push((point.clone(), element)); point = point + (width, 0); } } elements } } ================================================ FILE: src/ui.rs ================================================ pub mod navigation; ================================================ FILE: src/utils/four_bits.rs ================================================ 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"), } } } ================================================ FILE: src/utils/log.rs ================================================ use std::path::Path; use chrono::prelude::*; use crate::utils::try_block; macro_rules! debug { ($($args:expr),+) => { crate::utils::log::write( "DEBUG", file!(), line!(), &format!($($args),*) ) }; } macro_rules! warning { ($($args:expr),+) => { crate::utils::log::write( "WARNING", file!(), line!(), &format!($($args),*) ) }; } macro_rules! error { ($($args:expr),+) => { crate::utils::log::write( "ERROR", file!(), line!(), &format!($($args),*) ) }; } pub(crate) use debug; pub(crate) use error; pub(crate) use warning; pub fn write(level: &str, file: &str, line: u32, message: &str) { let date = Utc::now(); eprintln!( "[{:02}{:02}/{:02}{:02}{:02}.{:06}:{}:{}({})] {}", date.month(), date.day(), date.hour(), date.minute(), date.second(), date.nanosecond() / 1000, level, try_block!(Path::new(file).file_name()?.to_str()).unwrap_or("default"), line, message ); } ================================================ FILE: src/utils/try_block.rs ================================================ macro_rules! try_block { ($block:expr) => { (|| $block)() }; } pub(crate) use try_block; ================================================ FILE: src/utils.rs ================================================ mod four_bits; mod try_block; pub mod log; use try_block::*; pub use four_bits::*;