[
  {
    "path": ".cargo/config.toml",
    "content": "[build]\ntarget-dir = \"build\"\n"
  },
  {
    "path": ".dockerignore",
    "content": "/build\n!/build/browser\n/chromium\n"
  },
  {
    "path": ".github/funding.yml",
    "content": "github: fathyb\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\nnode_modules/\n\n/.ccache\n/.git_cache\n/.vscode\n/build\n/chromium/*\n!/chromium/.gclient\n!/chromium/depot_tools\n!/chromium/patches\n/packages/*/build\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"chromium/depot_tools\"]\n\tpath = chromium/depot_tools\n\turl = https://chromium.googlesource.com/chromium/tools/depot_tools.git\n"
  },
  {
    "path": ".refloat/config.js",
    "content": "import { commit } from \"refloat\";\nimport docker from \"github.com/refloat-plugins/docker\";\n\nimport pkg from \"../package.json\";\n\nconst { version } = JSON.parse(pkg);\nconst triple = (platform, arch) => `${archs[arch]}-${platforms[platform]}`;\nconst lib = (platform, arch) =>\n  `build/${triple(platform, arch)}/release/libcarbonyl.${sharedLib[platform]}`;\nconst sharedLib = {\n  macos: \"dylib\",\n  linux: \"so\",\n};\nconst platforms = {\n  macos: \"apple-darwin\",\n  linux: \"unknown-linux-gnu\",\n};\nconst archs = {\n  arm64: \"aarch64\",\n  amd64: \"x86_64\",\n};\n\nexport const jobs = [\"macos\", \"linux\"].flatMap((platform) => {\n  return [\n    {\n      name: `Build runtime (${platform})`,\n      agent: { tags: [\"chromium-src\", platform] },\n      steps: [\n        ...[\"arm64\", \"amd64\"].map((arch) => ({\n          import: { workspace: `core-${triple(platform, arch)}` },\n        })),\n        {\n          parallel: [\"arm64\", \"amd64\"].map((arch) => ({\n            name: `Fetch pre-built runtime for ${arch}`,\n            command: `\n              if scripts/runtime-pull.sh ${arch}; then\n                  touch skip-build-${arch}\n                  cp \\\\\n                    ${lib(platform, arch)} \\\\\n                    build/pre-built/${triple(platform, arch)}\n              fi\n            `,\n          })),\n        },\n        {\n          name: \"Fetch Chromium\",\n          command: `\n            if [ -z \"$CHROMIUM_ROOT\" ]; then\n                echo \"Chromium build environment not setup\"\n\n                exit 2\n            fi\n\n            if [ ! -f skip-build-arm64 ] || [ ! -f skip-build-amd64 ]; then\n                cp chromium/.gclient \"$CHROMIUM_ROOT\"\n\n                scripts/gclient.sh sync\n                scripts/patches.sh apply\n\n                rm -rf \"$CHROMIUM_ROOT/src/carbonyl\"\n                mkdir \"$CHROMIUM_ROOT/src/carbonyl\"\n                ln -s \"$(pwd)/src\" \"$CHROMIUM_ROOT/src/carbonyl/src\"\n                ln -s \"$(pwd)/build\" \"$CHROMIUM_ROOT/src/carbonyl/build\"\n            fi\n          `,\n        },\n        {\n          parallel: [\"arm64\", \"amd64\"].map((arch) => {\n            const target =\n              platform === \"linux\" && arch === \"amd64\" ? \"Default\" : arch;\n\n            return {\n              serial: [\n                {\n                  name: `Build Chromium (${arch})`,\n                  command: `\n                    if [ ! -f skip-build-${arch} ]; then\n                        scripts/build.sh ${target} ${arch}\n                        scripts/copy-binaries.sh ${target} ${arch}\n                    fi\n                  `,\n                  env: {\n                    MACOSX_DEPLOYMENT_TARGET: \"10.13\",\n                    CARBONYL_SKIP_CARGO_BUILD: \"true\",\n                    AR_AARCH64_UNKNOWN_LINUX_GNU: \"aarch64-linux-gnu-ar\",\n                    CC_AARCH64_UNKNOWN_LINUX_GNU: \"aarch64-linux-gnu-gcc\",\n                    CXX_AARCH64_UNKNOWN_LINUX_GNU: \"aarch64-linux-gnu-g++\",\n                    CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER:\n                      \"aarch64-linux-gnu-gcc\",\n                    AR_X86_64_UNKNOWN_LINUX_GNU: \"x86_64-linux-gnu-ar\",\n                    CC_X86_64_UNKNOWN_LINUX_GNU: \"x86_64-linux-gnu-gcc\",\n                    CXX_X86_64_UNKNOWN_LINUX_GNU: \"x86_64-linux-gnu-g++\",\n                    CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER:\n                      \"x86_64-linux-gnu-gcc\",\n                  },\n                },\n\n                {\n                  parallel: [\n                    {\n                      name: `Push binaries to CDN (${arch})`,\n                      command: `\n                        if [ ! -f skip-build-${arch} ]; then\n                            scripts/runtime-push.sh ${arch}\n                        fi\n                      `,\n                      env: {\n                        CDN_ACCESS_KEY_ID: { secret: true },\n                        CDN_SECRET_ACCESS_KEY: { secret: true },\n                      },\n                    },\n                    {\n                      export: {\n                        workspace: `runtime-${triple(platform, arch)}`,\n                        path: `build/pre-built/${triple(platform, arch)}`,\n                      },\n                    },\n                  ],\n                },\n              ],\n            };\n          }),\n        },\n      ],\n    },\n    ...[\"arm64\", \"amd64\"].flatMap((arch) => {\n      const triple = `${archs[arch]}-${platforms[platform]}`;\n      const lib = `build/${triple}/release/libcarbonyl.${sharedLib[platform]}`;\n\n      return [\n        {\n          name: `Build core (${platform}/${arch})`,\n          docker:\n            platform === \"linux\"\n              ? {\n                  image: \"fathyb/rust-cross\",\n                  cache: [\"/usr/local/cargo/registry\"],\n                }\n              : undefined,\n          agent: { tags: platform === \"linux\" ? [\"docker\"] : [\"macos\"] },\n          steps: [\n            {\n              name: \"Install Rust toolchain\",\n              command: `rustup target add ${triple}`,\n            },\n            {\n              name: \"Build core library\",\n              command: `cargo build --target ${triple} --release`,\n              env: { MACOSX_DEPLOYMENT_TARGET: \"10.13\" },\n            },\n            {\n              name: \"Set core library install name\",\n              command:\n                platform === \"macos\"\n                  ? `install_name_tool -id @executable_path/libcarbonyl.dylib ${lib}`\n                  : \"echo not necessary\",\n            },\n            {\n              export: {\n                workspace: `core-${triple}`,\n                path: \"build/*/release/*.{dylib,so,dll}\",\n              },\n            },\n          ],\n        },\n        {\n          name: `Package (${platform}/${arch})`,\n          docker: \"fathyb/rust-cross\",\n          agent: { tags: [\"docker\"] },\n          steps: [\n            {\n              import: { workspace: `runtime-${triple}` },\n            },\n            {\n              name: \"Zip binaries\",\n              command: `\n                mkdir build/zip\n                cp -r build/pre-built/${triple} build/zip/carbonyl-${version}\n\n                cd build/zip\n                zip -r package.zip carbonyl-${version}\n              `,\n            },\n            {\n              export: {\n                artifact: {\n                  name: `carbonyl.${platform}-${arch}.zip`,\n                  path: \"build/zip/package.zip\",\n                },\n              },\n            },\n          ],\n        },\n      ];\n    }),\n  ];\n});\n\nif (commit.defaultBranch) {\n  jobs.push(\n    {\n      name: \"Publish to Docker\",\n      agent: { tags: [\"carbonyl-publish\"] },\n      docker: \"fathyb/rust-cross\",\n      steps: [\n        {\n          serial: [\"arm64\", \"amd64\"].map((arch) => ({\n            import: { workspace: `runtime-${triple(\"linux\", arch)}` },\n          })),\n        },\n        {\n          parallel: [\"arm64\", \"amd64\"].map((arch) => ({\n            serial: [\n              {\n                name: `Build ${arch} image`,\n                command: `scripts/docker-build.sh ${arch}`,\n              },\n            ],\n          })),\n        },\n        {\n          name: \"Publish images to DockerHub\",\n          command: \"scripts/docker-push.sh next\",\n          using: docker.login({\n            username: { secret: \"DOCKER_PUBLISH_USERNAME\" },\n            password: { secret: \"DOCKER_PUBLISH_TOKEN\" },\n          }),\n        },\n      ],\n    },\n    {\n      name: \"Publish to npm\",\n      agent: { tags: [\"carbonyl-publish\"] },\n      docker: \"node:18\",\n      steps: [\n        ...[\"macos\", \"linux\"].flatMap((platform) =>\n          [\"arm64\", \"amd64\"].map((arch) => ({\n            import: { workspace: `runtime-${triple(platform, arch)}` },\n          }))\n        ),\n        {\n          name: \"Package\",\n          command: \"scripts/npm-package.sh\",\n        },\n        {\n          name: \"Write npm token\",\n          env: { CARBONYL_NPM_PUBLISH_TOKEN: { secret: true } },\n          command:\n            'echo \"//registry.npmjs.org/:_authToken=${CARBONYL_NPM_PUBLISH_TOKEN}\" > ~/.npmrc',\n        },\n        {\n          parallel: [\"amd64\", \"arm64\"].flatMap((arch) =>\n            [\"linux\", \"macos\"].map((platform) => ({\n              name: `Publish ${platform}/${arch} package`,\n              command: \"scripts/npm-publish.sh --tag next\",\n              env: {\n                CARBONYL_PUBLISH_ARCH: arch,\n                CARBONYL_PUBLISH_PLATFORM: platform,\n              },\n            }))\n          ),\n        },\n        {\n          name: \"Publish main package\",\n          command: \"scripts/npm-publish.sh --tag next\",\n        },\n      ],\n    }\n  );\n}\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"carbonyl\"\nversion = \"0.0.3\"\nedition = \"2021\"\n\n[dependencies]\nlibc = \"0.2\"\nunicode-width = \"0.1.10\"\nunicode-segmentation = \"1.10.0\"\nchrono = \"0.4.23\"\n\n[lib]\nname = \"carbonyl\"\npath = \"src/lib.rs\"\ncrate-type = [\"cdylib\"]\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM rust:1.67 AS cross-compile\n\nRUN apt-get update && \\\n    apt-get install -y \\\n        zip g++-aarch64-linux-gnu g++-x86-64-linux-gnu libc6-dev-arm64-cross libc6-dev-amd64-cross && \\\n    rustup target add aarch64-unknown-linux-gnu x86_64-unknown-linux-gnu && \\\n    rustup toolchain install stable-aarch64-unknown-linux-gnu stable-x86_64-unknown-linux-gnu && \\\n    rm -rf /var/lib/apt/lists/*\n\nENV AR_AARCH64_UNKNOWN_LINUX_GNU=aarch64-linux-gnu-ar\nENV CC_AARCH64_UNKNOWN_LINUX_GNU=aarch64-linux-gnu-gcc\nENV CXX_AARCH64_UNKNOWN_LINUX_GNU=aarch64-linux-gnu-g++\nENV CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc\n\nENV AR_X86_64_UNKNOWN_LINUX_GNU=x86_64-linux-gnu-ar\nENV CC_X86_64_UNKNOWN_LINUX_GNU=x86_64-linux-gnu-gcc\nENV CXX_X86_64_UNKNOWN_LINUX_GNU=x86_64-linux-gnu-g++\nENV CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=x86_64-linux-gnu-gcc\n\nFROM debian:bullseye-slim\n\nRUN groupadd -r carbonyl && \\\n    useradd -r -g carbonyl carbonyl && \\\n    mkdir -p /carbonyl/data && \\\n    chown -R carbonyl:carbonyl /carbonyl && \\\n    apt-get update && \\\n    apt-get install -y libasound2 libexpat1 libfontconfig1 libnss3 && \\\n    rm -rf /var/lib/apt/lists/*\n\nUSER carbonyl\nVOLUME /carbonyl/data\nENV HOME=/carbonyl/data\n\nCOPY . /carbonyl\n\nRUN /carbonyl/carbonyl --version\n\nENTRYPOINT [\"/carbonyl/carbonyl\", \"--no-sandbox\", \"--disable-dev-shm-usage\", \"--user-data-dir=/carbonyl/data\"]\n"
  },
  {
    "path": "build.rs",
    "content": "use std::path::PathBuf;\n\n#[cfg(target_arch = \"x86_64\")]\nfn link_sysroot() {\n    let sysroot_path = PathBuf::from(\"./chromium/src/build/linux/debian_bullseye_amd64-sysroot\");\n\n    if sysroot_path.is_dir() {\n        println!(\"cargo:rustc-link-search=chromium/src/build/linux/debian_bullseye_amd64-sysroot/lib/x86_64-linux-gnu\");\n        println!(\"cargo:rustc-link-search=chromium/src/build/linux/debian_bullseye_amd64-sysroot/usr/lib/x86_64-linux-gnu\");\n\n        println!(\n            \"cargo:rustc-link-arg=--sysroot=./chromium/src/build/linux/debian_bullseye_amd64-sysroot\"\n        );\n    } else {\n        println!(\"cargo:warning={}\", \"x86_64 debian sysroot provided by chromium was not found!\");\n        println!(\"cargo:warning={}\", \"carbonyl may fail to link against a proper libc!\");\n    }\n}\n\n#[cfg(target_arch = \"x86\")]\nfn link_sysroot() {\n    let sysroot_path = PathBuf::from(\"./chromium/src/build/linux/debian_bullseye_i386-sysroot\");\n\n    if sysroot_path.is_dir() {\n        println!(\"cargo:rustc-link-search=chromium/src/build/linux/debian_bullseye_i386-sysroot/lib/i386-linux-gnu\");\n        println!(\"cargo:rustc-link-search=chromium/src/build/linux/debian_bullseye_i386-sysroot/usr/lib/i386-linux-gnu\");\n\n        println!(\n            \"cargo:rustc-link-arg=--sysroot=./chromium/src/build/linux/debian_bullseye_i386-sysroot\"\n        );\n    } else {\n        println!(\"cargo:warning={}\", \"x86 debian sysroot provided by chromium was not found!\");\n        println!(\"cargo:warning={}\", \"carbonyl may fail to link against a proper libc!\");\n    }\n}\n\n#[cfg(not(any(target_arch = \"x86_64\", target_arch = \"x86\")))]\nfn link_sysroot() {\n    // Intentionally left blank.\n}\n\nfn main() {\n    link_sysroot();\n}\n"
  },
  {
    "path": "changelog.md",
    "content": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\n## [0.0.3] - 2023-02-18\n\n### 🚀 Features\n\n- Add `--help` and `--version` ([#105](https://github.com/fathyb/carbonyl/issues/105))\n- Add logo and description to `--help` ([#106](https://github.com/fathyb/carbonyl/issues/106))\n- Use Cmd instead of Alt for navigation shortcuts ([#109](https://github.com/fathyb/carbonyl/issues/109))\n- Enable h.264 support ([#103](https://github.com/fathyb/carbonyl/issues/103))\n- Introduce quadrant rendering ([#120](https://github.com/fathyb/carbonyl/issues/120))\n\n### 🐛 Bug Fixes\n\n- Fix arguments parsing ([#108](https://github.com/fathyb/carbonyl/issues/108))\n- Fix missing module error on npm package ([#113](https://github.com/fathyb/carbonyl/issues/113))\n- Enable threaded compositing with bitmap mode\n- Fix idling CPU usage ([#126](https://github.com/fathyb/carbonyl/issues/126))\n- Package proper library in binaries ([#127](https://github.com/fathyb/carbonyl/issues/127))\n\n### 📖 Documentation\n\n- Update download links\n- Fix commit_preprocessors url ([#102](https://github.com/fathyb/carbonyl/issues/102))\n- Add `--rm` to Docker example ([#101](https://github.com/fathyb/carbonyl/issues/101))\n\n## [0.0.2] - 2023-02-09\n\n### 🚀 Features\n\n- Better true color detection\n- Linux support\n- Xterm title\n- Hide stderr unless crash\n- Add `--debug` to print stderr on exit ([#23](https://github.com/fathyb/carbonyl/issues/23))\n- Add navigation UI ([#86](https://github.com/fathyb/carbonyl/issues/86))\n- Handle terminal resize ([#87](https://github.com/fathyb/carbonyl/issues/87))\n\n### 🐛 Bug Fixes\n\n- Parser fixes\n- Properly enter tab and return keys\n- Fix some special characters ([#35](https://github.com/fathyb/carbonyl/issues/35))\n- Improve terminal size detection ([#36](https://github.com/fathyb/carbonyl/issues/36))\n- Allow working directories that contain spaces ([#63](https://github.com/fathyb/carbonyl/issues/63))\n- Do not use tags for checkout ([#64](https://github.com/fathyb/carbonyl/issues/64))\n- Do not checkout nacl ([#79](https://github.com/fathyb/carbonyl/issues/79))\n- Wrap zip files in carbonyl folder ([#88](https://github.com/fathyb/carbonyl/issues/88))\n- Fix WebGL support on Linux ([#90](https://github.com/fathyb/carbonyl/issues/90))\n- Fix initial freeze on Docker ([#91](https://github.com/fathyb/carbonyl/issues/91))\n\n### 📖 Documentation\n\n- Upload demo videos\n- Fix video layout\n- Fix a typo ([#1](https://github.com/fathyb/carbonyl/issues/1))\n- Fix a typo `ie.` -> `i.e.` ([#9](https://github.com/fathyb/carbonyl/issues/9))\n- Fix build instructions ([#15](https://github.com/fathyb/carbonyl/issues/15))\n- Add ascii logo\n- Add comparisons ([#34](https://github.com/fathyb/carbonyl/issues/34))\n- Add OS support ([#50](https://github.com/fathyb/carbonyl/issues/50))\n- Add download link\n- Fix linux download links\n- Document shared library\n- Fix a typo (`know` -> `known`) ([#71](https://github.com/fathyb/carbonyl/issues/71))\n- Add license\n\n### Build\n\n- Various build system fixes ([#20](https://github.com/fathyb/carbonyl/issues/20))\n\n"
  },
  {
    "path": "chromium/.gclient",
    "content": "solutions = [\n  {\n    \"name\": \"src\",\n    \"url\": \"https://chromium.googlesource.com/chromium/src.git@111.0.5511.1\",\n    \"managed\": False,\n    \"custom_deps\": {},\n    \"custom_vars\": {\n      \"use_rust\": True,\n      \"checkout_pgo_profiles\": True, \n      \"checkout_nacl\": False,\n    }\n  },\n]\n"
  },
  {
    "path": "chromium/patches/chromium/0001-Add-Carbonyl-library.patch",
    "content": "From 0ed9a390f25d73492ce1170ce229b95772fd458d Mon Sep 17 00:00:00 2001\nFrom: Fathy Boundjadj <hey@fathy.fr>\nDate: Thu, 9 Feb 2023 03:20:50 +0100\nSubject: [PATCH 01/14] Add Carbonyl library\n\n---\n carbonyl/build    |  1 +\n carbonyl/src      |  1 +\n headless/BUILD.gn | 15 ++++++++++++++-\n 3 files changed, 16 insertions(+), 1 deletion(-)\n create mode 120000 carbonyl/build\n create mode 120000 carbonyl/src\n\ndiff --git a/carbonyl/build b/carbonyl/build\nnew file mode 120000\nindex 0000000000000..44735d5866459\n--- /dev/null\n+++ b/carbonyl/build\n@@ -0,0 +1 @@\n+../../../build\n\\ No newline at end of file\ndiff --git a/carbonyl/src b/carbonyl/src\nnew file mode 120000\nindex 0000000000000..dabb0e15a991e\n--- /dev/null\n+++ b/carbonyl/src\n@@ -0,0 +1 @@\n+../../../src\n\\ No newline at end of file\ndiff --git a/headless/BUILD.gn b/headless/BUILD.gn\nindex bfae1e3290de0..8018111ed9898 100644\n--- a/headless/BUILD.gn\n+++ b/headless/BUILD.gn\n@@ -453,6 +453,7 @@ component(\"headless_non_renderer\") {\n     \"//build:branding_buildflags\",\n     \"//build:branding_buildflags\",\n     \"//build:chromeos_buildflags\",\n+    \"//carbonyl/src/browser:carbonyl\",\n     \"//components/cookie_config\",\n     \"//components/crash/core/common:common\",\n     \"//components/embedder_support\",\n@@ -993,13 +994,25 @@ static_library(\"headless_shell_lib\") {\n }\n \n executable(\"headless_shell\") {\n+  if (is_mac && !use_lld) {\n+    ldflags = [ \"-Wl,-no_compact_unwind\" ]\n+  } else if (is_linux) {\n+    ldflags = [\n+      \"-Wl,-rpath=\\$ORIGIN/.\",\n+      \"-Wl,-rpath-link=.\",\n+    ]\n+  }\n+\n   configs -= [ \"//build/config/compiler:thinlto_optimize_default\" ]\n   configs += [ \"//build/config/compiler:thinlto_optimize_max\" ]\n \n   sources = [ \"app/headless_shell_main.cc\" ]\n   defines = []\n \n-  deps = [ \":headless_shell_lib\" ]\n+  deps = [\n+    \":headless_shell_lib\",\n+    \"//carbonyl/src/browser:carbonyl\",\n+  ]\n \n   if (!headless_use_embedded_resources) {\n     data = [\n"
  },
  {
    "path": "chromium/patches/chromium/0002-Add-Carbonyl-service.patch",
    "content": "From 795b29828fd7ac95548c4dcab483cbc3b6c1d361 Mon Sep 17 00:00:00 2001\nFrom: Fathy Boundjadj <hey@fathy.fr>\nDate: Thu, 9 Feb 2023 03:21:59 +0100\nSubject: [PATCH 02/14] Add Carbonyl service\n\n---\n cc/trees/layer_tree_host.cc                   |  21 ++\n cc/trees/layer_tree_host.h                    |   8 +\n content/browser/browser_interface_binders.cc  |   4 +\n .../renderer_host/render_frame_host_impl.cc   |   6 +\n .../renderer_host/render_frame_host_impl.h    |   7 +\n content/renderer/render_frame_impl.cc         | 288 ++++++++++++++++++\n content/renderer/render_frame_impl.h          |   8 +\n .../blink/public/web/web_frame_widget.h       |   7 +\n .../blink/renderer/platform/fonts/font.cc     |  63 +++-\n 9 files changed, 409 insertions(+), 3 deletions(-)\n\ndiff --git a/cc/trees/layer_tree_host.cc b/cc/trees/layer_tree_host.cc\nindex 55295cc3f1e2f..20fa1030fb4e8 100644\n--- a/cc/trees/layer_tree_host.cc\n+++ b/cc/trees/layer_tree_host.cc\n@@ -481,6 +481,24 @@ bool LayerTreeHost::MainFrameUpdatesAreDeferred() const {\n bool LayerTreeHost::IsUsingLayerLists() const {\n   return settings_.use_layer_lists;\n }\n+ \n+void LayerTreeHost::StartTerminalRender() {\n+  DCHECK(IsMainThread());\n+\n+  auto it = terminal_render_callbacks_.begin();\n+\n+  while (it != terminal_render_callbacks_.end()) {\n+    auto& callback = *it->get();\n+\n+    if (callback) {\n+      callback();\n+\n+      it++;\n+    } else {\n+      it = terminal_render_callbacks_.erase(it);\n+    }\n+  }\n+}\n \n void LayerTreeHost::CommitComplete(const CommitTimestamps& commit_timestamps) {\n   DCHECK(IsMainThread());\n@@ -1648,6 +1666,9 @@ bool LayerTreeHost::PaintContent(const LayerList& update_layer_list) {\n   for (const auto& layer : update_layer_list) {\n     did_paint_content |= layer->Update();\n   }\n+\n+  StartTerminalRender();\n+\n   return did_paint_content;\n }\n \ndiff --git a/cc/trees/layer_tree_host.h b/cc/trees/layer_tree_host.h\nindex c26301594abc2..ed217c22359c4 100644\n--- a/cc/trees/layer_tree_host.h\n+++ b/cc/trees/layer_tree_host.h\n@@ -167,6 +167,12 @@ class CC_EXPORT LayerTreeHost : public MutatorHostClient {\n   // Returns the process global unique identifier for this LayerTreeHost.\n   int GetId() const;\n \n+  // Carbonyl\n+  void StartTerminalRender();\n+  void ObserveTerminalRender(std::shared_ptr<std::function<bool()>> callback) {\n+    terminal_render_callbacks_.push_back(callback);\n+  }\n+\n   // The commit state for the frame being assembled by the compositor host.\n   const CommitState* pending_commit_state() const {\n     DCHECK(IsMainThread());\n@@ -1086,6 +1092,8 @@ class CC_EXPORT LayerTreeHost : public MutatorHostClient {\n \n   bool syncing_deltas_for_test_ = false;\n \n+  std::vector<std::shared_ptr<std::function<bool()>>> terminal_render_callbacks_;\n+\n   base::WeakPtrFactory<LayerTreeHost> weak_ptr_factory_{this};\n };\n \ndiff --git a/content/browser/browser_interface_binders.cc b/content/browser/browser_interface_binders.cc\nindex 6e62b36a76799..e07dcb983698b 100644\n--- a/content/browser/browser_interface_binders.cc\n+++ b/content/browser/browser_interface_binders.cc\n@@ -887,6 +887,10 @@ void PopulateFrameBinders(RenderFrameHostImpl* host, mojo::BinderMap* map) {\n       base::BindRepeating(&RenderFrameHostImpl::GetVirtualAuthenticatorManager,\n                           base::Unretained(host)));\n \n+  map->Add<carbonyl::mojom::CarbonylRenderService>(\n+      base::BindRepeating(&RenderFrameHostImpl::GetCarbonylRenderService,\n+                          base::Unretained(host)));\n+\n   map->Add<device::mojom::DevicePostureProvider>(\n       base::BindRepeating(&BindDevicePostureProvider));\n \ndiff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc\nindex 8ba82589beb32..cc0ad634bcb4c 100644\n--- a/content/browser/renderer_host/render_frame_host_impl.cc\n+++ b/content/browser/renderer_host/render_frame_host_impl.cc\n@@ -10731,6 +10731,12 @@ void RenderFrameHostImpl::BindTrustTokenQueryAnswerer(\n       std::move(receiver), ComputeTopFrameOrigin(GetLastCommittedOrigin()));\n }\n \n+void RenderFrameHostImpl::GetCarbonylRenderService(\n+    mojo::PendingReceiver<carbonyl::mojom::CarbonylRenderService> receiver) {\n+  carbonyl_render_service_ = std::make_unique<carbonyl::CarbonylRenderServiceImpl>(\n+    std::move(receiver));\n+}\n+\n void RenderFrameHostImpl::GetAudioContextManager(\n     mojo::PendingReceiver<blink::mojom::AudioContextManager> receiver) {\n   AudioContextManagerImpl::Create(this, std::move(receiver));\ndiff --git a/content/browser/renderer_host/render_frame_host_impl.h b/content/browser/renderer_host/render_frame_host_impl.h\nindex db16f07685acf..8c36cce0ec604 100644\n--- a/content/browser/renderer_host/render_frame_host_impl.h\n+++ b/content/browser/renderer_host/render_frame_host_impl.h\n@@ -176,6 +176,8 @@\n #include \"media/mojo/mojom/remoting.mojom-forward.h\"\n #endif\n \n+#include \"carbonyl/src/browser/render_service_impl.h\"\n+\n namespace blink {\n class AssociatedInterfaceRegistry;\n class DocumentPolicy;\n@@ -1834,6 +1836,9 @@ class CONTENT_EXPORT RenderFrameHostImpl\n   // Set the `frame_` for sending messages to the renderer process.\n   void SetMojomFrameRemote(mojo::PendingAssociatedRemote<mojom::Frame>);\n \n+  void GetCarbonylRenderService(\n+    mojo::PendingReceiver<carbonyl::mojom::CarbonylRenderService> receiver);\n+\n   void GetAudioContextManager(\n       mojo::PendingReceiver<blink::mojom::AudioContextManager> receiver);\n \n@@ -4720,6 +4725,8 @@ class CONTENT_EXPORT RenderFrameHostImpl\n   // The observers watching our state changed event.\n   base::ObserverList<RenderFrameHostObserver> observers_;\n \n+  std::unique_ptr<carbonyl::CarbonylRenderServiceImpl> carbonyl_render_service_;\n+\n   // BrowserInterfaceBroker implementation through which this\n   // RenderFrameHostImpl exposes document-scoped Mojo services to the currently\n   // active document in the corresponding RenderFrame.\ndiff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc\nindex 9e09c5342699e..97b61ffb954be 100644\n--- a/content/renderer/render_frame_impl.cc\n+++ b/content/renderer/render_frame_impl.cc\n@@ -232,6 +232,7 @@\n #include \"third_party/blink/public/web/web_view.h\"\n #include \"third_party/blink/public/web/web_widget.h\"\n #include \"third_party/blink/public/web/web_window_features.h\"\n+#include \"third_party/skia/include/core/SkPictureRecorder.h\"\n #include \"ui/accessibility/ax_tree_update.h\"\n #include \"ui/events/base_event_utils.h\"\n #include \"url/origin.h\"\n@@ -255,6 +256,39 @@\n #include \"content/renderer/java/gin_java_bridge_dispatcher.h\"\n #endif\n \n+// Carbonyl\n+#include <stdlib.h>\n+#include <iostream>\n+#include \"cc/paint/paint_recorder.h\"\n+#include \"cc/paint/skia_paint_canvas.h\"\n+#include \"cc/raster/playback_image_provider.h\"\n+#include \"cc/tiles/software_image_decode_cache.h\"\n+#include \"cc/trees/layer_tree_host.h\"\n+#include \"cc/trees/render_frame_metadata_observer.h\"\n+#include \"components/paint_preview/common/paint_preview_tracker.h\"\n+#include \"third_party/blink/renderer/core/exported/web_view_impl.h\"\n+#include \"third_party/blink/renderer/core/dom/frame_request_callback_collection.h\"\n+#include \"third_party/blink/renderer/core/frame/local_frame_view.h\"\n+#include \"third_party/blink/renderer/core/frame/web_local_frame_impl.h\"\n+#include \"third_party/blink/renderer/core/paint/paint_flags.h\"\n+#include \"third_party/blink/renderer/core/layout/layout_view.h\"\n+#include \"third_party/blink/renderer/platform/graphics/paint/cull_rect.h\"\n+#include \"third_party/blink/renderer/platform/graphics/paint/paint_record_builder.h\"\n+#include \"third_party/skia/include/core/SkEncodedImageFormat.h\"\n+#include \"third_party/skia/include/core/SkMesh.h\"\n+#include \"third_party/skia/include/core/SkStream.h\"\n+#include \"third_party/skia/include/core/SkSurface.h\"\n+#include \"third_party/skia/include/core/SkVertices.h\"\n+#include \"third_party/skia/include/docs/SkPDFDocument.h\"\n+#include \"third_party/skia/include/svg/SkSVGCanvas.h\"\n+#include \"third_party/skia/include/svg/SkSVGCanvas.h\"\n+#include \"third_party/skia/include/utils/SkBase64.h\"\n+#include \"third_party/skia/src/text/GlyphRun.h\"\n+#include \"third_party/skia/src/core/SkClipStackDevice.h\"\n+#include \"third_party/skia/src/core/SkDevice.h\"\n+#include \"third_party/skia/src/core/SkFontPriv.h\"\n+#include \"third_party/skia/src/utils/SkUTF.h\"\n+\n using base::Time;\n using blink::ContextMenuData;\n using blink::WebContentDecryptionModule;\n@@ -1912,8 +1946,229 @@ RenderFrameImpl::~RenderFrameImpl() {\n   base::trace_event::TraceLog::GetInstance()->RemoveProcessLabel(routing_id_);\n   g_routing_id_frame_map.Get().erase(routing_id_);\n   agent_scheduling_group_.RemoveRoute(routing_id_);\n+\n+  if (auto& callback = *render_callback_.get()) {\n+    callback = nullptr;\n+  }\n }\n \n+} // namespace content\n+\n+namespace carbonyl {\n+class TextCaptureDevice: public SkClipStackDevice {\n+public:\n+  TextCaptureDevice(\n+    const SkImageInfo& info,\n+    const SkSurfaceProps& props\n+  ):\n+    SkClipStackDevice(info, props)\n+  {\n+    clear(SkRect::MakeWH(info.width(), info.height()));\n+  }\n+\n+  void swap(std::vector<carbonyl::mojom::TextDataPtr>& data) {\n+    data.swap(data_);\n+  }\n+\n+  void clear() {\n+    data_.clear();\n+  }\n+\n+  void clear(const SkRect& rect) {\n+    data_.push_back(\n+      carbonyl::mojom::TextData::New(\n+        std::string(),\n+        gfx::SkRectToRectF(rect),\n+        0\n+      )\n+    );\n+  }\n+\n+protected:\n+  SkBaseDevice* onCreateDevice(const CreateInfo& info, const SkPaint*) override {\n+    return new TextCaptureDevice(info.fInfo, SkSurfaceProps(0, info.fPixelGeometry));\n+  }\n+\n+  void drawDevice(SkBaseDevice* baseDevice, const SkSamplingOptions&, const SkPaint& paint) override {\n+    if(isUnsupportedPaint(paint)) {\n+      return;\n+    }\n+\n+    auto blendMode = paint.getBlendMode_or(SkBlendMode::kClear);\n+\n+    if (blendMode != SkBlendMode::kSrc && blendMode != SkBlendMode::kSrcOver) {\n+      return;\n+    }\n+\n+    auto* device = static_cast<TextCaptureDevice*>(baseDevice);\n+    SkMatrix transform = device->getRelativeTransform(*this);\n+\n+    for (auto& data: device->data_) {\n+      data_.push_back(\n+        carbonyl::mojom::TextData::New(\n+          data->contents,\n+          gfx::SkRectToRectF(transform.mapRect(gfx::RectFToSkRect(data->bounds))),\n+          data->color\n+        )\n+      );\n+    }\n+  }\n+\n+  void drawPaint(const SkPaint&) override {}\n+  void drawOval(const SkRect&, const SkPaint&) override {}\n+  void drawPoints(SkCanvas::PointMode, size_t, const SkPoint[], const SkPaint&) override {}\n+  void drawImageRect(const SkImage*,\n+                              const SkRect*,\n+                              const SkRect& rect,\n+                              const SkSamplingOptions&,\n+                              const SkPaint&,\n+                              SkCanvas::SrcRectConstraint) override {\n+    // clear(scale(rect));\n+  }\n+\n+  void drawVertices(const SkVertices* vertices,\n+                            sk_sp<SkBlender>,\n+                            const SkPaint& paint,\n+                            bool = false) override {\n+    drawRect(vertices->bounds(), paint);\n+  }\n+\n+  void drawMesh(const SkMesh& mesh, sk_sp<SkBlender>, const SkPaint& paint) override {\n+    drawRect(mesh.bounds(), paint);\n+  }\n+\n+  void drawPath(const SkPath& path, const SkPaint& paint, bool = false) override {\n+    drawRect(path.getBounds(), paint);\n+  }\n+\n+  void drawRRect(const SkRRect& rect, const SkPaint& paint) override {\n+    drawRect(rect.rect(), paint);\n+  }\n+\n+  bool isUnsupportedPaint(const SkPaint& paint) {\n+      return (\n+        paint.getShader() ||\n+        paint.getBlender() ||\n+        paint.getPathEffect() ||\n+        paint.getMaskFilter() ||\n+        paint.getImageFilter() ||\n+        paint.getColorFilter() ||\n+        paint.getImageFilter()\n+      );\n+  }\n+\n+  void drawRect(const SkRect& rect, const SkPaint& paint) override {\n+    if (\n+      paint.getStyle() == SkPaint::Style::kFill_Style &&\n+      paint.getAlphaf() == 1.0 &&\n+      !isUnsupportedPaint(paint)\n+    ) {\n+      auto blendMode = paint.getBlendMode_or(SkBlendMode::kClear);\n+\n+      if (blendMode == SkBlendMode::kSrc || blendMode == SkBlendMode::kSrcOver) {\n+        clear(scale(rect));\n+      } else {\n+        std::cerr << \"Blending mode: \" << SkBlendMode_Name(blendMode) << std::endl;\n+      }\n+    }\n+  }\n+\n+  void onDrawGlyphRunList(SkCanvas*,\n+                          const sktext::GlyphRunList& glyphRunList,\n+                          const SkPaint&,\n+                          const SkPaint& paint) override {\n+    auto position = scale(glyphRunList.origin());\n+\n+    for (auto& glyphRun : glyphRunList) {\n+      auto runSize = glyphRun.runSize();\n+      SkAutoSTArray<64, SkUnichar> unichars(runSize);\n+      SkFontPriv::GlyphsToUnichars(glyphRun.font(), glyphRun.glyphsIDs().data(),\n+                                    runSize, unichars.get());\n+\n+      auto base64_ptr = std::make_unique<char[]>(runSize + 1);\n+      char* base64 = base64_ptr.get();\n+\n+      for (size_t i = 0; i < runSize; ++i) {\n+          base64[i] = unichars[i];\n+      }\n+\n+      base64[runSize] = '\\0';\n+\n+      size_t size = 0;\n+      auto error = SkBase64::Decode(base64, runSize, nullptr, &size);\n+\n+      if (error != SkBase64::kNoError) {\n+          return;\n+      }\n+\n+      auto utf8_ptr = std::make_unique<char[]>(size);\n+      char* utf8 = utf8_ptr.get();\n+\n+      error = SkBase64::Decode(base64, runSize, utf8, &size);\n+\n+      if (error != SkBase64::kNoError) {\n+          return;\n+      }\n+\n+      data_.push_back(\n+        carbonyl::mojom::TextData::New(\n+          std::string(utf8, size),\n+          gfx::RectF(position.x(), position.y(), 0, 0),\n+          paint.getColor()\n+        )\n+      );\n+    }\n+  }\n+\n+private:\n+  SkRect scale(const SkRect& rect) {\n+    return localToDevice().mapRect(rect);\n+  }\n+  SkPoint scale(const SkPoint& point) {\n+    return localToDevice().mapPoint(point);\n+  }\n+\n+  std::vector<carbonyl::mojom::TextDataPtr> data_;\n+};\n+\n+class RendererService {\n+  public:\n+  RendererService() = default;\n+\n+  SkCanvas* BeginPaint(int width, int height) {\n+    if (width != width_ || height != height_ || !device_) {\n+      width_ = width;\n+      height_ = height;\n+\n+      device_ = sk_sp(\n+        new TextCaptureDevice(\n+          SkImageInfo::MakeUnknown(width, height),\n+          SkSurfaceProps(0, kUnknown_SkPixelGeometry)\n+        )\n+      );\n+      canvas_ = std::make_unique<SkCanvas>(device_);\n+    }\n+    \n+    device_->clear();\n+\n+    return canvas_.get();\n+  }\n+\n+  void Swap(std::vector<carbonyl::mojom::TextDataPtr>& data) {\n+    device_->swap(data);\n+  }\n+\n+  private:\n+  int width_ = 0;\n+  int height_ = 0;\n+  sk_sp<TextCaptureDevice> device_;\n+  std::unique_ptr<SkCanvas> canvas_;\n+};\n+\n+} // namespace carbonyl\n+\n+namespace content {\n+\n void RenderFrameImpl::Initialize(blink::WebFrame* parent) {\n   initialized_ = true;\n   is_main_frame_ = !parent;\n@@ -1942,6 +2197,8 @@ void RenderFrameImpl::Initialize(blink::WebFrame* parent) {\n     factory.RegisterRemoteFactory(GetWebFrame()->GetLocalFrameToken(),\n                                   GetBrowserInterfaceBroker());\n   }\n+  \n+  browser_interface_broker_proxy_.GetInterface(std::move(carbonyl_render_service_receiver_));\n \n   frame_request_blocker_ = blink::WebFrameRequestBlocker::Create();\n \n@@ -1954,6 +2211,37 @@ void RenderFrameImpl::Initialize(blink::WebFrame* parent) {\n   agent_scheduling_group_.AddFrameRoute(\n       routing_id_, this,\n       GetTaskRunner(blink::TaskType::kInternalNavigationAssociated));\n+\n+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(\"carbonyl-b64-text\")) {\n+    setenv(\"carbonyl_b64_text\", \"true\", 1);\n+  }\n+\n+  auto* host = GetLocalRootWebFrameWidget()->LayerTreeHost();\n+  auto renderer = std::make_shared<carbonyl::RendererService>();\n+\n+  render_callback_ = std::make_shared<std::function<bool()>>(\n+    [=]() -> bool {\n+      if (!IsMainFrame() || IsHidden()) {\n+        return false;\n+      }\n+\n+      size_t width = frame_->DocumentSize().width();\n+      size_t height = frame_->VisibleContentRect().height();\n+      auto* view = static_cast<blink::WebViewImpl*>(GetWebFrame()->View());\n+      std::vector<carbonyl::mojom::TextDataPtr> data;\n+\n+      view->MainFrameImpl()->GetFrame()->View()->GetPaintRecord().Playback(\n+        renderer->BeginPaint(width, height)\n+      );\n+\n+      renderer->Swap(data);\n+      carbonyl_render_service_->DrawText(std::move(data));\n+\n+      return true;\n+    }\n+  );\n+\n+  host->ObserveTerminalRender(render_callback_);\n }\n \n void RenderFrameImpl::GetInterface(\ndiff --git a/content/renderer/render_frame_impl.h b/content/renderer/render_frame_impl.h\nindex 3b558293121ce..f996b74bfae7e 100644\n--- a/content/renderer/render_frame_impl.h\n+++ b/content/renderer/render_frame_impl.h\n@@ -118,6 +118,8 @@\n #include \"content/common/pepper_plugin.mojom.h\"\n #endif\n \n+#include \"carbonyl/src/browser/carbonyl.mojom.h\"\n+\n namespace blink {\n namespace scheduler {\n class WebAgentGroupScheduler;\n@@ -1446,6 +1448,8 @@ class CONTENT_EXPORT RenderFrameImpl\n   std::unique_ptr<blink::WebURLLoaderFactoryForTest>\n       web_url_loader_factory_override_for_test_;\n \n+  std::shared_ptr<std::function<bool()>> render_callback_;\n+\n   // When the browser asks the renderer to commit a navigation, it should always\n   // result in a committed navigation reported via DidCommitProvisionalLoad().\n   // This is important because DidCommitProvisionalLoad() is responsible for\n@@ -1522,6 +1526,10 @@ class CONTENT_EXPORT RenderFrameImpl\n   // false, but set to true by some tests.\n   bool send_content_state_immediately_ = false;\n \n+  mojo::Remote<carbonyl::mojom::CarbonylRenderService> carbonyl_render_service_;\n+  mojo::PendingReceiver<carbonyl::mojom::CarbonylRenderService> carbonyl_render_service_receiver_ =\n+    carbonyl_render_service_.BindNewPipeAndPassReceiver();\n+\n   base::WeakPtrFactory<RenderFrameImpl> weak_factory_{this};\n };\n \ndiff --git a/third_party/blink/public/web/web_frame_widget.h b/third_party/blink/public/web/web_frame_widget.h\nindex 6264d513b398c..3988df585a159 100644\n--- a/third_party/blink/public/web/web_frame_widget.h\n+++ b/third_party/blink/public/web/web_frame_widget.h\n@@ -53,6 +53,10 @@ struct ApplyViewportChangesArgs;\n class LayerTreeHost;\n }  // namespace cc\n \n+namespace content {\n+class RenderFrameImpl;\n+}  // namespace content\n+\n namespace gfx {\n class PointF;\n class RectF;\n@@ -227,6 +231,9 @@ class WebFrameWidget : public WebWidget {\n   // GPU benchmarking extension needs access to the LayerTreeHost\n   friend class GpuBenchmarkingContext;\n \n+  // Allow RenderFrameImpl to access the LayerTreeHost for html2svg\n+  friend class content::RenderFrameImpl;\n+\n   // This private constructor and the class/friend declaration ensures that\n   // WebFrameWidgetImpl is the only concrete subclass that implements\n   // WebFrameWidget, so that it is safe to downcast to WebFrameWidgetImpl.\ndiff --git a/third_party/blink/renderer/platform/fonts/font.cc b/third_party/blink/renderer/platform/fonts/font.cc\nindex 089a11b156ade..dfdc79eacce3b 100644\n--- a/third_party/blink/renderer/platform/fonts/font.cc\n+++ b/third_party/blink/renderer/platform/fonts/font.cc\n@@ -24,6 +24,8 @@\n \n #include \"third_party/blink/renderer/platform/fonts/font.h\"\n \n+#include \"base/base64.h\"\n+\n #include \"cc/paint/paint_canvas.h\"\n #include \"cc/paint/paint_flags.h\"\n #include \"third_party/blink/renderer/platform/fonts/character_range.h\"\n@@ -149,11 +151,17 @@ bool Font::operator==(const Font& other) const {\n \n namespace {\n \n+static const bool carbonyl_b64_text = true;\n+\n void DrawBlobs(cc::PaintCanvas* canvas,\n                const cc::PaintFlags& flags,\n                const ShapeResultBloberizer::BlobBuffer& blobs,\n                const gfx::PointF& point,\n-               cc::NodeId node_id = cc::kInvalidNodeId) {\n+               cc::NodeId node_id = cc::kInvalidNodeId) {  \n+  if (carbonyl_b64_text) {\n+    return;\n+  }\n+\n   for (const auto& blob_info : blobs) {\n     DCHECK(blob_info.blob);\n     cc::PaintCanvasAutoRestore auto_restore(canvas, false);\n@@ -198,8 +206,7 @@ void DrawBlobs(cc::PaintCanvas* canvas,\n       }\n     }\n     if (node_id != cc::kInvalidNodeId) {\n-      canvas->drawTextBlob(blob_info.blob, point.x(), point.y(), node_id,\n-                           flags);\n+      canvas->drawTextBlob(blob_info.blob, point.x(), point.y(), node_id, flags);\n     } else {\n       canvas->drawTextBlob(blob_info.blob, point.x(), point.y(), flags);\n     }\n@@ -230,6 +237,31 @@ void Font::DrawText(cc::PaintCanvas* canvas,\n   if (ShouldSkipDrawing())\n     return;\n \n+  if (carbonyl_b64_text) {\n+    auto string = StringView(\n+      run_info.run.ToStringView(),\n+      run_info.from,\n+      run_info.to - run_info.from\n+    ).ToString().Utf8();\n+    auto base64 = base::Base64Encode(base::as_bytes(base::make_span(string)));\n+\n+    // Bypass HarfBuzz text shaping for the Carbonyl Skia back-end\n+    auto blob = SkTextBlob::MakeFromString(\n+      base64.c_str(),\n+      PrimaryFont()->\n+        PlatformData().\n+        CreateSkFont(false, &font_description_)\n+    );\n+\n+    if (node_id != cc::kInvalidNodeId) {\n+      canvas->drawTextBlob(blob, point.x(), point.y(), node_id, flags);\n+    } else {\n+      canvas->drawTextBlob(blob, point.x(), point.y(), flags);\n+    }\n+\n+    return;\n+  }\n+\n   CachingWordShaper word_shaper(*this);\n   ShapeResultBuffer buffer;\n   word_shaper.FillResultBuffer(run_info, &buffer);\n@@ -253,6 +285,31 @@ void Font::DrawText(cc::PaintCanvas* canvas,\n   if (ShouldSkipDrawing())\n     return;\n \n+  if (carbonyl_b64_text) {\n+    auto string = StringView(\n+      text_info.text,\n+      text_info.from,\n+      text_info.Length()\n+    ).ToString().Utf8();\n+    auto base64 = base::Base64Encode(base::as_bytes(base::make_span(string)));\n+\n+    // Bypass HarfBuzz text shaping for the Carbonyl Skia back-end\n+    auto blob = SkTextBlob::MakeFromString(\n+      base64.c_str(),\n+      PrimaryFont()->\n+        PlatformData().\n+        CreateSkFont(false, &font_description_)\n+    );\n+\n+    if (node_id != cc::kInvalidNodeId) {\n+      canvas->drawTextBlob(blob, point.x(), point.y(), node_id, flags);\n+    } else {\n+      canvas->drawTextBlob(blob, point.x(), point.y(), flags);\n+    }\n+\n+    return;\n+  }\n+\n   ShapeResultBloberizer::FillGlyphsNG bloberizer(\n       GetFontDescription(), device_scale_factor > 1.0f, text_info.text,\n       text_info.from, text_info.to, text_info.shape_result,\n"
  },
  {
    "path": "chromium/patches/chromium/0003-Setup-shared-software-rendering-surface.patch",
    "content": "From eea9662f4ba08a655057143d320e4e692fd92469 Mon Sep 17 00:00:00 2001\nFrom: Fathy Boundjadj <hey@fathy.fr>\nDate: Thu, 9 Feb 2023 03:24:29 +0100\nSubject: [PATCH 03/14] Setup shared software rendering surface\n\n---\n components/viz/host/host_display_client.cc       |  4 +++-\n components/viz/host/host_display_client.h        |  6 ++++--\n .../viz/host/layered_window_updater_impl.cc      |  2 +-\n .../viz/host/layered_window_updater_impl.h       |  2 +-\n .../output_surface_provider_impl.cc              | 16 ++++++++++++++++\n .../compositor/viz_process_transport_factory.cc  |  4 +++-\n .../mojom/compositing/display_private.mojom      |  1 -\n .../compositing/layered_window_updater.mojom     |  2 +-\n ui/compositor/compositor.h                       | 16 ++++++++++++++++\n 9 files changed, 45 insertions(+), 8 deletions(-)\n\ndiff --git a/components/viz/host/host_display_client.cc b/components/viz/host/host_display_client.cc\nindex 6d905b62e6258..bfb803829c73b 100644\n--- a/components/viz/host/host_display_client.cc\n+++ b/components/viz/host/host_display_client.cc\n@@ -47,9 +47,9 @@ void HostDisplayClient::OnDisplayReceivedCALayerParams(\n }\n #endif\n \n-#if BUILDFLAG(IS_WIN)\n void HostDisplayClient::CreateLayeredWindowUpdater(\n     mojo::PendingReceiver<mojom::LayeredWindowUpdater> receiver) {\n+#if BUILDFLAG(IS_WIN)\n   if (!NeedsToUseLayerWindow(widget_)) {\n     DLOG(ERROR) << \"HWND shouldn't be using a layered window\";\n     return;\n@@ -57,7 +57,9 @@ void HostDisplayClient::CreateLayeredWindowUpdater(\n \n   layered_window_updater_ =\n       std::make_unique<LayeredWindowUpdaterImpl>(widget_, std::move(receiver));\n+#endif\n }\n+#if BUILDFLAG(IS_WIN)\n void HostDisplayClient::AddChildWindowToBrowser(\n     gpu::SurfaceHandle child_window) {\n   NOTREACHED();\ndiff --git a/components/viz/host/host_display_client.h b/components/viz/host/host_display_client.h\nindex 5eeaadec9773f..f3409f0eeda03 100644\n--- a/components/viz/host/host_display_client.h\n+++ b/components/viz/host/host_display_client.h\n@@ -47,11 +47,13 @@ class VIZ_HOST_EXPORT HostDisplayClient : public mojom::DisplayClient {\n #endif\n \n #if BUILDFLAG(IS_WIN)\n-  void CreateLayeredWindowUpdater(\n-      mojo::PendingReceiver<mojom::LayeredWindowUpdater> receiver) override;\n   void AddChildWindowToBrowser(gpu::SurfaceHandle child_window) override;\n #endif\n \n+  protected:\n+  void CreateLayeredWindowUpdater(\n+      mojo::PendingReceiver<mojom::LayeredWindowUpdater> receiver) override;\n+\n // TODO(crbug.com/1052397): Revisit the macro expression once build flag switch\n // of lacros-chrome is complete.\n #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)\ndiff --git a/components/viz/host/layered_window_updater_impl.cc b/components/viz/host/layered_window_updater_impl.cc\nindex 271486b45dcc8..a62210d8ca3c8 100644\n--- a/components/viz/host/layered_window_updater_impl.cc\n+++ b/components/viz/host/layered_window_updater_impl.cc\n@@ -44,7 +44,7 @@ void LayeredWindowUpdaterImpl::OnAllocatedSharedMemory(\n   // |region|'s handle will close when it goes out of scope.\n }\n \n-void LayeredWindowUpdaterImpl::Draw(DrawCallback draw_callback) {\n+void LayeredWindowUpdaterImpl::Draw(const gfx::Rect& damage_rect, DrawCallback draw_callback) {\n   TRACE_EVENT0(\"viz\", \"LayeredWindowUpdaterImpl::Draw\");\n \n   if (!canvas_) {\ndiff --git a/components/viz/host/layered_window_updater_impl.h b/components/viz/host/layered_window_updater_impl.h\nindex 8af69cac78b74..9f74e511c263d 100644\n--- a/components/viz/host/layered_window_updater_impl.h\n+++ b/components/viz/host/layered_window_updater_impl.h\n@@ -38,7 +38,7 @@ class VIZ_HOST_EXPORT LayeredWindowUpdaterImpl\n   // mojom::LayeredWindowUpdater implementation.\n   void OnAllocatedSharedMemory(const gfx::Size& pixel_size,\n                                base::UnsafeSharedMemoryRegion region) override;\n-  void Draw(DrawCallback draw_callback) override;\n+  void Draw(const gfx::Rect& damage_rect, DrawCallback draw_callback) override;\n \n  private:\n   const HWND hwnd_;\ndiff --git a/components/viz/service/display_embedder/output_surface_provider_impl.cc b/components/viz/service/display_embedder/output_surface_provider_impl.cc\nindex d8f25c1435d4b..2929ebd3887c2 100644\n--- a/components/viz/service/display_embedder/output_surface_provider_impl.cc\n+++ b/components/viz/service/display_embedder/output_surface_provider_impl.cc\n@@ -16,6 +16,7 @@\n #include \"build/build_config.h\"\n #include \"build/chromecast_buildflags.h\"\n #include \"build/chromeos_buildflags.h\"\n+#include \"carbonyl/src/browser/software_output_device_proxy.h\"\n #include \"cc/base/switches.h\"\n #include \"components/viz/common/display/renderer_settings.h\"\n #include \"components/viz/common/frame_sinks/begin_frame_source.h\"\n@@ -29,6 +30,7 @@\n #include \"gpu/command_buffer/service/scheduler_sequence.h\"\n #include \"gpu/config/gpu_finch_features.h\"\n #include \"gpu/ipc/common/surface_handle.h\"\n+#include \"services/viz/privileged/mojom/compositing/layered_window_updater.mojom.h\"\n #include \"ui/base/ui_base_switches.h\"\n \n #if BUILDFLAG(IS_WIN)\n@@ -134,10 +136,24 @@ std::unique_ptr<OutputSurface> OutputSurfaceProviderImpl::CreateOutputSurface(\n   }\n }\n \n+namespace {\n+  static const bool use_layered_window = true;\n+}\n+\n std::unique_ptr<SoftwareOutputDevice>\n OutputSurfaceProviderImpl::CreateSoftwareOutputDeviceForPlatform(\n     gpu::SurfaceHandle surface_handle,\n     mojom::DisplayClient* display_client) {\n+// #if !BUILDFLAG(IS_APPLE)\n+  if (use_layered_window) {\n+    DCHECK(display_client);\n+    mojo::PendingRemote<mojom::LayeredWindowUpdater> layered_window_updater;\n+    display_client->CreateLayeredWindowUpdater(\n+        layered_window_updater.InitWithNewPipeAndPassReceiver());\n+    return std::make_unique<SoftwareOutputDeviceProxy>(\n+        std::move(layered_window_updater));\n+  }\n+// #endif\n   if (headless_)\n     return std::make_unique<SoftwareOutputDevice>();\n \ndiff --git a/content/browser/compositor/viz_process_transport_factory.cc b/content/browser/compositor/viz_process_transport_factory.cc\nindex 3b44531f2618f..fae71c1c19a4f 100644\n--- a/content/browser/compositor/viz_process_transport_factory.cc\n+++ b/content/browser/compositor/viz_process_transport_factory.cc\n@@ -53,6 +53,8 @@\n #include \"ui/gfx/win/rendering_window_manager.h\"\n #endif\n \n+#include \"carbonyl/src/browser/host_display_client.h\"\n+\n namespace content {\n namespace {\n \n@@ -400,7 +402,7 @@ void VizProcessTransportFactory::OnEstablishedGpuChannel(\n   root_params->display_private =\n       display_private.BindNewEndpointAndPassReceiver();\n   compositor_data.display_client =\n-      std::make_unique<HostDisplayClient>(compositor);\n+      std::make_unique<carbonyl::HostDisplayClient>();\n   root_params->display_client =\n       compositor_data.display_client->GetBoundRemote(resize_task_runner_);\n   mojo::AssociatedRemote<viz::mojom::ExternalBeginFrameController>\ndiff --git a/services/viz/privileged/mojom/compositing/display_private.mojom b/services/viz/privileged/mojom/compositing/display_private.mojom\nindex 52f44a31de4a8..65b938d76e430 100644\n--- a/services/viz/privileged/mojom/compositing/display_private.mojom\n+++ b/services/viz/privileged/mojom/compositing/display_private.mojom\n@@ -103,7 +103,6 @@ interface DisplayClient {\n \n   // Creates a LayeredWindowUpdater implementation to draw into a layered\n   // window.\n-  [EnableIf=is_win]\n   CreateLayeredWindowUpdater(pending_receiver<LayeredWindowUpdater> receiver);\n \n   // Sends the created child window to the browser process so that it can be\ndiff --git a/services/viz/privileged/mojom/compositing/layered_window_updater.mojom b/services/viz/privileged/mojom/compositing/layered_window_updater.mojom\nindex 2f462f0deb5fc..695869b83cefa 100644\n--- a/services/viz/privileged/mojom/compositing/layered_window_updater.mojom\n+++ b/services/viz/privileged/mojom/compositing/layered_window_updater.mojom\n@@ -26,5 +26,5 @@ interface LayeredWindowUpdater {\n   // Draws to the HWND by copying pixels from shared memory. Callback must be\n   // called after draw operation is complete to signal shared memory can be\n   // modified.\n-  Draw() => ();\n+  Draw(gfx.mojom.Rect damage_rect) => ();\n };\ndiff --git a/ui/compositor/compositor.h b/ui/compositor/compositor.h\nindex 50cea82c6b477..f024e6013bfb9 100644\n--- a/ui/compositor/compositor.h\n+++ b/ui/compositor/compositor.h\n@@ -87,6 +87,7 @@ class DisplayPrivate;\n class ExternalBeginFrameController;\n }  // namespace mojom\n class ContextProvider;\n+class HostDisplayClient;\n class HostFrameSinkManager;\n class LocalSurfaceId;\n class RasterContextProvider;\n@@ -143,6 +144,16 @@ class COMPOSITOR_EXPORT ContextFactory {\n   virtual viz::HostFrameSinkManager* GetHostFrameSinkManager() = 0;\n };\n \n+class COMPOSITOR_EXPORT CompositorDelegate {\n+ public:\n+  virtual bool IsOffscreen() const = 0;\n+  virtual std::unique_ptr<viz::HostDisplayClient> CreateHostDisplayClient(\n+      ui::Compositor* compositor) = 0;\n+\n+ protected:\n+  virtual ~CompositorDelegate() {}\n+};\n+\n // Compositor object to take care of GPU painting.\n // A Browser compositor object is responsible for generating the final\n // displayable form of pixels comprising a single widget's contents. It draws an\n@@ -186,6 +197,9 @@ class COMPOSITOR_EXPORT Compositor : public base::PowerSuspendObserver,\n   // Schedules a redraw of the layer tree associated with this compositor.\n   void ScheduleDraw();\n \n+  CompositorDelegate* delegate() const { return delegate_; }\n+  void SetDelegate(CompositorDelegate* delegate) { delegate_ = delegate; }\n+\n   // Sets the root of the layer tree drawn by this Compositor. The root layer\n   // must have no parent. The compositor's root layer is reset if the root layer\n   // is destroyed. NULL can be passed to reset the root layer, in which case the\n@@ -503,6 +517,8 @@ class COMPOSITOR_EXPORT Compositor : public base::PowerSuspendObserver,\n \n   std::unique_ptr<PendingBeginFrameArgs> pending_begin_frame_args_;\n \n+  CompositorDelegate* delegate_ = nullptr;\n+\n   // The root of the Layer tree drawn by this compositor.\n   raw_ptr<Layer> root_layer_ = nullptr;\n \n"
  },
  {
    "path": "chromium/patches/chromium/0004-Setup-browser-default-settings.patch",
    "content": "From c960c9b1f7ef3f16b27e4eaa4896e3563c88ea91 Mon Sep 17 00:00:00 2001\nFrom: Fathy Boundjadj <hey@fathy.fr>\nDate: Thu, 9 Feb 2023 03:27:27 +0100\nSubject: [PATCH 04/14] Setup browser default settings\n\n---\n headless/public/headless_browser.cc | 4 ++--\n headless/public/headless_browser.h  | 4 ++--\n 2 files changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/headless/public/headless_browser.cc b/headless/public/headless_browser.cc\nindex b6c70ecb0fc23..c836a082d2e68 100644\n--- a/headless/public/headless_browser.cc\n+++ b/headless/public/headless_browser.cc\n@@ -22,14 +22,14 @@ namespace headless {\n \n namespace {\n // Product name for building the default user agent string.\n-const char kHeadlessProductName[] = \"HeadlessChrome\";\n+const char kHeadlessProductName[] = \"Google Chrome\";\n constexpr gfx::Size kDefaultWindowSize(800, 600);\n \n constexpr gfx::FontRenderParams::Hinting kDefaultFontRenderHinting =\n     gfx::FontRenderParams::Hinting::HINTING_FULL;\n \n std::string GetProductNameAndVersion() {\n-  return std::string(kHeadlessProductName) + \"/\" + PRODUCT_VERSION;\n+  return std::string(kHeadlessProductName) + \"/\" + PRODUCT_VERSION + \" (Carbonyl)\";\n }\n }  // namespace\n \ndiff --git a/headless/public/headless_browser.h b/headless/public/headless_browser.h\nindex 48efaa7d57ca2..afc0236147519 100644\n--- a/headless/public/headless_browser.h\n+++ b/headless/public/headless_browser.h\n@@ -176,10 +176,10 @@ struct HEADLESS_EXPORT HeadlessBrowser::Options {\n   base::FilePath user_data_dir;\n \n   // Run a browser context in an incognito mode. Enabled by default.\n-  bool incognito_mode = true;\n+  bool incognito_mode = false;\n \n   // If true, then all pop-ups and calls to window.open will fail.\n-  bool block_new_web_contents = false;\n+  bool block_new_web_contents = true;\n \n   // Whether or not BeginFrames will be issued over DevTools protocol\n   // (experimental).\n"
  },
  {
    "path": "chromium/patches/chromium/0005-Remove-some-debug-assertions.patch",
    "content": "From 481ff19118891fe65e80b8be0e1f4498874d3b56 Mon Sep 17 00:00:00 2001\nFrom: Fathy Boundjadj <hey@fathy.fr>\nDate: Thu, 9 Feb 2023 03:28:35 +0100\nSubject: [PATCH 05/14] Remove some debug assertions\n\n---\n .../browser/web_contents/web_contents_impl.cc |  1 -\n .../core/v8/script_promise_resolver.cc        | 44 +++++++++----------\n .../compositing/paint_artifact_compositor.cc  |  2 -\n .../platform/graphics/graphics_context.cc     | 16 +++----\n 4 files changed, 30 insertions(+), 33 deletions(-)\n\ndiff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc\nindex 74749758894a2..4eb891c32b474 100644\n--- a/content/browser/web_contents/web_contents_impl.cc\n+++ b/content/browser/web_contents/web_contents_impl.cc\n@@ -5988,7 +5988,6 @@ void WebContentsImpl::DidNavigateMainFramePreCommit(\n \n   if (IsFullscreen())\n     ExitFullscreen(false);\n-  DCHECK(!IsFullscreen());\n \n   // Clean up keyboard lock state when navigating.\n   CancelKeyboardLock(keyboard_lock_widget_);\ndiff --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\nindex c3176f4937c21..56d34529dedfa 100644\n--- a/third_party/blink/renderer/bindings/core/v8/script_promise_resolver.cc\n+++ b/third_party/blink/renderer/bindings/core/v8/script_promise_resolver.cc\n@@ -58,28 +58,28 @@ ScriptPromiseResolver::ScriptPromiseResolver(\n ScriptPromiseResolver::~ScriptPromiseResolver() = default;\n \n void ScriptPromiseResolver::Dispose() {\n-#if DCHECK_IS_ON()\n-  // This assertion fails if:\n-  //  - promise() is called at least once and\n-  //  - this resolver is destructed before it is resolved, rejected,\n-  //    detached, the V8 isolate is terminated or the associated\n-  //    ExecutionContext is stopped.\n-  const bool is_properly_detached =\n-      state_ == kDetached || !is_promise_called_ ||\n-      !GetScriptState()->ContextIsValid() || !GetExecutionContext() ||\n-      GetExecutionContext()->IsContextDestroyed();\n-  if (!is_properly_detached && !suppress_detach_check_) {\n-    // This is here to make it easier to track down which promise resolvers are\n-    // being abandoned. See https://crbug.com/873980.\n-    static crash_reporter::CrashKeyString<1024> trace_key(\n-        \"scriptpromiseresolver-trace\");\n-    crash_reporter::SetCrashKeyStringToStackTrace(&trace_key,\n-                                                  create_stack_trace_);\n-    DCHECK(false)\n-        << \"ScriptPromiseResolver was not properly detached; created at\\n\"\n-        << create_stack_trace_.ToString();\n-  }\n-#endif\n+// #if DCHECK_IS_ON()\n+//   // This assertion fails if:\n+//   //  - promise() is called at least once and\n+//   //  - this resolver is destructed before it is resolved, rejected,\n+//   //    detached, the V8 isolate is terminated or the associated\n+//   //    ExecutionContext is stopped.\n+//   const bool is_properly_detached =\n+//       state_ == kDetached || !is_promise_called_ ||\n+//       !GetScriptState()->ContextIsValid() || !GetExecutionContext() ||\n+//       GetExecutionContext()->IsContextDestroyed();\n+//   if (!is_properly_detached && !suppress_detach_check_) {\n+//     // This is here to make it easier to track down which promise resolvers are\n+//     // being abandoned. See https://crbug.com/873980.\n+//     static crash_reporter::CrashKeyString<1024> trace_key(\n+//         \"scriptpromiseresolver-trace\");\n+//     crash_reporter::SetCrashKeyStringToStackTrace(&trace_key,\n+//                                                   create_stack_trace_);\n+//     DCHECK(false)\n+//         << \"ScriptPromiseResolver was not properly detached; created at\\n\"\n+//         << create_stack_trace_.ToString();\n+//   }\n+// #endif\n   deferred_resolve_task_.Cancel();\n }\n \ndiff --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\nindex d3131a4e07ece..a9464abd86a69 100644\n--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc\n+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc\n@@ -196,7 +196,6 @@ bool NeedsFullUpdateAfterPaintingChunk(\n     // properties are changed, which would indicate a missing call to\n     // SetNeedsUpdate.\n     if (previous.properties != repainted.properties) {\n-      NOTREACHED();\n       return true;\n     }\n \n@@ -253,7 +252,6 @@ bool NeedsFullUpdateAfterPaintingChunk(\n   // properties are changed, which would indicate a missing call to\n   // SetNeedsUpdate.\n   if (previous.properties != repainted.properties) {\n-    NOTREACHED();\n     return true;\n   }\n \ndiff --git a/third_party/blink/renderer/platform/graphics/graphics_context.cc b/third_party/blink/renderer/platform/graphics/graphics_context.cc\nindex 2518b71275670..3a1b8e6646c43 100644\n--- a/third_party/blink/renderer/platform/graphics/graphics_context.cc\n+++ b/third_party/blink/renderer/platform/graphics/graphics_context.cc\n@@ -146,14 +146,14 @@ GraphicsContext::GraphicsContext(PaintController& paint_controller)\n }\n \n GraphicsContext::~GraphicsContext() {\n-#if DCHECK_IS_ON()\n-  if (!disable_destruction_checks_) {\n-    DCHECK(!paint_state_index_);\n-    DCHECK(!paint_state_->SaveCount());\n-    DCHECK(!layer_count_);\n-    DCHECK(!SaveCount());\n-  }\n-#endif\n+// #if DCHECK_IS_ON()\n+//   if (!disable_destruction_checks_) {\n+//     DCHECK(!paint_state_index_);\n+//     DCHECK(!paint_state_->SaveCount());\n+//     DCHECK(!layer_count_);\n+//     DCHECK(!SaveCount());\n+//   }\n+// #endif\n }\n \n void GraphicsContext::CopyConfigFrom(GraphicsContext& other) {\n"
  },
  {
    "path": "chromium/patches/chromium/0006-Setup-display-DPI.patch",
    "content": "From cc9c37adb3ad2613a114bd37e1fde43f83951d88 Mon Sep 17 00:00:00 2001\nFrom: Fathy Boundjadj <hey@fathy.fr>\nDate: Sun, 12 Feb 2023 01:00:43 +0100\nSubject: [PATCH 06/14] Setup display DPI\n\n---\n .../lib/browser/headless_browser_impl_aura.cc | 11 ++--\n headless/lib/browser/headless_screen.cc       |  5 +-\n ui/display/display.cc                         | 52 ++++++++++---------\n 3 files changed, 35 insertions(+), 33 deletions(-)\n\ndiff --git a/headless/lib/browser/headless_browser_impl_aura.cc b/headless/lib/browser/headless_browser_impl_aura.cc\nindex 81261215c702f..508660db32151 100644\n--- a/headless/lib/browser/headless_browser_impl_aura.cc\n+++ b/headless/lib/browser/headless_browser_impl_aura.cc\n@@ -19,6 +19,8 @@\n #include \"ui/events/devices/device_data_manager.h\"\n #include \"ui/gfx/geometry/rect.h\"\n \n+#include \"carbonyl/src/browser/bridge.h\"\n+\n namespace headless {\n \n void HeadlessBrowserImpl::PlatformInitialize() {\n@@ -57,13 +59,8 @@ void HeadlessBrowserImpl::PlatformSetWebContentsBounds(\n     const gfx::Rect& bounds) {\n   // Browser's window bounds should contain all web contents, so that we're sure\n   // that we will actually produce visible damage when taking a screenshot.\n-  gfx::Rect old_host_bounds =\n-      web_contents->window_tree_host()->GetBoundsInPixels();\n-  gfx::Rect new_host_bounds(\n-      0, 0, std::max(old_host_bounds.width(), bounds.x() + bounds.width()),\n-      std::max(old_host_bounds.height(), bounds.y() + bounds.height()));\n-  web_contents->window_tree_host()->SetBoundsInPixels(new_host_bounds);\n-  web_contents->window_tree_host()->window()->SetBounds(new_host_bounds);\n+  web_contents->window_tree_host()->SetBoundsInPixels(ScaleToEnclosedRect(bounds, carbonyl::Renderer::GetDPI()));\n+  web_contents->window_tree_host()->window()->SetBounds(bounds);\n \n   gfx::NativeView native_view = web_contents->web_contents()->GetNativeView();\n   native_view->SetBounds(bounds);\ndiff --git a/headless/lib/browser/headless_screen.cc b/headless/lib/browser/headless_screen.cc\nindex 28f1a65f6dce5..8bf00ef5e036a 100644\n--- a/headless/lib/browser/headless_screen.cc\n+++ b/headless/lib/browser/headless_screen.cc\n@@ -13,6 +13,8 @@\n #include \"ui/gfx/geometry/size_conversions.h\"\n #include \"ui/gfx/native_widget_types.h\"\n \n+#include \"carbonyl/src/browser/bridge.h\"\n+\n namespace headless {\n \n // static\n@@ -49,7 +51,8 @@ display::Display HeadlessScreen::GetDisplayNearestWindow(\n HeadlessScreen::HeadlessScreen(const gfx::Rect& screen_bounds) {\n   static int64_t synthesized_display_id = 2000;\n   display::Display display(synthesized_display_id++);\n-  display.SetScaleAndBounds(1.0f, screen_bounds);\n+  float dpi = carbonyl::Renderer::GetDPI();\n+  display.SetScaleAndBounds(dpi, ScaleToEnclosedRect(screen_bounds, dpi));\n   ProcessDisplayChanged(display, true /* is_primary */);\n }\n \ndiff --git a/ui/display/display.cc b/ui/display/display.cc\nindex 466ef1fd1fe6e..1d71f3b4c9857 100644\n--- a/ui/display/display.cc\n+++ b/ui/display/display.cc\n@@ -21,6 +21,8 @@\n #include \"ui/gfx/geometry/transform.h\"\n #include \"ui/gfx/icc_profile.h\"\n \n+#include \"carbonyl/src/browser/bridge.h\"\n+\n namespace display {\n namespace {\n \n@@ -39,22 +41,22 @@ float g_forced_device_scale_factor = -1.0;\n constexpr float kDisplaySizeAllowanceEpsilon = 0.01f;\n \n bool HasForceDeviceScaleFactorImpl() {\n-  return base::CommandLine::ForCurrentProcess()->HasSwitch(\n-      switches::kForceDeviceScaleFactor);\n+  // return base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kForceDeviceScaleFactor);\n+  return true;\n }\n \n float GetForcedDeviceScaleFactorImpl() {\n-  double scale_in_double = 1.0;\n-  if (HasForceDeviceScaleFactorImpl()) {\n-    std::string value =\n-        base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(\n-            switches::kForceDeviceScaleFactor);\n-    if (!base::StringToDouble(value, &scale_in_double)) {\n-      LOG(ERROR) << \"Failed to parse the default device scale factor:\" << value;\n-      scale_in_double = 1.0;\n-    }\n-  }\n-  return static_cast<float>(scale_in_double);\n+  // double scale_in_double = 1.0;\n+  // if (HasForceDeviceScaleFactorImpl()) {\n+  //   std::string value =\n+  //       base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(\n+  //           switches::kForceDeviceScaleFactor);\n+  //   if (!base::StringToDouble(value, &scale_in_double)) {\n+  //     LOG(ERROR) << \"Failed to parse the default device scale factor:\" << value;\n+  //     scale_in_double = 1.0;\n+  //   }\n+  // }\n+  return carbonyl::Bridge::GetCurrent()->GetDPI();\n }\n \n const char* ToRotationString(display::Display::Rotation rotation) {\n@@ -97,11 +99,11 @@ void Display::ResetForceDeviceScaleFactorForTesting() {\n // static\n void Display::SetForceDeviceScaleFactor(double dsf) {\n   // Reset any previously set values and unset the flag.\n-  g_has_forced_device_scale_factor = -1;\n-  g_forced_device_scale_factor = -1.0;\n+  // g_has_forced_device_scale_factor = -1;\n+  // g_forced_device_scale_factor = -1.0;\n \n-  base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(\n-      switches::kForceDeviceScaleFactor, base::StringPrintf(\"%.2f\", dsf));\n+  // base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(\n+  //     switches::kForceDeviceScaleFactor, base::StringPrintf(\"%.2f\", dsf));\n }\n \n // static\n@@ -273,15 +275,15 @@ void Display::SetScaleAndBounds(float device_scale_factor,\n }\n \n void Display::SetScale(float device_scale_factor) {\n-  if (!HasForceDeviceScaleFactor()) {\n-#if BUILDFLAG(IS_APPLE)\n-    // Unless an explicit scale factor was provided for testing, ensure the\n-    // scale is integral.\n-    device_scale_factor = static_cast<int>(device_scale_factor);\n-#endif\n+//   if (!HasForceDeviceScaleFactor()) {\n+// #if BUILDFLAG(IS_APPLE)\n+//     // Unless an explicit scale factor was provided for testing, ensure the\n+//     // scale is integral.\n+//     device_scale_factor = static_cast<int>(device_scale_factor);\n+// #endif\n     device_scale_factor_ = device_scale_factor;\n-  }\n-  device_scale_factor_ = std::max(0.5f, device_scale_factor_);\n+  // }\n+  // device_scale_factor_ = std::max(0.5f, device_scale_factor_);\n }\n \n void Display::SetSize(const gfx::Size& size_in_pixel) {\n"
  },
  {
    "path": "chromium/patches/chromium/0007-Disable-text-effects.patch",
    "content": "From 022ed4d808369659eab4e83cd677eb974215c58c Mon Sep 17 00:00:00 2001\nFrom: Fathy Boundjadj <hey@fathy.fr>\nDate: Thu, 9 Feb 2023 03:31:17 +0100\nSubject: [PATCH 07/14] Disable text effects\n\n---\n .../core/paint/ng/ng_text_painter_base.cc     | 30 ++++++++--------\n ui/gfx/render_text.cc                         | 34 +++++++++----------\n 2 files changed, 32 insertions(+), 32 deletions(-)\n\ndiff --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\nindex 008d80040e719..c11da51d0e906 100644\n--- a/third_party/blink/renderer/core/paint/ng/ng_text_painter_base.cc\n+++ b/third_party/blink/renderer/core/paint/ng/ng_text_painter_base.cc\n@@ -123,21 +123,21 @@ void NGTextPainterBase::PaintUnderOrOverLineDecorations(\n       continue;\n     }\n \n-    if (decoration_info.HasUnderline() && decoration_info.FontData() &&\n-        EnumHasFlags(lines_to_paint, TextDecorationLine::kUnderline)) {\n-      decoration_info.SetUnderlineLineData(decoration_offset);\n-      PaintDecorationUnderOrOverLine(fragment_paint_info, context,\n-                                     decoration_info,\n-                                     TextDecorationLine::kUnderline, flags);\n-    }\n-\n-    if (decoration_info.HasOverline() && decoration_info.FontData() &&\n-        EnumHasFlags(lines_to_paint, TextDecorationLine::kOverline)) {\n-      decoration_info.SetOverlineLineData(decoration_offset);\n-      PaintDecorationUnderOrOverLine(fragment_paint_info, context,\n-                                     decoration_info,\n-                                     TextDecorationLine::kOverline, flags);\n-    }\n+    // if (decoration_info.HasUnderline() && decoration_info.FontData() &&\n+    //     EnumHasFlags(lines_to_paint, TextDecorationLine::kUnderline)) {\n+    //   decoration_info.SetUnderlineLineData(decoration_offset);\n+    //   PaintDecorationUnderOrOverLine(fragment_paint_info, context,\n+    //                                  decoration_info,\n+    //                                  TextDecorationLine::kUnderline, flags);\n+    // }\n+\n+    // if (decoration_info.HasOverline() && decoration_info.FontData() &&\n+    //     EnumHasFlags(lines_to_paint, TextDecorationLine::kOverline)) {\n+    //   decoration_info.SetOverlineLineData(decoration_offset);\n+    //   PaintDecorationUnderOrOverLine(fragment_paint_info, context,\n+    //                                  decoration_info,\n+    //                                  TextDecorationLine::kOverline, flags);\n+    // }\n   }\n }\n \ndiff --git a/ui/gfx/render_text.cc b/ui/gfx/render_text.cc\nindex 67fbf128ea158..a645ba61c8597 100644\n--- a/ui/gfx/render_text.cc\n+++ b/ui/gfx/render_text.cc\n@@ -55,9 +55,9 @@ constexpr char16_t kEllipsisCodepoint = 0x2026;\n \n // Fraction of the text size to raise the center of a strike-through line above\n // the baseline.\n-const SkScalar kStrikeThroughOffset = (SK_Scalar1 * 65 / 252);\n+// const SkScalar kStrikeThroughOffset = (SK_Scalar1 * 65 / 252);\n // Fraction of the text size to lower an underline below the baseline.\n-const SkScalar kUnderlineOffset = (SK_Scalar1 / 9);\n+// const SkScalar kUnderlineOffset = (SK_Scalar1 / 9);\n \n // Float comparison needs epsilon to consider rounding errors in float\n // arithmetic. Epsilon should be dependent on the context and here, we are\n@@ -374,27 +374,27 @@ void SkiaTextRenderer::DrawUnderline(int x,\n                                      int y,\n                                      int width,\n                                      SkScalar thickness_factor) {\n-  SkScalar x_scalar = SkIntToScalar(x);\n-  const SkScalar text_size = font_.getSize();\n-  SkRect r = SkRect::MakeLTRB(\n-      x_scalar, y + text_size * kUnderlineOffset, x_scalar + width,\n-      y + (text_size *\n-           (kUnderlineOffset +\n-            (thickness_factor * RenderText::kLineThicknessFactor))));\n-  canvas_skia_->drawRect(r, flags_);\n+  // SkScalar x_scalar = SkIntToScalar(x);\n+  // const SkScalar text_size = font_.getSize();\n+  // SkRect r = SkRect::MakeLTRB(\n+  //     x_scalar, y + text_size * kUnderlineOffset, x_scalar + width,\n+  //     y + (text_size *\n+  //          (kUnderlineOffset +\n+  //           (thickness_factor * RenderText::kLineThicknessFactor))));\n+  // canvas_skia_->drawRect(r, flags_);\n }\n \n void SkiaTextRenderer::DrawStrike(int x,\n                                   int y,\n                                   int width,\n                                   SkScalar thickness_factor) {\n-  const SkScalar text_size = font_.getSize();\n-  const SkScalar height = text_size * thickness_factor;\n-  const SkScalar top = y - text_size * kStrikeThroughOffset - height / 2;\n-  SkScalar x_scalar = SkIntToScalar(x);\n-  const SkRect r =\n-      SkRect::MakeLTRB(x_scalar, top, x_scalar + width, top + height);\n-  canvas_skia_->drawRect(r, flags_);\n+  // const SkScalar text_size = font_.getSize();\n+  // const SkScalar height = text_size * thickness_factor;\n+  // const SkScalar top = y - text_size * kStrikeThroughOffset - height / 2;\n+  // SkScalar x_scalar = SkIntToScalar(x);\n+  // const SkRect r =\n+  //     SkRect::MakeLTRB(x_scalar, top, x_scalar + width, top + height);\n+  // canvas_skia_->drawRect(r, flags_);\n }\n \n StyleIterator::StyleIterator(const BreakList<SkColor>* colors,\n"
  },
  {
    "path": "chromium/patches/chromium/0008-Fix-text-layout.patch",
    "content": "From 7b1f72900f704ffecc48c66da7ccd6de205b88f7 Mon Sep 17 00:00:00 2001\nFrom: Fathy Boundjadj <hey@fathy.fr>\nDate: Thu, 9 Feb 2023 03:32:14 +0100\nSubject: [PATCH 08/14] Fix text layout\n\n---\n .../core/css/resolver/style_resolver.cc         | 17 ++++++++++++++++-\n 1 file changed, 16 insertions(+), 1 deletion(-)\n\ndiff --git a/third_party/blink/renderer/core/css/resolver/style_resolver.cc b/third_party/blink/renderer/core/css/resolver/style_resolver.cc\nindex 6207b72d17cb9..79cb8c85b697f 100644\n--- a/third_party/blink/renderer/core/css/resolver/style_resolver.cc\n+++ b/third_party/blink/renderer/core/css/resolver/style_resolver.cc\n@@ -281,7 +281,9 @@ String ComputeBaseComputedStyleDiff(const ComputedStyle* base_computed_style,\n     return g_null_atom;\n   }\n \n-  return String(\"Field diff: \") + builder.ReleaseString();\n+  // TODO(fathy): Carbonyl should properly set the computed style\n+  // return String(\"Field diff: \") + builder.ReleaseString();\n+  return g_null_atom;\n }\n #endif  // DCHECK_IS_ON()\n \n@@ -1039,6 +1041,19 @@ scoped_refptr<ComputedStyle> StyleResolver::ResolveStyle(\n     UseCounter::Count(GetDocument(), WebFeature::kHasGlyphRelativeUnits);\n   }\n \n+  auto font = state.StyleBuilder().GetFontDescription();\n+  FontFamily family;\n+\n+  family.SetFamily(\"monospace\", FontFamily::Type::kGenericFamily);\n+  font.SetFamily(family);\n+  font.SetStretch(ExtraExpandedWidthValue());\n+  font.SetKerning(FontDescription::kNoneKerning);\n+  font.SetComputedSize(11.75 / 7.0);\n+  font.SetGenericFamily(FontDescription::kMonospaceFamily);\n+  font.SetIsAbsoluteSize(true);\n+  state.StyleBuilder().SetFontDescription(font);\n+  state.StyleBuilder().SetLineHeight(Length::Fixed(14.0 / 7.0));\n+\n   state.LoadPendingResources();\n \n   // Now return the style.\n"
  },
  {
    "path": "chromium/patches/chromium/0009-Bridge-browser-into-Carbonyl-library.patch",
    "content": "From 792e123bb57b1b379b0367b2568302e2cb0dc3c9 Mon Sep 17 00:00:00 2001\nFrom: Fathy Boundjadj <hey@fathy.fr>\nDate: Thu, 9 Feb 2023 03:32:30 +0100\nSubject: [PATCH 09/14] Bridge browser into Carbonyl library\n\n---\n headless/app/headless_shell.cc                |  33 +-\n headless/app/headless_shell_main.cc           |   5 +\n headless/lib/browser/headless_browser_impl.cc | 406 +++++++++++++++++-\n headless/lib/browser/headless_browser_impl.h  |  16 +\n .../lib/browser/headless_web_contents_impl.cc |  28 ++\n .../lib/browser/headless_web_contents_impl.h  |   2 +\n 6 files changed, 462 insertions(+), 28 deletions(-)\n\ndiff --git a/headless/app/headless_shell.cc b/headless/app/headless_shell.cc\nindex e08385b9cf740..5b51c22ae1da3 100644\n--- a/headless/app/headless_shell.cc\n+++ b/headless/app/headless_shell.cc\n@@ -4,6 +4,8 @@\n \n #include \"headless/app/headless_shell.h\"\n \n+#include \"carbonyl/src/browser/bridge.h\"\n+\n #include <memory>\n \n #include \"base/base_switches.h\"\n@@ -90,6 +92,8 @@ void HeadlessShell::OnBrowserStart(HeadlessBrowser* browser) {\n   HeadlessBrowserContext::Builder context_builder =\n       browser_->CreateBrowserContextBuilder();\n \n+  context_builder.SetWindowSize(carbonyl::Renderer::Main()->GetSize());\n+\n   // Retrieve the locale set by InitApplicationLocale() in\n   // headless_content_main_delegate.cc in a way that is free of side-effects.\n   context_builder.SetAcceptLanguage(base::i18n::GetConfiguredLocale());\n@@ -113,39 +117,14 @@ void HeadlessShell::OnBrowserStart(HeadlessBrowser* browser) {\n \n   GURL target_url = ConvertArgumentToURL(args.front());\n \n-  // If driven by a debugger just open the target page and\n-  // leave expecting the debugger will do what they need.\n-  if (IsRemoteDebuggingEnabled()) {\n-    HeadlessWebContents::Builder builder(\n-        browser_context_->CreateWebContentsBuilder());\n-    HeadlessWebContents* web_contents =\n-        builder.SetInitialURL(target_url).Build();\n-    if (!web_contents) {\n-      LOG(ERROR) << \"Navigation to \" << target_url << \" failed.\";\n-      ShutdownSoon();\n-    }\n-    return;\n-  }\n-\n-  // Otherwise instantiate headless shell command handler that will\n-  // execute the commands against the target page.\n-#if defined(HEADLESS_ENABLE_COMMANDS)\n-  GURL handler_url = HeadlessCommandHandler::GetHandlerUrl();\n   HeadlessWebContents::Builder builder(\n       browser_context_->CreateWebContentsBuilder());\n   HeadlessWebContents* web_contents =\n-      builder.SetInitialURL(handler_url).Build();\n+      builder.SetInitialURL(target_url).Build();\n   if (!web_contents) {\n-    LOG(ERROR) << \"Navigation to \" << handler_url << \" failed.\";\n+    LOG(ERROR) << \"Navigation to \" << target_url << \" failed.\";\n     ShutdownSoon();\n-    return;\n   }\n-\n-  HeadlessCommandHandler::ProcessCommands(\n-      HeadlessWebContentsImpl::From(web_contents)->web_contents(),\n-      std::move(target_url),\n-      base::BindOnce(&HeadlessShell::ShutdownSoon, weak_factory_.GetWeakPtr()));\n-#endif\n }\n \n void HeadlessShell::ShutdownSoon() {\ndiff --git a/headless/app/headless_shell_main.cc b/headless/app/headless_shell_main.cc\nindex 35736145f5caf..f9b8bac5c18a5 100644\n--- a/headless/app/headless_shell_main.cc\n+++ b/headless/app/headless_shell_main.cc\n@@ -13,7 +13,12 @@\n #include \"sandbox/mac/seatbelt_exec.h\"\n #endif\n \n+#include \"base/at_exit.h\"\n+#include \"carbonyl/src/browser/bridge.h\"\n+\n int main(int argc, const char** argv) {\n+  carbonyl_shell_main();\n+\n #if BUILDFLAG(IS_WIN)\n   sandbox::SandboxInterfaceInfo sandbox_info = {nullptr};\n   content::InitializeSandboxInfo(&sandbox_info);\ndiff --git a/headless/lib/browser/headless_browser_impl.cc b/headless/lib/browser/headless_browser_impl.cc\nindex 1a1223108be6d..fd45d215479ab 100644\n--- a/headless/lib/browser/headless_browser_impl.cc\n+++ b/headless/lib/browser/headless_browser_impl.cc\n@@ -7,6 +7,8 @@\n #include <string>\n #include <utility>\n #include <vector>\n+#include <iostream>\n+#include <signal.h>\n \n #include \"base/callback_helpers.h\"\n #include \"base/command_line.h\"\n@@ -27,6 +29,23 @@\n #include \"services/network/public/cpp/network_switches.h\"\n #include \"ui/events/devices/device_data_manager.h\"\n \n+#include \"content/public/browser/render_frame_host.h\"\n+#include \"content/public/browser/render_view_host.h\"\n+#include \"content/public/browser/render_widget_host.h\"\n+#include \"content/public/browser/web_contents.h\"\n+#include \"carbonyl/src/browser/bridge.h\"\n+#include \"third_party/blink/public/common/input/web_mouse_event.h\"\n+#include \"third_party/blink/public/common/input/web_mouse_wheel_event.h\"\n+#include \"ui/events/keycodes/keyboard_codes.h\"\n+\n+namespace carbonyl {\n+\n+static unsigned int current_mouse_x = 0;\n+static unsigned int current_mouse_y = 0;\n+static headless::HeadlessBrowserImpl* browser = nullptr;\n+\n+}\n+\n namespace headless {\n \n HeadlessBrowserImpl::HeadlessBrowserImpl(\n@@ -38,7 +57,15 @@ HeadlessBrowserImpl::HeadlessBrowserImpl(\n       default_browser_context_(nullptr),\n       agent_host_(nullptr) {}\n \n-HeadlessBrowserImpl::~HeadlessBrowserImpl() = default;\n+HeadlessBrowserImpl::~HeadlessBrowserImpl() {\n+  if (carbonyl::browser == this) {\n+    carbonyl::browser = nullptr;\n+  }\n+\n+  if (input_thread_.joinable()) {\n+    input_thread_.join();\n+  }\n+}\n \n HeadlessBrowserContext::Builder\n HeadlessBrowserImpl::CreateBrowserContextBuilder() {\n@@ -91,6 +118,307 @@ void HeadlessBrowserImpl::set_browser_main_parts(\n   browser_main_parts_ = browser_main_parts;\n }\n \n+void HeadlessBrowserImpl::Resize() {\n+  auto size = carbonyl::Renderer::GetSize();\n+  auto rect = gfx::Rect(0, 0, size.width(), size.height());\n+\n+  for (auto* ctx: GetAllBrowserContexts()) {\n+    for (auto* contents: ctx->GetAllWebContents()) {\n+      auto* impl = HeadlessWebContentsImpl::From(contents);\n+\n+      if (!impl) {\n+        continue;\n+      }\n+\n+      PlatformSetWebContentsBounds(impl, rect);\n+    }\n+  }\n+\n+  carbonyl::Renderer::Main()->Resize();\n+}\n+\n+void HeadlessBrowserImpl::OnShutdownInput() {\n+  Shutdown();\n+}\n+void HeadlessBrowserImpl::OnRefreshInput() {\n+  for (auto* ctx: GetAllBrowserContexts()) {\n+    for (auto* contents: ctx->GetAllWebContents()) {\n+      auto* impl = HeadlessWebContentsImpl::From(contents);\n+\n+      if (!impl) {\n+        continue;\n+      }\n+\n+      auto& nav = impl->web_contents()->GetController();\n+      \n+      nav.Reload(content::ReloadType::NORMAL, true);\n+    }\n+  }\n+}\n+\n+void HeadlessBrowserImpl::OnGoToInput(const char* url_str) {\n+  if (!carbonyl::browser) {\n+    return;\n+  }\n+\n+  auto ctxs = GetAllBrowserContexts();\n+  \n+  if (ctxs.empty()) {\n+    return;\n+  }\n+\n+  auto url = GURL(std::string(url_str));\n+\n+  if (!url.is_valid() || url.spec().size() > url::kMaxURLChars) {\n+    return;\n+  }\n+\n+  auto* ctx = ctxs[0];\n+  auto contents = ctx->GetAllWebContents();\n+\n+  if (contents.empty()) {\n+    ctx->CreateWebContentsBuilder().SetInitialURL(url).Build();\n+  } else {\n+    HeadlessWebContentsImpl::From(contents[0])->OpenURL(url);\n+  }\n+}\n+\n+void HeadlessBrowserImpl::OnGoBackInput() {\n+  for (auto* ctx: GetAllBrowserContexts()) {\n+    for (auto* contents: ctx->GetAllWebContents()) {\n+      auto* impl = HeadlessWebContentsImpl::From(contents);\n+\n+      if (!impl) {\n+        continue;\n+      }\n+\n+      auto& nav = impl->web_contents()->GetController();\n+      \n+      if (nav.CanGoBack()) {\n+        nav.GoBack();\n+      }\n+    }\n+  }\n+}\n+void HeadlessBrowserImpl::OnGoForwardInput() {\n+  for (auto* ctx: GetAllBrowserContexts()) {\n+    for (auto* contents: ctx->GetAllWebContents()) {\n+      auto* impl = HeadlessWebContentsImpl::From(contents);\n+\n+      if (!impl) {\n+        continue;\n+      }\n+\n+      auto& nav = impl->web_contents()->GetController();\n+      \n+      if (nav.CanGoForward()) {\n+        nav.GoForward();\n+      }\n+    }\n+  }\n+}\n+\n+void HeadlessBrowserImpl::OnScrollInput(int delta) {\n+  blink::WebMouseWheelEvent event;\n+\n+  event.SetType(blink::WebInputEvent::Type::kMouseWheel);\n+  event.SetTimeStamp(base::TimeTicks::Now());\n+  event.SetPositionInWidget(carbonyl::current_mouse_x, carbonyl::current_mouse_y);\n+  event.SetPositionInScreen(carbonyl::current_mouse_x, carbonyl::current_mouse_y);\n+\n+  event.delta_y = delta;\n+  event.phase = blink::WebMouseWheelEvent::kPhaseBegan;\n+  event.dispatch_type = blink::WebInputEvent::DispatchType::kBlocking;\n+\n+  for (auto* ctx: GetAllBrowserContexts()) {\n+    for (auto* contents: ctx->GetAllWebContents()) {\n+      auto* impl = HeadlessWebContentsImpl::From(contents);\n+\n+      if (!impl) {\n+        continue;\n+      }\n+\n+      auto *host = impl->web_contents()->GetRenderViewHost()->GetWidget();\n+\n+      if (!host) {\n+        continue;\n+      }\n+\n+      host->ForwardWheelEvent(event);\n+    }\n+  }\n+\n+  // Send a synthetic wheel event with phaseEnded to finish scrolling.\n+  event.delta_y = 0;\n+  event.phase = blink::WebMouseWheelEvent::kPhaseEnded;\n+  event.dispatch_type = blink::WebInputEvent::DispatchType::kEventNonBlocking;\n+  event.has_synthetic_phase = true;\n+\n+  for (auto* ctx: GetAllBrowserContexts()) {\n+    for (auto* contents: ctx->GetAllWebContents()) {\n+      auto* impl = HeadlessWebContentsImpl::From(contents);\n+\n+      if (!impl) {\n+        continue;\n+      }\n+\n+      auto *host = impl->web_contents()->GetRenderViewHost()->GetWidget();\n+\n+      if (!host) {\n+        continue;\n+      }\n+\n+      host->ForwardWheelEvent(event);\n+    }\n+  }\n+}\n+\n+void HeadlessBrowserImpl::OnKeyPressInput(char key) {\n+  bool raw = true;\n+  content::NativeWebKeyboardEvent event(\n+      blink::WebKeyboardEvent::Type::kRawKeyDown,\n+      blink::WebInputEvent::kNoModifiers,\n+      base::TimeTicks::Now());\n+  \n+  // TODO(fathy): support IME\n+  switch (key) {\n+    case 0x11:\n+      event.windows_key_code = ui::KeyboardCode::VKEY_UP;\n+      break;\n+    case 0x12:\n+      event.windows_key_code = ui::KeyboardCode::VKEY_DOWN;\n+      break;\n+    case 0x13:\n+      event.windows_key_code = ui::KeyboardCode::VKEY_RIGHT;\n+      break;\n+    case 0x14:\n+      event.windows_key_code = ui::KeyboardCode::VKEY_LEFT;\n+      break;\n+    case 0x7f:\n+      event.windows_key_code = ui::KeyboardCode::VKEY_BACK;\n+      break;\n+    default:\n+      raw = false;\n+\n+      event.text[0] = key;\n+  }\n+\n+  for (auto* ctx: GetAllBrowserContexts()) {\n+    for (auto* contents: ctx->GetAllWebContents()) {\n+      auto* impl = HeadlessWebContentsImpl::From(contents);\n+\n+      if (!impl) {\n+        continue;\n+      }\n+\n+      auto *host = impl->web_contents()->GetRenderViewHost()->GetWidget();\n+\n+      if (!host) {\n+        continue;\n+      }\n+\n+      event.SetType(\n+        raw\n+          ? blink::WebKeyboardEvent::Type::kRawKeyDown\n+          : blink::WebKeyboardEvent::Type::kKeyDown\n+      );\n+      host->ForwardKeyboardEvent(event);\n+\n+      event.SetType(blink::WebKeyboardEvent::Type::kKeyUp);\n+      host->ForwardKeyboardEvent(event);\n+    }\n+  }\n+}\n+\n+void HeadlessBrowserImpl::OnMouseUpInput(unsigned int x, unsigned int y) {\n+  for (auto* ctx: GetAllBrowserContexts()) {\n+    for (auto* contents: ctx->GetAllWebContents()) {\n+      auto* impl = HeadlessWebContentsImpl::From(contents);\n+\n+      if (!impl) {\n+        continue;\n+      }\n+\n+      auto *host = impl->web_contents()->GetRenderViewHost()->GetWidget();\n+\n+      if (!host) {\n+        continue;\n+      }\n+\n+      blink::WebMouseEvent event;\n+\n+      event.button = blink::WebMouseEvent::Button::kLeft;\n+      event.click_count = 1;\n+      event.SetType(blink::WebInputEvent::Type::kMouseUp);\n+      event.SetTimeStamp(base::TimeTicks::Now());\n+      event.SetPositionInWidget(x, y);\n+      event.SetPositionInScreen(x, y);\n+\n+      host->ForwardMouseEvent(event);\n+    }\n+  }\n+}\n+\n+void HeadlessBrowserImpl::OnMouseDownInput(unsigned int x, unsigned int y) {\n+  for (auto* ctx: GetAllBrowserContexts()) {\n+    for (auto* contents: ctx->GetAllWebContents()) {\n+      auto* impl = HeadlessWebContentsImpl::From(contents);\n+\n+      if (!impl) {\n+        continue;\n+      }\n+\n+      auto *host = impl->web_contents()->GetRenderViewHost()->GetWidget();\n+\n+      if (!host) {\n+        continue;\n+      }\n+\n+      blink::WebMouseEvent event;\n+\n+      event.button = blink::WebMouseEvent::Button::kLeft;\n+      event.click_count = 1;\n+      event.SetType(blink::WebInputEvent::Type::kMouseDown);\n+      event.SetTimeStamp(base::TimeTicks::Now());\n+      event.SetPositionInWidget(x, y);\n+      event.SetPositionInScreen(x, y);\n+\n+      host->ForwardMouseEvent(event);\n+    }\n+  }\n+}\n+\n+void HeadlessBrowserImpl::OnMouseMoveInput(unsigned int x, unsigned int y) {\n+  for (auto* ctx: GetAllBrowserContexts()) {\n+    for (auto* contents: ctx->GetAllWebContents()) {\n+      auto* impl = HeadlessWebContentsImpl::From(contents);\n+\n+      if (!impl) {\n+        continue;\n+      }\n+\n+      auto *host = impl->web_contents()->GetRenderViewHost()->GetWidget();\n+\n+      if (!host) {\n+        continue;\n+      }\n+\n+      blink::WebMouseEvent event;\n+\n+      carbonyl::current_mouse_x = x;\n+      carbonyl::current_mouse_y = y;\n+\n+      event.click_count = 1;\n+      event.SetType(blink::WebInputEvent::Type::kMouseMove);\n+      event.SetTimeStamp(base::TimeTicks::Now());\n+      event.SetPositionInWidget(x, y);\n+      event.SetPositionInScreen(x, y);\n+\n+      host->ForwardMouseEvent(event);\n+    }\n+  }\n+}\n+\n void HeadlessBrowserImpl::RunOnStartCallback() {\n   // We don't support the tethering domain on this agent host.\n   agent_host_ = content::DevToolsAgentHost::CreateForBrowser(\n@@ -98,6 +426,82 @@ void HeadlessBrowserImpl::RunOnStartCallback() {\n \n   PlatformStart();\n   std::move(on_start_callback_).Run(this);\n+\n+  signal(SIGWINCH, [](int signal) {\n+    if (carbonyl::browser) {\n+      carbonyl::browser->Resize();\n+    }\n+  });\n+\n+  input_thread_ = std::thread([=]() {\n+    carbonyl::browser = this;\n+\n+    carbonyl_bridge_browser_delegate delegate = {\n+      .shutdown = []() {\n+        if (carbonyl::browser) {\n+          carbonyl::browser->OnShutdownInput();\n+        }\n+      },\n+      .refresh = []() {\n+        if (carbonyl::browser) {\n+          carbonyl::browser->OnRefreshInput();\n+        }\n+      },\n+      .go_to = [](const char* url) {\n+        if (carbonyl::browser) {\n+          carbonyl::browser->OnGoToInput(url);\n+        }\n+      },\n+      .go_back = []() {\n+        if (carbonyl::browser) {\n+          carbonyl::browser->OnGoBackInput();\n+        }\n+      },\n+      .go_forward = []() {\n+        if (carbonyl::browser) {\n+          carbonyl::browser->OnGoForwardInput();\n+        }\n+      },\n+      .scroll = [](int delta) {\n+        if (carbonyl::browser) {\n+          carbonyl::browser->OnScrollInput(delta);\n+        }\n+      },\n+      .key_press = [](char key) {\n+        if (carbonyl::browser) {\n+          carbonyl::browser->OnKeyPressInput(key);\n+        }\n+      },\n+      .mouse_up = [](unsigned int x, unsigned int y) {\n+        if (carbonyl::browser) {\n+          carbonyl::browser->OnMouseUpInput(x, y);\n+        }\n+      },\n+      .mouse_down = [](unsigned int x, unsigned int y) {\n+        if (carbonyl::browser) {\n+          carbonyl::browser->OnMouseDownInput(x, y);\n+        }\n+      },\n+      .mouse_move = [](unsigned int x, unsigned int y) {\n+        if (carbonyl::browser) {\n+          carbonyl::browser->OnMouseMoveInput(x, y);\n+        }\n+      },\n+      .post_task = [](void (*fn)(void*), void* data) {\n+        if (carbonyl::browser) {\n+          carbonyl::browser->BrowserMainThread()->PostTask(\n+            FROM_HERE,\n+            base::BindOnce(\n+              fn,\n+              data\n+            )\n+          );\n+        }\n+      }\n+    };\n+\n+    carbonyl::Renderer::Main()->Listen(&delegate);\n+  });\n }\n \n HeadlessBrowserContext* HeadlessBrowserImpl::CreateBrowserContext(\ndiff --git a/headless/lib/browser/headless_browser_impl.h b/headless/lib/browser/headless_browser_impl.h\nindex a2d531ab32ff5..963808352c0c4 100644\n--- a/headless/lib/browser/headless_browser_impl.h\n+++ b/headless/lib/browser/headless_browser_impl.h\n@@ -11,6 +11,7 @@\n #include <memory>\n #include <string>\n #include <vector>\n+#include <thread>\n \n #include \"base/memory/weak_ptr.h\"\n #include \"base/task/single_thread_task_runner.h\"\n@@ -121,9 +122,24 @@ class HEADLESS_EXPORT HeadlessBrowserImpl : public HeadlessBrowser,\n   policy::PolicyService* GetPolicyService();\n #endif\n \n+  void Resize();\n+  void OnShutdownInput();\n+  void OnRefreshInput();\n+  void OnGoToInput(const char* url);\n+  void OnGoBackInput();\n+  void OnGoForwardInput();\n+  void OnScrollInput(int delta);\n+  void OnKeyPressInput(char key);\n+  void OnMouseUpInput(unsigned int x, unsigned int y);\n+  void OnMouseDownInput(unsigned int x, unsigned int y);\n+  void OnMouseMoveInput(unsigned int x, unsigned int y);\n+\n   bool did_shutdown() const { return did_shutdown_; }\n \n  protected:\n+ // TODO: use base::TaskRunner\n+  std::thread input_thread_;\n+\n #if BUILDFLAG(IS_MAC)\n   std::unique_ptr<display::ScopedNativeScreen> screen_;\n #endif\ndiff --git a/headless/lib/browser/headless_web_contents_impl.cc b/headless/lib/browser/headless_web_contents_impl.cc\nindex 010ff2c94287e..fad8c3fdd2bfe 100644\n--- a/headless/lib/browser/headless_web_contents_impl.cc\n+++ b/headless/lib/browser/headless_web_contents_impl.cc\n@@ -8,6 +8,7 @@\n #include <string>\n #include <utility>\n #include <vector>\n+#include <iostream>\n \n #include \"base/bind.h\"\n #include \"base/command_line.h\"\n@@ -21,10 +22,13 @@\n #include \"base/values.h\"\n #include \"build/build_config.h\"\n #include \"build/chromeos_buildflags.h\"\n+#include \"carbonyl/src/browser/bridge.h\"\n #include \"content/public/browser/browser_thread.h\"\n #include \"content/public/browser/child_process_termination_info.h\"\n #include \"content/public/browser/devtools_agent_host.h\"\n #include \"content/public/browser/navigation_controller.h\"\n+#include \"content/public/browser/navigation_entry.h\"\n+#include \"content/public/browser/navigation_details.h\"\n #include \"content/public/browser/navigation_handle.h\"\n #include \"content/public/browser/render_frame_host.h\"\n #include \"content/public/browser/render_process_host.h\"\n@@ -390,6 +394,30 @@ void HeadlessWebContentsImpl::RenderViewReady() {\n   devtools_target_ready_notification_sent_ = true;\n }\n \n+void HeadlessWebContentsImpl::TitleWasSet(content::NavigationEntry* entry) {\n+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);\n+\n+  if (!web_contents() || !web_contents()->GetPrimaryMainFrame()->IsActive())\n+    return;\n+\n+  carbonyl::Renderer::Main()->SetTitle(base::UTF16ToUTF8(entry->GetTitleForDisplay()));\n+}\n+\n+void HeadlessWebContentsImpl::DidFinishNavigation(content::NavigationHandle* handle) {\n+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);\n+\n+  if (!handle->IsInMainFrame() || !web_contents() || !web_contents()->GetPrimaryMainFrame()->IsActive())\n+    return;\n+\n+  auto& nav = web_contents()->GetController();\n+\n+  carbonyl::Renderer::Main()->PushNav(\n+    handle->GetURL().spec(),\n+    nav.CanGoBack(),\n+    nav.CanGoForward()\n+  );\n+}\n+\n int HeadlessWebContentsImpl::GetMainFrameRenderProcessId() const {\n   if (!web_contents() || !web_contents()->GetPrimaryMainFrame())\n     return -1;\ndiff --git a/headless/lib/browser/headless_web_contents_impl.h b/headless/lib/browser/headless_web_contents_impl.h\nindex b80147fd06be8..09773596aa5ce 100644\n--- a/headless/lib/browser/headless_web_contents_impl.h\n+++ b/headless/lib/browser/headless_web_contents_impl.h\n@@ -91,6 +91,8 @@ class HEADLESS_EXPORT HeadlessWebContentsImpl\n   void RenderFrameCreated(content::RenderFrameHost* render_frame_host) override;\n   void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override;\n   void RenderViewReady() override;\n+  void TitleWasSet(content::NavigationEntry* entry) override;\n+  void DidFinishNavigation(content::NavigationHandle* navigation_handle) override;\n \n   content::WebContents* web_contents() const;\n   bool OpenURL(const GURL& url);\n"
  },
  {
    "path": "chromium/patches/chromium/0010-Conditionally-enable-text-rendering.patch",
    "content": "From bdc80f35a7113b7523c4d992edc9170db082deb0 Mon Sep 17 00:00:00 2001\nFrom: Fathy Boundjadj <hey@fathy.fr>\nDate: Sun, 12 Feb 2023 00:55:33 +0100\nSubject: [PATCH 10/14] Conditionally enable text rendering\n\n---\n content/renderer/render_frame_impl.cc         |  3 ++-\n .../core/css/resolver/style_resolver.cc       | 26 +++++++++++--------\n third_party/blink/renderer/platform/BUILD.gn  |  1 +\n .../blink/renderer/platform/fonts/font.cc     | 10 +++----\n 4 files changed, 23 insertions(+), 17 deletions(-)\n\ndiff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc\nindex 97b61ffb954be..891efd6a9d796 100644\n--- a/content/renderer/render_frame_impl.cc\n+++ b/content/renderer/render_frame_impl.cc\n@@ -259,6 +259,7 @@\n // Carbonyl\n #include <stdlib.h>\n #include <iostream>\n+#include \"carbonyl/src/browser/bridge.h\"\n #include \"cc/paint/paint_recorder.h\"\n #include \"cc/paint/skia_paint_canvas.h\"\n #include \"cc/raster/playback_image_provider.h\"\n@@ -2221,7 +2222,7 @@ void RenderFrameImpl::Initialize(blink::WebFrame* parent) {\n \n   render_callback_ = std::make_shared<std::function<bool()>>(\n     [=]() -> bool {\n-      if (!IsMainFrame() || IsHidden()) {\n+      if (!IsMainFrame() || IsHidden() || carbonyl::Bridge::BitmapMode()) {\n         return false;\n       }\n \ndiff --git a/third_party/blink/renderer/core/css/resolver/style_resolver.cc b/third_party/blink/renderer/core/css/resolver/style_resolver.cc\nindex 79cb8c85b697f..7129982acf4a6 100644\n--- a/third_party/blink/renderer/core/css/resolver/style_resolver.cc\n+++ b/third_party/blink/renderer/core/css/resolver/style_resolver.cc\n@@ -116,6 +116,8 @@\n #include \"third_party/blink/renderer/platform/wtf/text/atomic_string_hash.h\"\n #include \"third_party/blink/renderer/platform/wtf/text/string_builder.h\"\n \n+#include \"carbonyl/src/browser/bridge.h\"\n+\n namespace blink {\n \n namespace {\n@@ -1041,18 +1043,20 @@ scoped_refptr<ComputedStyle> StyleResolver::ResolveStyle(\n     UseCounter::Count(GetDocument(), WebFeature::kHasGlyphRelativeUnits);\n   }\n \n-  auto font = state.StyleBuilder().GetFontDescription();\n-  FontFamily family;\n+  if (!carbonyl::Bridge::BitmapMode()) {\n+    auto font = state.StyleBuilder().GetFontDescription();\n+    FontFamily family;\n \n-  family.SetFamily(\"monospace\", FontFamily::Type::kGenericFamily);\n-  font.SetFamily(family);\n-  font.SetStretch(ExtraExpandedWidthValue());\n-  font.SetKerning(FontDescription::kNoneKerning);\n-  font.SetComputedSize(11.75 / 7.0);\n-  font.SetGenericFamily(FontDescription::kMonospaceFamily);\n-  font.SetIsAbsoluteSize(true);\n-  state.StyleBuilder().SetFontDescription(font);\n-  state.StyleBuilder().SetLineHeight(Length::Fixed(14.0 / 7.0));\n+    family.SetFamily(\"monospace\", FontFamily::Type::kGenericFamily);\n+    font.SetFamily(family);\n+    font.SetStretch(ExtraExpandedWidthValue());\n+    font.SetKerning(FontDescription::kNoneKerning);\n+    font.SetComputedSize(13.25 / 4.0);\n+    font.SetGenericFamily(FontDescription::kMonospaceFamily);\n+    font.SetIsAbsoluteSize(true);\n+    state.StyleBuilder().SetFontDescription(font);\n+    state.StyleBuilder().SetLineHeight(Length::Fixed(16.0 / 4.0));\n+  }\n \n   state.LoadPendingResources();\n \ndiff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn\nindex e7b1c1a52e4c9..63fc13e44b5ae 100644\n--- a/third_party/blink/renderer/platform/BUILD.gn\n+++ b/third_party/blink/renderer/platform/BUILD.gn\n@@ -1678,6 +1678,7 @@ component(\"platform\") {\n     \"//base/allocator:buildflags\",\n     \"//build:chromecast_buildflags\",\n     \"//build:chromeos_buildflags\",\n+    \"//carbonyl/src/browser:carbonyl\",\n     \"//cc/ipc\",\n     \"//cc/mojo_embedder\",\n     \"//components/paint_preview/common\",\ndiff --git a/third_party/blink/renderer/platform/fonts/font.cc b/third_party/blink/renderer/platform/fonts/font.cc\nindex dfdc79eacce3b..4625300729523 100644\n--- a/third_party/blink/renderer/platform/fonts/font.cc\n+++ b/third_party/blink/renderer/platform/fonts/font.cc\n@@ -49,6 +49,8 @@\n #include \"third_party/skia/include/core/SkTextBlob.h\"\n #include \"ui/gfx/geometry/rect_f.h\"\n \n+#include \"carbonyl/src/browser/bridge.h\"\n+\n namespace blink {\n \n namespace {\n@@ -151,14 +153,12 @@ bool Font::operator==(const Font& other) const {\n \n namespace {\n \n-static const bool carbonyl_b64_text = true;\n-\n void DrawBlobs(cc::PaintCanvas* canvas,\n                const cc::PaintFlags& flags,\n                const ShapeResultBloberizer::BlobBuffer& blobs,\n                const gfx::PointF& point,\n                cc::NodeId node_id = cc::kInvalidNodeId) {  \n-  if (carbonyl_b64_text) {\n+  if (!carbonyl::Bridge::BitmapMode()) {\n     return;\n   }\n \n@@ -237,7 +237,7 @@ void Font::DrawText(cc::PaintCanvas* canvas,\n   if (ShouldSkipDrawing())\n     return;\n \n-  if (carbonyl_b64_text) {\n+  if (!carbonyl::Bridge::BitmapMode()) {\n     auto string = StringView(\n       run_info.run.ToStringView(),\n       run_info.from,\n@@ -285,7 +285,7 @@ void Font::DrawText(cc::PaintCanvas* canvas,\n   if (ShouldSkipDrawing())\n     return;\n \n-  if (carbonyl_b64_text) {\n+  if (!carbonyl::Bridge::BitmapMode()) {\n     auto string = StringView(\n       text_info.text,\n       text_info.from,\n"
  },
  {
    "path": "chromium/patches/chromium/0011-Rename-carbonyl-Renderer-to-carbonyl-Bridge.patch",
    "content": "From fa52dbb68b7822ee4c01a697197e68ef1ab4a19c Mon Sep 17 00:00:00 2001\nFrom: Fathy Boundjadj <hey@fathy.fr>\nDate: Sun, 12 Feb 2023 01:29:05 +0100\nSubject: [PATCH 11/14] Rename carbonyl::Renderer to carbonyl::Bridge\n\n---\n headless/app/headless_shell.cc                     | 5 ++++-\n headless/app/headless_shell_main.cc                | 2 +-\n headless/lib/browser/headless_browser_impl.cc      | 8 ++++----\n headless/lib/browser/headless_browser_impl_aura.cc | 2 +-\n headless/lib/browser/headless_screen.cc            | 2 +-\n headless/lib/browser/headless_web_contents_impl.cc | 4 ++--\n 6 files changed, 13 insertions(+), 10 deletions(-)\n\ndiff --git a/headless/app/headless_shell.cc b/headless/app/headless_shell.cc\nindex 5b51c22ae1da3..b6a52857e8f90 100644\n--- a/headless/app/headless_shell.cc\n+++ b/headless/app/headless_shell.cc\n@@ -12,6 +12,7 @@\n #include \"base/bind.h\"\n #include \"base/command_line.h\"\n #include \"base/files/file_util.h\"\n+#include \"base/functional/callback.h\"\n #include \"base/i18n/rtl.h\"\n #include \"base/task/thread_pool.h\"\n #include \"build/branding_buildflags.h\"\n@@ -92,7 +93,9 @@ void HeadlessShell::OnBrowserStart(HeadlessBrowser* browser) {\n   HeadlessBrowserContext::Builder context_builder =\n       browser_->CreateBrowserContextBuilder();\n \n-  context_builder.SetWindowSize(carbonyl::Renderer::Main()->GetSize());\n+  carbonyl::Bridge::GetCurrent()->StartRenderer();\n+\n+  context_builder.SetWindowSize(carbonyl::Bridge::GetCurrent()->GetSize());\n \n   // Retrieve the locale set by InitApplicationLocale() in\n   // headless_content_main_delegate.cc in a way that is free of side-effects.\ndiff --git a/headless/app/headless_shell_main.cc b/headless/app/headless_shell_main.cc\nindex f9b8bac5c18a5..739df1ae1bd58 100644\n--- a/headless/app/headless_shell_main.cc\n+++ b/headless/app/headless_shell_main.cc\n@@ -17,7 +17,7 @@\n #include \"carbonyl/src/browser/bridge.h\"\n \n int main(int argc, const char** argv) {\n-  carbonyl_shell_main();\n+  carbonyl::Bridge::Main();\n \n #if BUILDFLAG(IS_WIN)\n   sandbox::SandboxInterfaceInfo sandbox_info = {nullptr};\ndiff --git a/headless/lib/browser/headless_browser_impl.cc b/headless/lib/browser/headless_browser_impl.cc\nindex fd45d215479ab..1df3ffe72c93d 100644\n--- a/headless/lib/browser/headless_browser_impl.cc\n+++ b/headless/lib/browser/headless_browser_impl.cc\n@@ -119,7 +119,7 @@ void HeadlessBrowserImpl::set_browser_main_parts(\n }\n \n void HeadlessBrowserImpl::Resize() {\n-  auto size = carbonyl::Renderer::GetSize();\n+  auto size = carbonyl::Bridge::GetCurrent()->Resize();\n   auto rect = gfx::Rect(0, 0, size.width(), size.height());\n \n   for (auto* ctx: GetAllBrowserContexts()) {\n@@ -134,7 +134,7 @@ void HeadlessBrowserImpl::Resize() {\n     }\n   }\n \n-  carbonyl::Renderer::Main()->Resize();\n+  carbonyl::Bridge::GetCurrent()->Resize();\n }\n \n void HeadlessBrowserImpl::OnShutdownInput() {\n@@ -279,7 +279,7 @@ void HeadlessBrowserImpl::OnKeyPressInput(char key) {\n       blink::WebKeyboardEvent::Type::kRawKeyDown,\n       blink::WebInputEvent::kNoModifiers,\n       base::TimeTicks::Now());\n-  \n+\n   // TODO(fathy): support IME\n   switch (key) {\n     case 0x11:\n@@ -500,7 +500,7 @@ void HeadlessBrowserImpl::RunOnStartCallback() {\n       }\n     };\n \n-    carbonyl::Renderer::Main()->Listen(&delegate);\n+    carbonyl::Bridge::GetCurrent()->Listen(&delegate);\n   });\n }\n \ndiff --git a/headless/lib/browser/headless_browser_impl_aura.cc b/headless/lib/browser/headless_browser_impl_aura.cc\nindex 508660db32151..80340d9f1b3b3 100644\n--- a/headless/lib/browser/headless_browser_impl_aura.cc\n+++ b/headless/lib/browser/headless_browser_impl_aura.cc\n@@ -59,7 +59,7 @@ void HeadlessBrowserImpl::PlatformSetWebContentsBounds(\n     const gfx::Rect& bounds) {\n   // Browser's window bounds should contain all web contents, so that we're sure\n   // that we will actually produce visible damage when taking a screenshot.\n-  web_contents->window_tree_host()->SetBoundsInPixels(ScaleToEnclosedRect(bounds, carbonyl::Renderer::GetDPI()));\n+  web_contents->window_tree_host()->SetBoundsInPixels(ScaleToEnclosedRect(bounds, carbonyl::Bridge::GetCurrent()->GetDPI()));\n   web_contents->window_tree_host()->window()->SetBounds(bounds);\n \n   gfx::NativeView native_view = web_contents->web_contents()->GetNativeView();\ndiff --git a/headless/lib/browser/headless_screen.cc b/headless/lib/browser/headless_screen.cc\nindex 8bf00ef5e036a..89c5ccc8d7759 100644\n--- a/headless/lib/browser/headless_screen.cc\n+++ b/headless/lib/browser/headless_screen.cc\n@@ -51,7 +51,7 @@ display::Display HeadlessScreen::GetDisplayNearestWindow(\n HeadlessScreen::HeadlessScreen(const gfx::Rect& screen_bounds) {\n   static int64_t synthesized_display_id = 2000;\n   display::Display display(synthesized_display_id++);\n-  float dpi = carbonyl::Renderer::GetDPI();\n+  float dpi = carbonyl::Bridge::GetCurrent()->GetDPI();\n   display.SetScaleAndBounds(dpi, ScaleToEnclosedRect(screen_bounds, dpi));\n   ProcessDisplayChanged(display, true /* is_primary */);\n }\ndiff --git a/headless/lib/browser/headless_web_contents_impl.cc b/headless/lib/browser/headless_web_contents_impl.cc\nindex fad8c3fdd2bfe..a166a08f6ea15 100644\n--- a/headless/lib/browser/headless_web_contents_impl.cc\n+++ b/headless/lib/browser/headless_web_contents_impl.cc\n@@ -400,7 +400,7 @@ void HeadlessWebContentsImpl::TitleWasSet(content::NavigationEntry* entry) {\n   if (!web_contents() || !web_contents()->GetPrimaryMainFrame()->IsActive())\n     return;\n \n-  carbonyl::Renderer::Main()->SetTitle(base::UTF16ToUTF8(entry->GetTitleForDisplay()));\n+  carbonyl::Bridge::GetCurrent()->SetTitle(base::UTF16ToUTF8(entry->GetTitleForDisplay()));\n }\n \n void HeadlessWebContentsImpl::DidFinishNavigation(content::NavigationHandle* handle) {\n@@ -411,7 +411,7 @@ void HeadlessWebContentsImpl::DidFinishNavigation(content::NavigationHandle* han\n \n   auto& nav = web_contents()->GetController();\n \n-  carbonyl::Renderer::Main()->PushNav(\n+  carbonyl::Bridge::GetCurrent()->PushNav(\n     handle->GetURL().spec(),\n     nav.CanGoBack(),\n     nav.CanGoForward()\n"
  },
  {
    "path": "chromium/patches/chromium/0012-Create-separate-bridge-for-Blink.patch",
    "content": "From 6862e372717eff278470453e800dc693f33b873c Mon Sep 17 00:00:00 2001\nFrom: Fathy Boundjadj <hey@fathy.fr>\nDate: Sun, 12 Feb 2023 02:20:32 +0100\nSubject: [PATCH 12/14] Create separate bridge for Blink\n\n---\n .../blink/renderer/core/css/resolver/style_resolver.cc    | 4 ++--\n third_party/blink/renderer/platform/BUILD.gn              | 2 +-\n third_party/blink/renderer/platform/fonts/font.cc         | 8 ++++----\n 3 files changed, 7 insertions(+), 7 deletions(-)\n\ndiff --git a/third_party/blink/renderer/core/css/resolver/style_resolver.cc b/third_party/blink/renderer/core/css/resolver/style_resolver.cc\nindex 7129982acf4a6..cb116ee07c8f6 100644\n--- a/third_party/blink/renderer/core/css/resolver/style_resolver.cc\n+++ b/third_party/blink/renderer/core/css/resolver/style_resolver.cc\n@@ -116,7 +116,7 @@\n #include \"third_party/blink/renderer/platform/wtf/text/atomic_string_hash.h\"\n #include \"third_party/blink/renderer/platform/wtf/text/string_builder.h\"\n \n-#include \"carbonyl/src/browser/bridge.h\"\n+#include \"carbonyl/src/browser/blink.h\"\n \n namespace blink {\n \n@@ -1043,7 +1043,7 @@ scoped_refptr<ComputedStyle> StyleResolver::ResolveStyle(\n     UseCounter::Count(GetDocument(), WebFeature::kHasGlyphRelativeUnits);\n   }\n \n-  if (!carbonyl::Bridge::BitmapMode()) {\n+  if (!carbonyl::blink::BitmapMode()) {\n     auto font = state.StyleBuilder().GetFontDescription();\n     FontFamily family;\n \ndiff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn\nindex 63fc13e44b5ae..ceb41d781acf6 100644\n--- a/third_party/blink/renderer/platform/BUILD.gn\n+++ b/third_party/blink/renderer/platform/BUILD.gn\n@@ -1678,7 +1678,7 @@ component(\"platform\") {\n     \"//base/allocator:buildflags\",\n     \"//build:chromecast_buildflags\",\n     \"//build:chromeos_buildflags\",\n-    \"//carbonyl/src/browser:carbonyl\",\n+    \"//carbonyl/src/browser:blink\",\n     \"//cc/ipc\",\n     \"//cc/mojo_embedder\",\n     \"//components/paint_preview/common\",\ndiff --git a/third_party/blink/renderer/platform/fonts/font.cc b/third_party/blink/renderer/platform/fonts/font.cc\nindex 4625300729523..3d1b463e9651c 100644\n--- a/third_party/blink/renderer/platform/fonts/font.cc\n+++ b/third_party/blink/renderer/platform/fonts/font.cc\n@@ -49,7 +49,7 @@\n #include \"third_party/skia/include/core/SkTextBlob.h\"\n #include \"ui/gfx/geometry/rect_f.h\"\n \n-#include \"carbonyl/src/browser/bridge.h\"\n+#include \"carbonyl/src/browser/blink.h\"\n \n namespace blink {\n \n@@ -158,7 +158,7 @@ void DrawBlobs(cc::PaintCanvas* canvas,\n                const ShapeResultBloberizer::BlobBuffer& blobs,\n                const gfx::PointF& point,\n                cc::NodeId node_id = cc::kInvalidNodeId) {  \n-  if (!carbonyl::Bridge::BitmapMode()) {\n+  if (!carbonyl::blink::BitmapMode()) {\n     return;\n   }\n \n@@ -237,7 +237,7 @@ void Font::DrawText(cc::PaintCanvas* canvas,\n   if (ShouldSkipDrawing())\n     return;\n \n-  if (!carbonyl::Bridge::BitmapMode()) {\n+  if (!carbonyl::blink::BitmapMode()) {\n     auto string = StringView(\n       run_info.run.ToStringView(),\n       run_info.from,\n@@ -285,7 +285,7 @@ void Font::DrawText(cc::PaintCanvas* canvas,\n   if (ShouldSkipDrawing())\n     return;\n \n-  if (!carbonyl::Bridge::BitmapMode()) {\n+  if (!carbonyl::blink::BitmapMode()) {\n     auto string = StringView(\n       text_info.text,\n       text_info.from,\n"
  },
  {
    "path": "chromium/patches/chromium/0013-Refactor-rendering-bridge.patch",
    "content": "From 3b67b346ec26a47b50178124d715a8320f612d4d Mon Sep 17 00:00:00 2001\nFrom: Fathy Boundjadj <hey@fathy.fr>\nDate: Mon, 13 Feb 2023 16:28:50 +0100\nSubject: [PATCH 13/14] Refactor rendering bridge\n\n---\n components/viz/service/BUILD.gn               |   3 +\n .../output_surface_provider_impl.cc           |   2 +-\n .../software_output_device_proxy.cc           | 155 ++++++++++++++++++\n .../software_output_device_proxy.h            |  91 ++++++++++\n content/browser/BUILD.gn                      |   2 +\n .../browser/web_contents/web_contents_impl.cc |   4 +-\n .../browser/web_contents/web_contents_impl.h  |   2 +-\n content/public/browser/web_contents.h         |   3 +\n content/renderer/BUILD.gn                     |   3 +\n content/renderer/render_frame_impl.cc         |   5 +\n headless/BUILD.gn                             |  10 +-\n headless/app/headless_shell.cc                |   6 +-\n headless/app/headless_shell_main.cc           |   4 +-\n headless/lib/browser/headless_browser_impl.cc |  12 +-\n .../lib/browser/headless_browser_impl_aura.cc |   2 +-\n .../lib/browser/headless_browser_impl_mac.mm  |   9 +-\n .../headless_browser_main_parts_mac.mm        |   9 +\n headless/lib/browser/headless_screen.cc       |  28 +++-\n headless/lib/browser/headless_screen.h        |   8 +-\n .../lib/browser/headless_web_contents_impl.cc |   8 +-\n printing/printing_context_mac.mm              |   2 +\n third_party/blink/renderer/core/BUILD.gn      |   1 +\n .../core/css/resolver/style_resolver.cc       |   4 +-\n third_party/blink/renderer/platform/BUILD.gn  |   2 +-\n .../blink/renderer/platform/fonts/font.cc     |   8 +-\n ui/display/BUILD.gn                           |   1 +\n ui/display/display.cc                         |  51 +-----\n 27 files changed, 351 insertions(+), 84 deletions(-)\n create mode 100644 components/viz/service/display_embedder/software_output_device_proxy.cc\n create mode 100644 components/viz/service/display_embedder/software_output_device_proxy.h\n\ndiff --git a/components/viz/service/BUILD.gn b/components/viz/service/BUILD.gn\nindex fb793e98d5939..e1b4dfa4cd49f 100644\n--- a/components/viz/service/BUILD.gn\n+++ b/components/viz/service/BUILD.gn\n@@ -139,6 +139,8 @@ viz_component(\"service\") {\n     \"display_embedder/skia_render_copy_results.h\",\n     \"display_embedder/software_output_surface.cc\",\n     \"display_embedder/software_output_surface.h\",\n+    \"display_embedder/software_output_device_proxy.cc\",\n+    \"display_embedder/software_output_device_proxy.h\",\n     \"display_embedder/vsync_parameter_listener.cc\",\n     \"display_embedder/vsync_parameter_listener.h\",\n     \"frame_sinks/begin_frame_tracker.cc\",\n@@ -229,6 +231,7 @@ viz_component(\"service\") {\n     \"//build:chromeos_buildflags\",\n     \"//cc/base\",\n     \"//cc/paint\",\n+    \"//carbonyl/src/browser:viz\",\n     \"//components/crash/core/common:crash_key\",\n     \"//components/power_scheduler\",\n \ndiff --git a/components/viz/service/display_embedder/output_surface_provider_impl.cc b/components/viz/service/display_embedder/output_surface_provider_impl.cc\nindex 2929ebd3887c2..cd6f81dfa8b1d 100644\n--- a/components/viz/service/display_embedder/output_surface_provider_impl.cc\n+++ b/components/viz/service/display_embedder/output_surface_provider_impl.cc\n@@ -16,7 +16,6 @@\n #include \"build/build_config.h\"\n #include \"build/chromecast_buildflags.h\"\n #include \"build/chromeos_buildflags.h\"\n-#include \"carbonyl/src/browser/software_output_device_proxy.h\"\n #include \"cc/base/switches.h\"\n #include \"components/viz/common/display/renderer_settings.h\"\n #include \"components/viz/common/frame_sinks/begin_frame_source.h\"\n@@ -24,6 +23,7 @@\n #include \"components/viz/service/display_embedder/server_shared_bitmap_manager.h\"\n #include \"components/viz/service/display_embedder/skia_output_surface_dependency_impl.h\"\n #include \"components/viz/service/display_embedder/skia_output_surface_impl.h\"\n+#include \"components/viz/service/display_embedder/software_output_device_proxy.h\"\n #include \"components/viz/service/display_embedder/software_output_surface.h\"\n #include \"components/viz/service/gl/gpu_service_impl.h\"\n #include \"gpu/command_buffer/client/shared_memory_limits.h\"\ndiff --git a/components/viz/service/display_embedder/software_output_device_proxy.cc b/components/viz/service/display_embedder/software_output_device_proxy.cc\nnew file mode 100644\nindex 0000000000000..a61668050cf0e\n--- /dev/null\n+++ b/components/viz/service/display_embedder/software_output_device_proxy.cc\n@@ -0,0 +1,155 @@\n+#include \"components/viz/service/display_embedder/software_output_device_proxy.h\"\n+\n+#include \"base/memory/unsafe_shared_memory_region.h\"\n+#include \"base/threading/thread_checker.h\"\n+#include \"base/trace_event/trace_event.h\"\n+#include \"build/build_config.h\"\n+#include \"components/viz/common/resources/resource_sizes.h\"\n+#include \"components/viz/service/display_embedder/output_device_backing.h\"\n+#include \"mojo/public/cpp/system/platform_handle.h\"\n+#include \"services/viz/privileged/mojom/compositing/layered_window_updater.mojom.h\"\n+#include \"skia/ext/platform_canvas.h\"\n+#include \"third_party/skia/include/core/SkCanvas.h\"\n+#include \"ui/gfx/skia_util.h\"\n+\n+#if BUILDFLAG(IS_WIN)\n+#include \"skia/ext/skia_utils_win.h\"\n+#include \"ui/gfx/gdi_util.h\"\n+#include \"ui/gfx/win/hwnd_util.h\"\n+#else\n+#include \"mojo/public/cpp/base/shared_memory_utils.h\"\n+#endif\n+\n+namespace viz {\n+\n+SoftwareOutputDeviceBase::~SoftwareOutputDeviceBase() {\n+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);\n+  DCHECK(!in_paint_);\n+}\n+\n+void SoftwareOutputDeviceBase::Resize(const gfx::Size& viewport_pixel_size,\n+                                         float scale_factor) {\n+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);\n+  DCHECK(!in_paint_);\n+\n+  if (viewport_pixel_size_ == viewport_pixel_size)\n+    return;\n+\n+  viewport_pixel_size_ = viewport_pixel_size;\n+  ResizeDelegated();\n+}\n+\n+SkCanvas* SoftwareOutputDeviceBase::BeginPaint(\n+    const gfx::Rect& damage_rect) {\n+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);\n+  DCHECK(!in_paint_);\n+\n+  damage_rect_ = damage_rect;\n+  in_paint_ = true;\n+  return BeginPaintDelegated();\n+}\n+\n+void SoftwareOutputDeviceBase::EndPaint() {\n+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);\n+  DCHECK(in_paint_);\n+\n+  in_paint_ = false;\n+\n+  gfx::Rect intersected_damage_rect = damage_rect_;\n+  intersected_damage_rect.Intersect(gfx::Rect(viewport_pixel_size_));\n+  if (intersected_damage_rect.IsEmpty())\n+    return;\n+\n+  EndPaintDelegated(intersected_damage_rect);\n+}\n+\n+SoftwareOutputDeviceProxy::~SoftwareOutputDeviceProxy() = default;\n+\n+SoftwareOutputDeviceProxy::SoftwareOutputDeviceProxy(\n+    mojo::PendingRemote<mojom::LayeredWindowUpdater> layered_window_updater)\n+    : layered_window_updater_(std::move(layered_window_updater)) {\n+  DCHECK(layered_window_updater_.is_bound());\n+}\n+\n+void SoftwareOutputDeviceProxy::OnSwapBuffers(\n+    SoftwareOutputDevice::SwapBuffersCallback swap_ack_callback,\n+    gfx::FrameData data) {\n+  DCHECK(swap_ack_callback_.is_null());\n+\n+  // We aren't waiting on DrawAck() and can immediately run the callback.\n+  if (!waiting_on_draw_ack_) {\n+    task_runner_->PostTask(FROM_HERE,\n+        base::BindOnce(std::move(swap_ack_callback), viewport_pixel_size_));\n+    return;\n+  }\n+\n+  swap_ack_callback_ = std::move(swap_ack_callback);\n+}\n+\n+void SoftwareOutputDeviceProxy::ResizeDelegated() {\n+  canvas_.reset();\n+\n+  size_t required_bytes;\n+  if (!ResourceSizes::MaybeSizeInBytes(\n+          viewport_pixel_size_, ResourceFormat::RGBA_8888, &required_bytes)) {\n+    DLOG(ERROR) << \"Invalid viewport size \" << viewport_pixel_size_.ToString();\n+    return;\n+  }\n+\n+  base::UnsafeSharedMemoryRegion region =\n+      base::UnsafeSharedMemoryRegion::Create(required_bytes);\n+  if (!region.IsValid()) {\n+    DLOG(ERROR) << \"Failed to allocate \" << required_bytes << \" bytes\";\n+    return;\n+  }\n+\n+  #if defined(WIN32)\n+  canvas_ = skia::CreatePlatformCanvasWithSharedSection(\n+      viewport_pixel_size_.width(), viewport_pixel_size_.height(), false,\n+      region.GetPlatformHandle(), skia::CRASH_ON_FAILURE);\n+  #else\n+  shm_mapping_ = region.Map();\n+  if (!shm_mapping_.IsValid()) {\n+    DLOG(ERROR) << \"Failed to map \" << required_bytes << \" bytes\";\n+    return;\n+  }\n+\n+  canvas_ = skia::CreatePlatformCanvasWithPixels(\n+      viewport_pixel_size_.width(), viewport_pixel_size_.height(), false,\n+      static_cast<uint8_t*>(shm_mapping_.memory()), skia::CRASH_ON_FAILURE);\n+  #endif\n+\n+  // Transfer region ownership to the browser process.\n+  layered_window_updater_->OnAllocatedSharedMemory(viewport_pixel_size_,\n+                                                   std::move(region));\n+}\n+\n+SkCanvas* SoftwareOutputDeviceProxy::BeginPaintDelegated() {\n+  return canvas_.get();\n+}\n+\n+void SoftwareOutputDeviceProxy::EndPaintDelegated(\n+    const gfx::Rect& damage_rect) {\n+  DCHECK(!waiting_on_draw_ack_);\n+\n+  if (!canvas_)\n+    return;\n+\n+  layered_window_updater_->Draw(damage_rect, base::BindOnce(\n+      &SoftwareOutputDeviceProxy::DrawAck, base::Unretained(this)));\n+  waiting_on_draw_ack_ = true;\n+\n+  TRACE_EVENT_ASYNC_BEGIN0(\"viz\", \"SoftwareOutputDeviceProxy::Draw\", this);\n+}\n+\n+void SoftwareOutputDeviceProxy::DrawAck() {\n+  DCHECK(waiting_on_draw_ack_);\n+  DCHECK(!swap_ack_callback_.is_null());\n+\n+  TRACE_EVENT_ASYNC_END0(\"viz\", \"SoftwareOutputDeviceProxy::Draw\", this);\n+\n+  waiting_on_draw_ack_ = false;\n+  std::move(swap_ack_callback_).Run(viewport_pixel_size_);\n+}\n+\n+}  // namespace viz\ndiff --git a/components/viz/service/display_embedder/software_output_device_proxy.h b/components/viz/service/display_embedder/software_output_device_proxy.h\nnew file mode 100644\nindex 0000000000000..4f0a64830b18b\n--- /dev/null\n+++ b/components/viz/service/display_embedder/software_output_device_proxy.h\n@@ -0,0 +1,91 @@\n+#ifndef CARBONYL_SRC_BROWSER_SOFTWARE_OUTPUT_DEVICE_PROXY_H_\n+#define CARBONYL_SRC_BROWSER_SOFTWARE_OUTPUT_DEVICE_PROXY_H_\n+\n+#include <memory>\n+\n+#include \"base/memory/shared_memory_mapping.h\"\n+#include \"base/threading/thread_checker.h\"\n+#include \"build/build_config.h\"\n+#include \"components/viz/host/host_display_client.h\"\n+#include \"components/viz/service/display/software_output_device.h\"\n+#include \"components/viz/service/viz_service_export.h\"\n+#include \"mojo/public/cpp/bindings/pending_remote.h\"\n+#include \"mojo/public/cpp/bindings/remote.h\"\n+#include \"services/viz/privileged/mojom/compositing/display_private.mojom.h\"\n+#include \"services/viz/privileged/mojom/compositing/layered_window_updater.mojom.h\"\n+\n+#if BUILDFLAG(IS_WIN)\n+#include <windows.h>\n+#endif\n+\n+namespace viz {\n+\n+// Shared base class for SoftwareOutputDevice implementations.\n+class SoftwareOutputDeviceBase : public SoftwareOutputDevice {\n+ public:\n+  SoftwareOutputDeviceBase() = default;\n+  ~SoftwareOutputDeviceBase() override;\n+\n+  SoftwareOutputDeviceBase(const SoftwareOutputDeviceBase&) = delete;\n+  SoftwareOutputDeviceBase& operator=(const SoftwareOutputDeviceBase&) = delete;\n+\n+  // SoftwareOutputDevice implementation.\n+  void Resize(const gfx::Size& viewport_pixel_size,\n+              float scale_factor) override;\n+  SkCanvas* BeginPaint(const gfx::Rect& damage_rect) override;\n+  void EndPaint() override;\n+\n+  // Called from Resize() if |viewport_pixel_size_| has changed.\n+  virtual void ResizeDelegated() = 0;\n+\n+  // Called from BeginPaint() and should return an SkCanvas.\n+  virtual SkCanvas* BeginPaintDelegated() = 0;\n+\n+  // Called from EndPaint() if there is damage.\n+  virtual void EndPaintDelegated(const gfx::Rect& damage_rect) = 0;\n+\n+ private:\n+  bool in_paint_ = false;\n+\n+  THREAD_CHECKER(thread_checker_);\n+};\n+\n+// SoftwareOutputDevice implementation that draws indirectly. An implementation\n+// of mojom::LayeredWindowUpdater in the browser process handles the actual\n+// drawing. Pixel backing is in SharedMemory so no copying between processes\n+// is required.\n+class SoftwareOutputDeviceProxy : public SoftwareOutputDeviceBase {\n+ public:\n+  explicit SoftwareOutputDeviceProxy(\n+      mojo::PendingRemote<mojom::LayeredWindowUpdater> layered_window_updater);\n+  ~SoftwareOutputDeviceProxy() override;\n+\n+  SoftwareOutputDeviceProxy(const SoftwareOutputDeviceProxy&) = delete;\n+  SoftwareOutputDeviceProxy& operator=(const SoftwareOutputDeviceProxy&) = delete;\n+\n+  // SoftwareOutputDevice implementation.\n+  void OnSwapBuffers(SoftwareOutputDevice::SwapBuffersCallback swap_ack_callback, gfx::FrameData data) override;\n+\n+  // SoftwareOutputDeviceBase implementation.\n+  void ResizeDelegated() override;\n+  SkCanvas* BeginPaintDelegated() override;\n+  void EndPaintDelegated(const gfx::Rect& rect) override;\n+\n+ private:\n+  // Runs |swap_ack_callback_| after draw has happened.\n+  void DrawAck();\n+\n+  mojo::Remote<mojom::LayeredWindowUpdater> layered_window_updater_;\n+\n+  std::unique_ptr<SkCanvas> canvas_;\n+  bool waiting_on_draw_ack_ = false;\n+  SoftwareOutputDevice::SwapBuffersCallback swap_ack_callback_;\n+\n+#if !defined(WIN32)\n+  base::WritableSharedMemoryMapping shm_mapping_;\n+#endif\n+};\n+\n+}  // namespace viz\n+\n+#endif  // CARBONYL_SRC_BROWSER_SOFTWARE_OUTPUT_DEVICE_PROXY_H_\ndiff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn\nindex aff547e8e5ed0..d2645a0be3008 100644\n--- a/content/browser/BUILD.gn\n+++ b/content/browser/BUILD.gn\n@@ -69,6 +69,8 @@ source_set(\"browser\") {\n     \"//build:chromecast_buildflags\",\n     \"//build:chromeos_buildflags\",\n     \"//build/config/compiler:compiler_buildflags\",\n+    \"//carbonyl/src/browser:renderer\",\n+    \"//carbonyl/src/browser:viz\",\n     \"//cc\",\n     \"//cc/animation\",\n     \"//cc/mojo_embedder\",\ndiff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc\nindex 4eb891c32b474..6ae4005040371 100644\n--- a/content/browser/web_contents/web_contents_impl.cc\n+++ b/content/browser/web_contents/web_contents_impl.cc\n@@ -1650,7 +1650,7 @@ void WebContentsImpl::OnScreensChange(bool is_multi_screen_changed) {\n   // Mac display info may originate from a remote process hosting the NSWindow;\n   // this local process display::Screen signal should not trigger updates.\n   // TODO(crbug.com/1169291): Unify screen info plumbing, caching, etc.\n-#if !BUILDFLAG(IS_MAC)\n+// #if !BUILDFLAG(IS_MAC)\n   // This updates Screen attributes and fires Screen.change events as needed,\n   // propagating to all widgets through the VisualProperties update waterfall.\n   // This is triggered by system changes, not renderer IPC, so explicitly check\n@@ -1662,7 +1662,7 @@ void WebContentsImpl::OnScreensChange(bool is_multi_screen_changed) {\n     if (!view->IsRenderWidgetHostViewChildFrame())\n       view->UpdateScreenInfo();\n   }\n-#endif  // !BUILDFLAG(IS_MAC)\n+// #endif  // !BUILDFLAG(IS_MAC)\n }\n \n void WebContentsImpl::OnScreenOrientationChange() {\ndiff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h\nindex 3f0ecf8749e44..196c3174e20f1 100644\n--- a/content/browser/web_contents/web_contents_impl.h\n+++ b/content/browser/web_contents/web_contents_impl.h\n@@ -265,7 +265,7 @@ class CONTENT_EXPORT WebContentsImpl : public WebContents,\n \n   // Called on screen information changes; |is_multi_screen_changed| is true iff\n   // the plurality of connected screens changed (e.g. 1 screen <-> 2 screens).\n-  void OnScreensChange(bool is_multi_screen_changed);\n+  void OnScreensChange(bool is_multi_screen_changed) override;\n \n   void OnScreenOrientationChange();\n \ndiff --git a/content/public/browser/web_contents.h b/content/public/browser/web_contents.h\nindex 1fd156150b6b5..60be0bf1bda48 100644\n--- a/content/public/browser/web_contents.h\n+++ b/content/public/browser/web_contents.h\n@@ -344,6 +344,9 @@ class WebContents : public PageNavigator,\n \n   ~WebContents() override = default;\n \n+  // Carbonyl patches\n+  virtual void OnScreensChange(bool is_multi_screen_changed) = 0;\n+\n   // Intrinsic tab state -------------------------------------------------------\n \n   // Gets/Sets the delegate.\ndiff --git a/content/renderer/BUILD.gn b/content/renderer/BUILD.gn\nindex 2a2410d4a46c1..1900c294f2f65 100644\n--- a/content/renderer/BUILD.gn\n+++ b/content/renderer/BUILD.gn\n@@ -229,6 +229,9 @@ target(link_target_type, \"renderer\") {\n     \"//base:i18n\",\n     \"//build:chromecast_buildflags\",\n     \"//build:chromeos_buildflags\",\n+    \"//carbonyl/src/browser:bridge\",\n+    \"//carbonyl/src/browser:mojom\",\n+    \"//carbonyl/src/browser:renderer\",\n     \"//cc\",\n     \"//cc/animation\",\n     \"//cc/mojo_embedder\",\ndiff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc\nindex 891efd6a9d796..379cf6c58b2b0 100644\n--- a/content/renderer/render_frame_impl.cc\n+++ b/content/renderer/render_frame_impl.cc\n@@ -285,6 +285,7 @@\n #include \"third_party/skia/include/svg/SkSVGCanvas.h\"\n #include \"third_party/skia/include/utils/SkBase64.h\"\n #include \"third_party/skia/src/text/GlyphRun.h\"\n+#include \"third_party/skia/src/core/SkBitmapDevice.h\"\n #include \"third_party/skia/src/core/SkClipStackDevice.h\"\n #include \"third_party/skia/src/core/SkDevice.h\"\n #include \"third_party/skia/src/core/SkFontPriv.h\"\n@@ -2243,6 +2244,10 @@ void RenderFrameImpl::Initialize(blink::WebFrame* parent) {\n   );\n \n   host->ObserveTerminalRender(render_callback_);\n+\n+  if (!carbonyl::Bridge::BitmapMode()) {\n+    SkBitmapDevice::DisableTextRendering();\n+  }\n }\n \n void RenderFrameImpl::GetInterface(\ndiff --git a/headless/BUILD.gn b/headless/BUILD.gn\nindex 8018111ed9898..17120c83ee13e 100644\n--- a/headless/BUILD.gn\n+++ b/headless/BUILD.gn\n@@ -349,6 +349,8 @@ component(\"headless_non_renderer\") {\n     \"lib/browser/headless_platform_event_source.h\",\n     \"lib/browser/headless_request_context_manager.cc\",\n     \"lib/browser/headless_request_context_manager.h\",\n+    \"lib/browser/headless_screen.cc\",\n+    \"lib/browser/headless_screen.h\",\n     \"lib/browser/headless_select_file_dialog_factory.cc\",\n     \"lib/browser/headless_select_file_dialog_factory.h\",\n     \"lib/browser/headless_web_contents_impl.cc\",\n@@ -416,8 +418,6 @@ component(\"headless_non_renderer\") {\n       \"lib/browser/headless_clipboard.h\",\n       \"lib/browser/headless_focus_client.cc\",\n       \"lib/browser/headless_focus_client.h\",\n-      \"lib/browser/headless_screen.cc\",\n-      \"lib/browser/headless_screen.h\",\n       \"lib/browser/headless_window_parenting_client.cc\",\n       \"lib/browser/headless_window_parenting_client.h\",\n       \"lib/browser/headless_window_tree_host.cc\",\n@@ -453,7 +453,8 @@ component(\"headless_non_renderer\") {\n     \"//build:branding_buildflags\",\n     \"//build:branding_buildflags\",\n     \"//build:chromeos_buildflags\",\n-    \"//carbonyl/src/browser:carbonyl\",\n+    \"//carbonyl/src/browser:bridge\",\n+    \"//carbonyl/src/browser:renderer\",\n     \"//components/cookie_config\",\n     \"//components/crash/core/common:common\",\n     \"//components/embedder_support\",\n@@ -474,6 +475,7 @@ component(\"headless_non_renderer\") {\n     \"//components/profile_metrics\",\n     \"//components/profile_metrics:profile_metrics\",\n     \"//components/security_state/core\",\n+    \"//components/zoom\",\n     \"//content/public/app\",\n     \"//content/public/app:app\",\n     \"//content/public/browser\",\n@@ -1011,7 +1013,7 @@ executable(\"headless_shell\") {\n \n   deps = [\n     \":headless_shell_lib\",\n-    \"//carbonyl/src/browser:carbonyl\",\n+    \"//carbonyl/src/browser:renderer\",\n   ]\n \n   if (!headless_use_embedded_resources) {\ndiff --git a/headless/app/headless_shell.cc b/headless/app/headless_shell.cc\nindex b6a52857e8f90..6aed55bd6062d 100644\n--- a/headless/app/headless_shell.cc\n+++ b/headless/app/headless_shell.cc\n@@ -4,7 +4,7 @@\n \n #include \"headless/app/headless_shell.h\"\n \n-#include \"carbonyl/src/browser/bridge.h\"\n+#include \"carbonyl/src/browser/renderer.h\"\n \n #include <memory>\n \n@@ -93,9 +93,9 @@ void HeadlessShell::OnBrowserStart(HeadlessBrowser* browser) {\n   HeadlessBrowserContext::Builder context_builder =\n       browser_->CreateBrowserContextBuilder();\n \n-  carbonyl::Bridge::GetCurrent()->StartRenderer();\n+  carbonyl::Renderer::GetCurrent()->StartRenderer();\n \n-  context_builder.SetWindowSize(carbonyl::Bridge::GetCurrent()->GetSize());\n+  context_builder.SetWindowSize(carbonyl::Renderer::GetCurrent()->GetSize());\n \n   // Retrieve the locale set by InitApplicationLocale() in\n   // headless_content_main_delegate.cc in a way that is free of side-effects.\ndiff --git a/headless/app/headless_shell_main.cc b/headless/app/headless_shell_main.cc\nindex 739df1ae1bd58..1f6184af60ef0 100644\n--- a/headless/app/headless_shell_main.cc\n+++ b/headless/app/headless_shell_main.cc\n@@ -14,10 +14,10 @@\n #endif\n \n #include \"base/at_exit.h\"\n-#include \"carbonyl/src/browser/bridge.h\"\n+#include \"carbonyl/src/browser/renderer.h\"\n \n int main(int argc, const char** argv) {\n-  carbonyl::Bridge::Main();\n+  carbonyl::Renderer::Main();\n \n #if BUILDFLAG(IS_WIN)\n   sandbox::SandboxInterfaceInfo sandbox_info = {nullptr};\ndiff --git a/headless/lib/browser/headless_browser_impl.cc b/headless/lib/browser/headless_browser_impl.cc\nindex 1df3ffe72c93d..5aa0bdc25e409 100644\n--- a/headless/lib/browser/headless_browser_impl.cc\n+++ b/headless/lib/browser/headless_browser_impl.cc\n@@ -15,6 +15,7 @@\n #include \"base/memory/ptr_util.h\"\n #include \"base/run_loop.h\"\n #include \"base/threading/thread_task_runner_handle.h\"\n+#include \"components/zoom/zoom_controller.h\"\n #include \"content/public/app/content_main.h\"\n #include \"content/public/browser/browser_task_traits.h\"\n #include \"content/public/browser/browser_thread.h\"\n@@ -24,9 +25,11 @@\n #include \"headless/lib/browser/headless_browser_context_impl.h\"\n #include \"headless/lib/browser/headless_browser_main_parts.h\"\n #include \"headless/lib/browser/headless_devtools_agent_host_client.h\"\n+#include \"headless/lib/browser/headless_screen.h\"\n #include \"headless/lib/browser/headless_web_contents_impl.h\"\n #include \"net/http/http_util.h\"\n #include \"services/network/public/cpp/network_switches.h\"\n+#include \"ui/compositor/compositor.h\"\n #include \"ui/events/devices/device_data_manager.h\"\n \n #include \"content/public/browser/render_frame_host.h\"\n@@ -34,6 +37,7 @@\n #include \"content/public/browser/render_widget_host.h\"\n #include \"content/public/browser/web_contents.h\"\n #include \"carbonyl/src/browser/bridge.h\"\n+#include \"carbonyl/src/browser/renderer.h\"\n #include \"third_party/blink/public/common/input/web_mouse_event.h\"\n #include \"third_party/blink/public/common/input/web_mouse_wheel_event.h\"\n #include \"ui/events/keycodes/keyboard_codes.h\"\n@@ -119,7 +123,7 @@ void HeadlessBrowserImpl::set_browser_main_parts(\n }\n \n void HeadlessBrowserImpl::Resize() {\n-  auto size = carbonyl::Bridge::GetCurrent()->Resize();\n+  auto size = carbonyl::Renderer::GetCurrent()->Resize();\n   auto rect = gfx::Rect(0, 0, size.width(), size.height());\n \n   for (auto* ctx: GetAllBrowserContexts()) {\n@@ -133,8 +137,6 @@ void HeadlessBrowserImpl::Resize() {\n       PlatformSetWebContentsBounds(impl, rect);\n     }\n   }\n-\n-  carbonyl::Bridge::GetCurrent()->Resize();\n }\n \n void HeadlessBrowserImpl::OnShutdownInput() {\n@@ -436,7 +438,7 @@ void HeadlessBrowserImpl::RunOnStartCallback() {\n   input_thread_ = std::thread([=]() {\n     carbonyl::browser = this;\n \n-    carbonyl_bridge_browser_delegate delegate = {\n+    carbonyl_renderer_browser_delegate delegate = {\n       .shutdown = []() {\n         if (carbonyl::browser) {\n           carbonyl::browser->OnShutdownInput();\n@@ -500,7 +502,7 @@ void HeadlessBrowserImpl::RunOnStartCallback() {\n       }\n     };\n \n-    carbonyl::Bridge::GetCurrent()->Listen(&delegate);\n+    carbonyl::Renderer::GetCurrent()->Listen(&delegate);\n   });\n }\n \ndiff --git a/headless/lib/browser/headless_browser_impl_aura.cc b/headless/lib/browser/headless_browser_impl_aura.cc\nindex 80340d9f1b3b3..91be528753cdd 100644\n--- a/headless/lib/browser/headless_browser_impl_aura.cc\n+++ b/headless/lib/browser/headless_browser_impl_aura.cc\n@@ -59,7 +59,7 @@ void HeadlessBrowserImpl::PlatformSetWebContentsBounds(\n     const gfx::Rect& bounds) {\n   // Browser's window bounds should contain all web contents, so that we're sure\n   // that we will actually produce visible damage when taking a screenshot.\n-  web_contents->window_tree_host()->SetBoundsInPixels(ScaleToEnclosedRect(bounds, carbonyl::Bridge::GetCurrent()->GetDPI()));\n+  web_contents->window_tree_host()->SetBoundsInPixels(ScaleToEnclosedRect(bounds, carbonyl::Bridge::GetDPI()));\n   web_contents->window_tree_host()->window()->SetBounds(bounds);\n \n   gfx::NativeView native_view = web_contents->web_contents()->GetNativeView();\ndiff --git a/headless/lib/browser/headless_browser_impl_mac.mm b/headless/lib/browser/headless_browser_impl_mac.mm\nindex e2cb88fbcf708..397b2585f3d0f 100644\n--- a/headless/lib/browser/headless_browser_impl_mac.mm\n+++ b/headless/lib/browser/headless_browser_impl_mac.mm\n@@ -6,6 +6,8 @@\n \n #import \"base/mac/scoped_objc_class_swizzler.h\"\n #include \"base/no_destructor.h\"\n+#include \"carbonyl/src/browser/bridge.h\"\n+#include \"content/browser/renderer_host/render_widget_host_view_mac.h\"\n #include \"content/public/browser/render_widget_host_view.h\"\n #include \"content/public/browser/web_contents.h\"\n #include \"headless/lib/browser/headless_web_contents_impl.h\"\n@@ -95,8 +97,13 @@ void HeadlessBrowserImpl::PlatformSetWebContentsBounds(\n \n   content::RenderWidgetHostView* host_view =\n       web_contents->web_contents()->GetRenderWidgetHostView();\n-  if (host_view)\n+  if (host_view) {\n     host_view->SetWindowFrameInScreen(bounds);\n+\n+    static_cast<content::RenderWidgetHostViewMac*>(host_view)->SetCurrentDeviceScaleFactor(\n+      carbonyl::Bridge::GetDPI()\n+    );\n+  }\n }\n \n ui::Compositor* HeadlessBrowserImpl::PlatformGetCompositor(\ndiff --git a/headless/lib/browser/headless_browser_main_parts_mac.mm b/headless/lib/browser/headless_browser_main_parts_mac.mm\nindex 718e37ef8bd3e..8ca30b9d88d5b 100644\n--- a/headless/lib/browser/headless_browser_main_parts_mac.mm\n+++ b/headless/lib/browser/headless_browser_main_parts_mac.mm\n@@ -6,6 +6,14 @@\n \n #import <Cocoa/Cocoa.h>\n \n+#include \"base/command_line.h\"\n+#include \"build/build_config.h\"\n+#include \"build/chromeos_buildflags.h\"\n+#include \"components/os_crypt/os_crypt.h\"\n+#include \"content/public/browser/browser_task_traits.h\"\n+#include \"content/public/browser/browser_thread.h\"\n+#include \"device/bluetooth/dbus/bluez_dbus_manager.h\"\n+#include \"headless/app/headless_shell_switches.h\"\n #include \"headless/lib/browser/headless_shell_application_mac.h\"\n #include \"services/device/public/cpp/geolocation/geolocation_manager_impl_mac.h\"\n \n@@ -16,6 +24,7 @@ void HeadlessBrowserMainParts::PreCreateMainMessageLoop() {\n   [NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];\n   if (!geolocation_manager_)\n     geolocation_manager_ = device::GeolocationManagerImpl::Create();\n+\n }\n \n }  // namespace headless\ndiff --git a/headless/lib/browser/headless_screen.cc b/headless/lib/browser/headless_screen.cc\nindex 89c5ccc8d7759..047d741638df2 100644\n--- a/headless/lib/browser/headless_screen.cc\n+++ b/headless/lib/browser/headless_screen.cc\n@@ -6,14 +6,18 @@\n \n #include <stdint.h>\n \n-#include \"ui/aura/env.h\"\n-#include \"ui/aura/window.h\"\n #include \"ui/base/ime/input_method.h\"\n #include \"ui/gfx/geometry/rect_conversions.h\"\n #include \"ui/gfx/geometry/size_conversions.h\"\n #include \"ui/gfx/native_widget_types.h\"\n \n+#if !BUILDFLAG(IS_MAC)\n+#include \"ui/aura/env.h\"\n+#include \"ui/aura/window.h\"\n+#endif\n+\n #include \"carbonyl/src/browser/bridge.h\"\n+#include \"carbonyl/src/browser/renderer.h\"\n \n namespace headless {\n \n@@ -24,6 +28,7 @@ HeadlessScreen* HeadlessScreen::Create(const gfx::Size& size) {\n \n HeadlessScreen::~HeadlessScreen() = default;\n \n+#if !BUILDFLAG(IS_MAC)\n gfx::Point HeadlessScreen::GetCursorScreenPoint() {\n   return aura::Env::GetInstance()->last_mouse_location();\n }\n@@ -31,6 +36,7 @@ gfx::Point HeadlessScreen::GetCursorScreenPoint() {\n bool HeadlessScreen::IsWindowUnderCursor(gfx::NativeWindow window) {\n   return GetWindowAtScreenPoint(GetCursorScreenPoint()) == window;\n }\n+#endif\n \n gfx::NativeWindow HeadlessScreen::GetWindowAtScreenPoint(\n     const gfx::Point& point) {\n@@ -48,12 +54,22 @@ display::Display HeadlessScreen::GetDisplayNearestWindow(\n   return GetPrimaryDisplay();\n }\n \n+void HeadlessScreen::Resize() {\n+  float dpi = carbonyl::Bridge::GetDPI();  \n+  auto size = carbonyl::Renderer::GetCurrent()->GetSize();\n+  auto rect = gfx::Rect(0, 0, size.width() * dpi, size.height() * dpi);\n+\n+  display_.SetScaleAndBounds(dpi, rect);\n+  ProcessDisplayChanged(display_, true /* is_primary */);\n+}\n+\n HeadlessScreen::HeadlessScreen(const gfx::Rect& screen_bounds) {\n+  float dpi = carbonyl::Bridge::GetDPI();\n   static int64_t synthesized_display_id = 2000;\n-  display::Display display(synthesized_display_id++);\n-  float dpi = carbonyl::Bridge::GetCurrent()->GetDPI();\n-  display.SetScaleAndBounds(dpi, ScaleToEnclosedRect(screen_bounds, dpi));\n-  ProcessDisplayChanged(display, true /* is_primary */);\n+\n+  display_ = display::Display(synthesized_display_id++);\n+  display_.SetScaleAndBounds(dpi, ScaleToEnclosedRect(screen_bounds, dpi));\n+  ProcessDisplayChanged(display_, true /* is_primary */);\n }\n \n }  // namespace headless\ndiff --git a/headless/lib/browser/headless_screen.h b/headless/lib/browser/headless_screen.h\nindex 5ec78e5fe4685..23c073b1c86b3 100644\n--- a/headless/lib/browser/headless_screen.h\n+++ b/headless/lib/browser/headless_screen.h\n@@ -6,7 +6,6 @@\n #define HEADLESS_LIB_BROWSER_HEADLESS_SCREEN_H_\n \n #include \"base/compiler_specific.h\"\n-#include \"ui/aura/window_observer.h\"\n #include \"ui/display/display.h\"\n #include \"ui/display/screen_base.h\"\n \n@@ -26,10 +25,15 @@ class HeadlessScreen : public display::ScreenBase {\n \n   ~HeadlessScreen() override;\n \n+  void Resize();\n+\n  protected:\n   // display::Screen overrides:\n+#if !BUILDFLAG(IS_MAC)\n   gfx::Point GetCursorScreenPoint() override;\n   bool IsWindowUnderCursor(gfx::NativeWindow window) override;\n+#endif\n+\n   gfx::NativeWindow GetWindowAtScreenPoint(const gfx::Point& point) override;\n   gfx::NativeWindow GetLocalProcessWindowAtPoint(\n       const gfx::Point& point,\n@@ -39,6 +43,8 @@ class HeadlessScreen : public display::ScreenBase {\n \n  private:\n   explicit HeadlessScreen(const gfx::Rect& screen_bounds);\n+\n+  display::Display display_;\n };\n \n }  // namespace headless\ndiff --git a/headless/lib/browser/headless_web_contents_impl.cc b/headless/lib/browser/headless_web_contents_impl.cc\nindex a166a08f6ea15..091bde787d47c 100644\n--- a/headless/lib/browser/headless_web_contents_impl.cc\n+++ b/headless/lib/browser/headless_web_contents_impl.cc\n@@ -22,7 +22,8 @@\n #include \"base/values.h\"\n #include \"build/build_config.h\"\n #include \"build/chromeos_buildflags.h\"\n-#include \"carbonyl/src/browser/bridge.h\"\n+#include \"carbonyl/src/browser/renderer.h\"\n+#include \"components/zoom/zoom_controller.h\"\n #include \"content/public/browser/browser_thread.h\"\n #include \"content/public/browser/child_process_termination_info.h\"\n #include \"content/public/browser/devtools_agent_host.h\"\n@@ -335,6 +336,7 @@ HeadlessWebContentsImpl::HeadlessWebContentsImpl(\n #if BUILDFLAG(ENABLE_PRINTING)\n   HeadlessPrintManager::CreateForWebContents(web_contents_.get());\n #endif\n+  zoom::ZoomController::CreateForWebContents(web_contents_.get());\n   UpdatePrefsFromSystemSettings(web_contents_->GetMutableRendererPrefs());\n   web_contents_->GetMutableRendererPrefs()->accept_languages =\n       browser_context->options()->accept_language();\n@@ -400,7 +402,7 @@ void HeadlessWebContentsImpl::TitleWasSet(content::NavigationEntry* entry) {\n   if (!web_contents() || !web_contents()->GetPrimaryMainFrame()->IsActive())\n     return;\n \n-  carbonyl::Bridge::GetCurrent()->SetTitle(base::UTF16ToUTF8(entry->GetTitleForDisplay()));\n+  carbonyl::Renderer::GetCurrent()->SetTitle(base::UTF16ToUTF8(entry->GetTitleForDisplay()));\n }\n \n void HeadlessWebContentsImpl::DidFinishNavigation(content::NavigationHandle* handle) {\n@@ -411,7 +413,7 @@ void HeadlessWebContentsImpl::DidFinishNavigation(content::NavigationHandle* han\n \n   auto& nav = web_contents()->GetController();\n \n-  carbonyl::Bridge::GetCurrent()->PushNav(\n+  carbonyl::Renderer::GetCurrent()->PushNav(\n     handle->GetURL().spec(),\n     nav.CanGoBack(),\n     nav.CanGoForward()\ndiff --git a/printing/printing_context_mac.mm b/printing/printing_context_mac.mm\nindex 9dd650de9a9a3..8cfb6e9015c3f 100644\n--- a/printing/printing_context_mac.mm\n+++ b/printing/printing_context_mac.mm\n@@ -400,6 +400,7 @@ bool PrintingContextMac::SetDuplexModeInPrintSettings(mojom::DuplexMode mode) {\n bool PrintingContextMac::SetOutputColor(int color_mode) {\n   const mojom::ColorModel color_model = ColorModeToColorModel(color_mode);\n \n+#if BUILDFLAG(USE_CUPS)\n   if (!base::FeatureList::IsEnabled(features::kCupsIppPrintingBackend)) {\n     std::string color_setting_name;\n     std::string color_value;\n@@ -412,6 +413,7 @@ bool PrintingContextMac::SetOutputColor(int color_mode) {\n                    GetIppColorModelForModel(color_model))) {\n     return false;\n   }\n+#endif\n \n   struct PpdColorSetting {\n     constexpr PpdColorSetting(base::StringPiece name,\ndiff --git a/third_party/blink/renderer/core/BUILD.gn b/third_party/blink/renderer/core/BUILD.gn\nindex c29495a7060d7..654438b8d7424 100644\n--- a/third_party/blink/renderer/core/BUILD.gn\n+++ b/third_party/blink/renderer/core/BUILD.gn\n@@ -312,6 +312,7 @@ component(\"core\") {\n     \":generate_eventhandler_names\",\n     \":generated_settings_macros\",\n     \"//build:chromeos_buildflags\",\n+    \"//carbonyl/src/browser:bridge\",\n     \"//components/attribution_reporting\",\n     \"//components/attribution_reporting:mojom_blink\",\n     \"//components/paint_preview/common\",\ndiff --git a/third_party/blink/renderer/core/css/resolver/style_resolver.cc b/third_party/blink/renderer/core/css/resolver/style_resolver.cc\nindex cb116ee07c8f6..7129982acf4a6 100644\n--- a/third_party/blink/renderer/core/css/resolver/style_resolver.cc\n+++ b/third_party/blink/renderer/core/css/resolver/style_resolver.cc\n@@ -116,7 +116,7 @@\n #include \"third_party/blink/renderer/platform/wtf/text/atomic_string_hash.h\"\n #include \"third_party/blink/renderer/platform/wtf/text/string_builder.h\"\n \n-#include \"carbonyl/src/browser/blink.h\"\n+#include \"carbonyl/src/browser/bridge.h\"\n \n namespace blink {\n \n@@ -1043,7 +1043,7 @@ scoped_refptr<ComputedStyle> StyleResolver::ResolveStyle(\n     UseCounter::Count(GetDocument(), WebFeature::kHasGlyphRelativeUnits);\n   }\n \n-  if (!carbonyl::blink::BitmapMode()) {\n+  if (!carbonyl::Bridge::BitmapMode()) {\n     auto font = state.StyleBuilder().GetFontDescription();\n     FontFamily family;\n \ndiff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn\nindex ceb41d781acf6..bc6428f4f5c0d 100644\n--- a/third_party/blink/renderer/platform/BUILD.gn\n+++ b/third_party/blink/renderer/platform/BUILD.gn\n@@ -1678,7 +1678,7 @@ component(\"platform\") {\n     \"//base/allocator:buildflags\",\n     \"//build:chromecast_buildflags\",\n     \"//build:chromeos_buildflags\",\n-    \"//carbonyl/src/browser:blink\",\n+    \"//carbonyl/src/browser:bridge\",\n     \"//cc/ipc\",\n     \"//cc/mojo_embedder\",\n     \"//components/paint_preview/common\",\ndiff --git a/third_party/blink/renderer/platform/fonts/font.cc b/third_party/blink/renderer/platform/fonts/font.cc\nindex 3d1b463e9651c..4625300729523 100644\n--- a/third_party/blink/renderer/platform/fonts/font.cc\n+++ b/third_party/blink/renderer/platform/fonts/font.cc\n@@ -49,7 +49,7 @@\n #include \"third_party/skia/include/core/SkTextBlob.h\"\n #include \"ui/gfx/geometry/rect_f.h\"\n \n-#include \"carbonyl/src/browser/blink.h\"\n+#include \"carbonyl/src/browser/bridge.h\"\n \n namespace blink {\n \n@@ -158,7 +158,7 @@ void DrawBlobs(cc::PaintCanvas* canvas,\n                const ShapeResultBloberizer::BlobBuffer& blobs,\n                const gfx::PointF& point,\n                cc::NodeId node_id = cc::kInvalidNodeId) {  \n-  if (!carbonyl::blink::BitmapMode()) {\n+  if (!carbonyl::Bridge::BitmapMode()) {\n     return;\n   }\n \n@@ -237,7 +237,7 @@ void Font::DrawText(cc::PaintCanvas* canvas,\n   if (ShouldSkipDrawing())\n     return;\n \n-  if (!carbonyl::blink::BitmapMode()) {\n+  if (!carbonyl::Bridge::BitmapMode()) {\n     auto string = StringView(\n       run_info.run.ToStringView(),\n       run_info.from,\n@@ -285,7 +285,7 @@ void Font::DrawText(cc::PaintCanvas* canvas,\n   if (ShouldSkipDrawing())\n     return;\n \n-  if (!carbonyl::blink::BitmapMode()) {\n+  if (!carbonyl::Bridge::BitmapMode()) {\n     auto string = StringView(\n       text_info.text,\n       text_info.from,\ndiff --git a/ui/display/BUILD.gn b/ui/display/BUILD.gn\nindex aadf9f2a52fd6..9b3aefc4d3b04 100644\n--- a/ui/display/BUILD.gn\n+++ b/ui/display/BUILD.gn\n@@ -101,6 +101,7 @@ component(\"display\") {\n     \"//base\",\n     \"//base:i18n\",\n     \"//build:chromeos_buildflags\",\n+    \"//carbonyl/src/browser:bridge\",\n     \"//mojo/public/cpp/bindings:struct_traits\",\n     \"//ui/display/mojom:mojom_shared_cpp_sources\",\n     \"//ui/display/util\",\ndiff --git a/ui/display/display.cc b/ui/display/display.cc\nindex 1d71f3b4c9857..d670831de4ca4 100644\n--- a/ui/display/display.cc\n+++ b/ui/display/display.cc\n@@ -26,39 +26,10 @@\n namespace display {\n namespace {\n \n-// This variable tracks whether the forced device scale factor switch needs to\n-// be read from the command line, i.e. if it is set to -1 then the command line\n-// is checked.\n-int g_has_forced_device_scale_factor = -1;\n-\n-// This variable caches the forced device scale factor value which is read off\n-// the command line. If the cache is invalidated by setting this variable to\n-// -1.0, we read the forced device scale factor again.\n-float g_forced_device_scale_factor = -1.0;\n-\n // An allowance error epsilon caused by fractional scale factor to produce\n // expected DP display size.\n constexpr float kDisplaySizeAllowanceEpsilon = 0.01f;\n \n-bool HasForceDeviceScaleFactorImpl() {\n-  // return base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kForceDeviceScaleFactor);\n-  return true;\n-}\n-\n-float GetForcedDeviceScaleFactorImpl() {\n-  // double scale_in_double = 1.0;\n-  // if (HasForceDeviceScaleFactorImpl()) {\n-  //   std::string value =\n-  //       base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(\n-  //           switches::kForceDeviceScaleFactor);\n-  //   if (!base::StringToDouble(value, &scale_in_double)) {\n-  //     LOG(ERROR) << \"Failed to parse the default device scale factor:\" << value;\n-  //     scale_in_double = 1.0;\n-  //   }\n-  // }\n-  return carbonyl::Bridge::GetCurrent()->GetDPI();\n-}\n-\n const char* ToRotationString(display::Display::Rotation rotation) {\n   switch (rotation) {\n     case display::Display::ROTATE_0:\n@@ -78,33 +49,19 @@ const char* ToRotationString(display::Display::Rotation rotation) {\n \n // static\n float Display::GetForcedDeviceScaleFactor() {\n-  if (g_forced_device_scale_factor < 0)\n-    g_forced_device_scale_factor = GetForcedDeviceScaleFactorImpl();\n-  return g_forced_device_scale_factor;\n+  return carbonyl::Bridge::GetDPI();\n }\n \n // static\n bool Display::HasForceDeviceScaleFactor() {\n-  if (g_has_forced_device_scale_factor == -1)\n-    g_has_forced_device_scale_factor = HasForceDeviceScaleFactorImpl();\n-  return !!g_has_forced_device_scale_factor;\n+  return true;\n }\n \n // static\n-void Display::ResetForceDeviceScaleFactorForTesting() {\n-  g_has_forced_device_scale_factor = -1;\n-  g_forced_device_scale_factor = -1.0;\n-}\n+void Display::ResetForceDeviceScaleFactorForTesting() {}\n \n // static\n-void Display::SetForceDeviceScaleFactor(double dsf) {\n-  // Reset any previously set values and unset the flag.\n-  // g_has_forced_device_scale_factor = -1;\n-  // g_forced_device_scale_factor = -1.0;\n-\n-  // base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(\n-  //     switches::kForceDeviceScaleFactor, base::StringPrintf(\"%.2f\", dsf));\n-}\n+void Display::SetForceDeviceScaleFactor(double dsf) {}\n \n // static\n gfx::ColorSpace Display::GetForcedRasterColorProfile() {\n"
  },
  {
    "path": "chromium/patches/chromium/0014-Move-Skia-text-rendering-control-to-bridge.patch",
    "content": "From 2275364ee7e16ba6b46f0f339e34326d4a8c7584 Mon Sep 17 00:00:00 2001\nFrom: Fathy Boundjadj <hey@fathy.fr>\nDate: Mon, 13 Feb 2023 17:13:38 +0100\nSubject: [PATCH 14/14] Move Skia text rendering control to bridge\n\n---\n content/renderer/render_frame_impl.cc | 5 -----\n skia/BUILD.gn                         | 2 +-\n 2 files changed, 1 insertion(+), 6 deletions(-)\n\ndiff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc\nindex 379cf6c58b2b0..891efd6a9d796 100644\n--- a/content/renderer/render_frame_impl.cc\n+++ b/content/renderer/render_frame_impl.cc\n@@ -285,7 +285,6 @@\n #include \"third_party/skia/include/svg/SkSVGCanvas.h\"\n #include \"third_party/skia/include/utils/SkBase64.h\"\n #include \"third_party/skia/src/text/GlyphRun.h\"\n-#include \"third_party/skia/src/core/SkBitmapDevice.h\"\n #include \"third_party/skia/src/core/SkClipStackDevice.h\"\n #include \"third_party/skia/src/core/SkDevice.h\"\n #include \"third_party/skia/src/core/SkFontPriv.h\"\n@@ -2244,10 +2243,6 @@ void RenderFrameImpl::Initialize(blink::WebFrame* parent) {\n   );\n \n   host->ObserveTerminalRender(render_callback_);\n-\n-  if (!carbonyl::Bridge::BitmapMode()) {\n-    SkBitmapDevice::DisableTextRendering();\n-  }\n }\n \n void RenderFrameImpl::GetInterface(\ndiff --git a/skia/BUILD.gn b/skia/BUILD.gn\nindex b330273c16db3..297ffacf073fa 100644\n--- a/skia/BUILD.gn\n+++ b/skia/BUILD.gn\n@@ -203,7 +203,7 @@ source_set(\"skcms\") {\n }\n \n component(\"skia\") {\n-  deps = []\n+  deps = [ \"//carbonyl/src/browser:bridge\" ]\n   sources = [\n     # Chrome sources.\n     \"config/SkUserConfig.h\",\n"
  },
  {
    "path": "chromium/patches/skia/0001-Disable-text-rendering.patch",
    "content": "From 218fbf4bba772c465712c4ea442adb57968e9c22 Mon Sep 17 00:00:00 2001\nFrom: Fathy Boundjadj <hey@fathy.fr>\nDate: Mon, 13 Feb 2023 17:18:18 +0100\nSubject: [PATCH 1/2] Disable text rendering\n\n---\n src/core/SkBitmapDevice.cpp | 8 ++++++--\n 1 file changed, 6 insertions(+), 2 deletions(-)\n\ndiff --git a/src/core/SkBitmapDevice.cpp b/src/core/SkBitmapDevice.cpp\nindex b497d690f7..9631f47967 100644\n--- a/src/core/SkBitmapDevice.cpp\n+++ b/src/core/SkBitmapDevice.cpp\n@@ -28,6 +28,8 @@\n #include \"src/image/SkImage_Base.h\"\n #include \"src/text/GlyphRun.h\"\n \n+#include \"carbonyl/src/browser/bridge.h\"\n+\n struct Bounder {\n     SkRect  fBounds;\n     bool    fHasBounds;\n@@ -522,8 +524,10 @@ void SkBitmapDevice::onDrawGlyphRunList(SkCanvas* canvas,\n                                         const sktext::GlyphRunList& glyphRunList,\n                                         const SkPaint& initialPaint,\n                                         const SkPaint& drawingPaint) {\n-    SkASSERT(!glyphRunList.hasRSXForm());\n-    LOOP_TILER( drawGlyphRunList(canvas, &fGlyphPainter, glyphRunList, drawingPaint), nullptr )\n+    if (carbonyl::Bridge::BitmapMode()) {\n+        SkASSERT(!glyphRunList.hasRSXForm());\n+        LOOP_TILER( drawGlyphRunList(canvas, &fGlyphPainter, glyphRunList, drawingPaint), nullptr )\n+    }\n }\n \n void SkBitmapDevice::drawVertices(const SkVertices* vertices,\n"
  },
  {
    "path": "chromium/patches/skia/0002-Export-some-private-APIs.patch",
    "content": "From a271b203a2b60f0cd450bda0fa2cc14885f1d9a8 Mon Sep 17 00:00:00 2001\nFrom: Fathy Boundjadj <hey@fathy.fr>\nDate: Thu, 9 Feb 2023 03:38:05 +0100\nSubject: [PATCH 2/2] Export some private APIs\n\nTemporary until TextCaptureDevice moves here\n---\n include/utils/SkBase64.h     | 2 +-\n src/core/SkClipStack.h       | 2 +-\n src/core/SkClipStackDevice.h | 2 +-\n src/core/SkDevice.h          | 2 +-\n src/core/SkFontPriv.h        | 2 +-\n 5 files changed, 5 insertions(+), 5 deletions(-)\n\ndiff --git a/include/utils/SkBase64.h b/include/utils/SkBase64.h\nindex e01028543a..beddbd2c95 100644\n--- a/include/utils/SkBase64.h\n+++ b/include/utils/SkBase64.h\n@@ -12,7 +12,7 @@\n \n #include <cstddef>\n \n-struct SkBase64 {\n+struct SK_API SkBase64 {\n public:\n     enum Error {\n         kNoError,\ndiff --git a/src/core/SkClipStack.h b/src/core/SkClipStack.h\nindex c325d2c619..d93b9cf37f 100644\n--- a/src/core/SkClipStack.h\n+++ b/src/core/SkClipStack.h\n@@ -30,7 +30,7 @@ class GrProxyProvider;\n // (i.e., the fSaveCount in force when it was added). Restores are thus\n // implemented by removing clips from fDeque that have an fSaveCount larger\n // then the freshly decremented count.\n-class SkClipStack {\n+class SK_API SkClipStack {\n public:\n     enum BoundsType {\n         // The bounding box contains all the pixels that can be written to\ndiff --git a/src/core/SkClipStackDevice.h b/src/core/SkClipStackDevice.h\nindex eff1f1a440..a8d6b4fe07 100644\n--- a/src/core/SkClipStackDevice.h\n+++ b/src/core/SkClipStackDevice.h\n@@ -11,7 +11,7 @@\n #include \"src/core/SkClipStack.h\"\n #include \"src/core/SkDevice.h\"\n \n-class SkClipStackDevice : public SkBaseDevice {\n+class SK_API SkClipStackDevice : public SkBaseDevice {\n public:\n     SkClipStackDevice(const SkImageInfo& info, const SkSurfaceProps& props)\n         : SkBaseDevice(info, props)\ndiff --git a/src/core/SkDevice.h b/src/core/SkDevice.h\nindex e0fed94b9b..c7194f9c1c 100644\n--- a/src/core/SkDevice.h\n+++ b/src/core/SkDevice.h\n@@ -54,7 +54,7 @@ struct SkStrikeDeviceInfo {\n     const sktext::gpu::SDFTControl* const fSDFTControl;\n };\n \n-class SkBaseDevice : public SkRefCnt, public SkMatrixProvider {\n+class SK_API SkBaseDevice : public SkRefCnt, public SkMatrixProvider {\n public:\n     SkBaseDevice(const SkImageInfo&, const SkSurfaceProps&);\n \ndiff --git a/src/core/SkFontPriv.h b/src/core/SkFontPriv.h\nindex 95ca905bf1..a31aba8e2b 100644\n--- a/src/core/SkFontPriv.h\n+++ b/src/core/SkFontPriv.h\n@@ -16,7 +16,7 @@\n class SkReadBuffer;\n class SkWriteBuffer;\n \n-class SkFontPriv {\n+class SK_API SkFontPriv {\n public:\n     /*  This is the size we use when we ask for a glyph's path. We then\n      *  post-transform it as we draw to match the request.\n"
  },
  {
    "path": "chromium/patches/webrtc/0001-Disable-GIO-on-Linux.patch",
    "content": "From 5ad57c96f23739717bcea018baf2bc8f4157b01d Mon Sep 17 00:00:00 2001\nFrom: Fathy Boundjadj <hey@fathy.fr>\nDate: Mon, 13 Feb 2023 16:37:40 +0100\nSubject: [PATCH] Disable GIO on Linux\n\n---\n modules/portal/BUILD.gn | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/modules/portal/BUILD.gn b/modules/portal/BUILD.gn\nindex 36bcb53e8e..822688b1dc 100644\n--- a/modules/portal/BUILD.gn\n+++ b/modules/portal/BUILD.gn\n@@ -85,7 +85,7 @@ if ((is_linux || is_chromeos) && rtc_use_pipewire) {\n     # `rtc_use_pipewire` is not set, which causes pipewire_config to not be\n     # included in targets. More details in: webrtc:13898\n     if (is_linux && !is_castos) {\n-      defines += [ \"WEBRTC_USE_GIO\" ]\n+      $ defines += [ \"WEBRTC_USE_GIO\" ]\n     }\n   }\n \n"
  },
  {
    "path": "cliff.toml",
    "content": "[changelog]\nheader = \"\"\"\n# Changelog\\n\nAll notable changes to this project will be documented in this file.\\n\n\"\"\"\nbody = \"\"\"\n{% if version %}\\\n    ## [{{ version | trim_start_matches(pat=\"v\") }}] - {{ timestamp | date(format=\"%Y-%m-%d\") }}\n{% else %}\\\n    ## [unreleased]\n{% endif %}\\\n{% for group, commits in commits | group_by(attribute=\"group\") %}\n    ### {{ group | striptags | trim | upper_first }}\n    {% for commit in commits %}\n        - {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}\\\n            {%- if commit.links %} ({% for link in commit.links %}[{{link.text}}]({{link.href}}){% endfor -%}){% endif %}\\\n    {% endfor %}\n{% endfor %}\\n\n\"\"\"\ntrim = true\nfooter = \"\"\n\n[git]\nconventional_commits = true\nfilter_unconventional = true\nsplit_commits = false\ncommit_preprocessors = [\n    { pattern = \"#([0-9]+)\", replace = \"[#${1}](https://github.com/fathyb/carbonyl/issues/${1})\" }\n]\ncommit_parsers = [\n    { message = \"^feat\", group = \"<!-- 0 -->🚀 Features\" },\n    { message = \"^fix\", group = \"<!-- 1 -->🐛 Bug Fixes\" },\n    { message = \"^doc\", group = \"<!-- 2 -->📖 Documentation\" },\n    { message = \"^perf\", group = \"<!-- 3 -->⚡ Performance\"},\n    { message = \"^chore\", skip = true },\n    { body = \".*security\", group = \"<!-- 8 -->🔐 Security\"},\n]\nprotect_breaking_commits = false\nfilter_commits = false\ntag_pattern = \"v[0-9]*\"\nskip_tags = \"\"\nignore_tags = \"\"\ntopo_order = true\nsort_commits = \"oldest\"\n"
  },
  {
    "path": "license.md",
    "content": "_Copyright © 2023, Fathy Boundjadj_\n_All rights reserved._\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright\n   notice, this list of conditions and the following disclaimer.\n2. Redistributions in binary form must reproduce the above copyright\n   notice, this list of conditions and the following disclaimer in the\n   documentation and/or other materials provided with the distribution.\n3. Neither the name of the copyright holder nor the\n   names of its contributors may be used to endorse or promote products\n   derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY\nDIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"carbonyl\",\n  \"version\": \"0.0.3\",\n  \"license\": \"BSD-3-Clause\"\n}\n"
  },
  {
    "path": "readme.md",
    "content": "<table align=\"center\">\n  <tbody>\n    <tr>\n      <td>\n        <p></p>\n        <pre>\n   O    O\n    \\  /\nO —— Cr —— O\n    /  \\\n   O    O</pre>\n      </td>\n      <td><h1>Carbonyl</h1></td>\n    </tr>\n  </tbody>\n</table>\n\nCarbonyl is a Chromium based browser built to run in a terminal. [Read the blog post](https://fathy.fr/carbonyl).\n\nIt supports pretty much all Web APIs including WebGL, WebGPU, audio and video playback, animations, etc..\n\nIt'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.\n\nCarbonyl originally started as [`html2svg`](https://github.com/fathyb/html2svg) and is now the runtime behind it.\n\n## Usage\n\n> Carbonyl on Linux without Docker requires the same dependencies as Chromium.\n\n### Docker\n\n```shell\n$ docker run --rm -ti fathyb/carbonyl https://youtube.com\n```\n\n### npm\n\n```console\n$ npm install --global carbonyl\n$ carbonyl https://github.com\n```\n\n### Binaries\n\n- [macOS amd64](https://github.com/fathyb/carbonyl/releases/download/v0.0.3/carbonyl.macos-amd64.zip)\n- [macOS arm64](https://github.com/fathyb/carbonyl/releases/download/v0.0.3/carbonyl.macos-arm64.zip)\n- [Linux amd64](https://github.com/fathyb/carbonyl/releases/download/v0.0.3/carbonyl.linux-amd64.zip)\n- [Linux arm64](https://github.com/fathyb/carbonyl/releases/download/v0.0.3/carbonyl.linux-arm64.zip)\n\n## Demo\n\n<table>\n  <tbody>\n    <tr>\n      <td>\n        <video src=\"https://user-images.githubusercontent.com/5746414/213682926-f1cc2de7-a38c-4125-9257-92faecfc7e24.mp4\">\n      </td>\n      <td>\n        <video src=\"https://user-images.githubusercontent.com/5746414/213682913-398d3d11-1af8-4ae6-a0cd-a7f878efd88b.mp4\">\n      </td>\n    </tr>\n    <tr>\n      <td colspan=\"2\">\n        <video src=\"https://user-images.githubusercontent.com/5746414/213682918-d6396a4f-ee23-431d-828e-4ad6a00e690e.mp4\">\n      </td>\n    </tr>\n  </tbody>\n</table>\n\n## Known issues\n\n- Fullscreen mode not supported yet\n\n## Comparisons\n\n### Lynx\n\nLynx is the original terminal web browser, and the oldest one still maintained.\n\n#### Pros\n\n- When it understands a page, Lynx has the best layout, fully optimized for the terminal\n\n#### Cons\n\n> Some might sound like pluses, but Browsh and Carbonyl let you disable most of those if you'd like\n\n- Does not support a lot of modern web standards\n- Cannot run JavaScript/WebAssembly\n- Cannot view or play media (audio, video, DOOM)\n\n### Browsh\n\nBrowsh is the original \"normal browser into a terminal\" project. It starts Firefox in headless mode and connects to it through an automation protocol.\n\n#### Pro\n\n- It's easier to update the underlying browser: just update Firefox\n- This makes development easier: just install Firefox and compile the Go code in a few seconds\n- As of today, Browsh supports extensions while Carbonyl doesn't, although it's on our roadmap\n\n#### Cons\n\n- 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.\n- It uses custom stylesheets to fix the layout, which is less reliable than Carbonyl's changes to its HTML engine (Blink).\n\n## Operating System Support\n\nAs far as tested, the operating systems under are supported:\n\n- Linux (Debian, Ubuntu and Arch tested)\n- MacOS\n- Windows 11 and WSL\n\n## Contributing\n\nCarbonyl 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).\n\nThe 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.\n\nIf you're just making changes to the Rust code, build `libcarbonyl` and replace it in a release version of Carbonyl.\n\n### Core\n\n```console\n$ cargo build\n```\n\n### Runtime\n\nFew notes:\n\n- 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..\n- Building Chromium for arm64 on Linux requires an amd64 processor\n- Carbonyl is only tested on Linux and macOS, other platforms likely require code changes to Chromium\n- 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.\n\n#### Fetch\n\n> Fetch Chromium's code.\n\n```console\n$ ./scripts/gclient.sh sync\n```\n\n#### Apply patches\n\n> Any changes made to Chromium will be reverted, make sure to save any changes you made.\n\n```console\n$ ./scripts/patches.sh apply\n```\n\n#### Configure\n\n```console\n$ ./scripts/gn.sh args out/Default\n```\n\n> `Default` is the target name, you can use multiple ones and pick any name you'd like, i.e.:\n>\n> ```console\n> $ ./scripts/gn.sh args out/release\n> $ ./scripts/gn.sh args out/debug\n> # or if you'd like to build a multi-platform image\n> $ ./scripts/gn.sh args out/arm64\n> $ ./scripts/gn.sh args out/amd64\n> ```\n\nWhen prompted, enter the following arguments:\n\n```gn\nimport(\"//carbonyl/src/browser/args.gn\")\n\n# uncomment this to build for arm64\n# target_cpu = \"arm64\"\n\n# comment this to disable ccache\ncc_wrapper = \"env CCACHE_SLOPPINESS=time_macros ccache\"\n\n# comment this for a debug build\nis_debug = false\nsymbol_level = 0\nis_official_build = true\n```\n\n#### Build binaries\n\n```console\n$ ./scripts/build.sh Default\n```\n\nThis should produce the following outputs:\n\n- `out/Default/headless_shell`: browser binary\n- `out/Default/icudtl.dat`\n- `out/Default/libEGL.so`\n- `out/Default/libGLESv2.so`\n- `out/Default/v8_context_snapshot.bin`\n\n#### Build Docker image\n\n```console\n# Build arm64 Docker image using binaries from the Default target\n$ ./scripts/docker-build.sh Default arm64\n# Build amd64 Docker image using binaries from the Default target\n$ ./scripts/docker-build.sh Default amd64\n```\n\n#### Run\n\n```\n$ ./scripts/run.sh Default https://wikipedia.org\n```\n"
  },
  {
    "path": "scripts/build.sh",
    "content": "#!/usr/bin/env bash\n\nexport CARBONYL_ROOT=$(cd $(dirname -- \"$0\") && dirname -- \"$(pwd)\")\nexport INSTALL_DEPOT_TOOLS=\"true\"\n\ncd \"$CARBONYL_ROOT\"\nsource scripts/env.sh\n\ntarget=\"$1\"\ncpu=\"$2\"\n\nif [ ! -z \"$target\" ]; then\n    shift\nfi\nif [ ! -z \"$cpu\" ]; then\n    shift\nfi\n\ntriple=$(scripts/platform-triple.sh \"$cpu\")\n\nif [ -z \"$CARBONYL_SKIP_CARGO_BUILD\" ]; then\n    if [ -z \"$MACOSX_DEPLOYMENT_TARGET\" ]; then\n        export MACOSX_DEPLOYMENT_TARGET=10.13\n    fi\n\n    cargo build --target \"$triple\" --release\nfi\n\nif [ -f \"build/$triple/release/libcarbonyl.dylib\" ]; then\n    cp \"build/$triple/release/libcarbonyl.dylib\" \"$CHROMIUM_SRC/out/$target\"\n    install_name_tool \\\n        -id @executable_path/libcarbonyl.dylib \\\n        \"build/$triple/release/libcarbonyl.dylib\"\nelse\n    cp \"build/$triple/release/libcarbonyl.so\" \"$CHROMIUM_SRC/out/$target\"\nfi\n\ncd \"$CHROMIUM_SRC/out/$target\"\n\nninja headless:headless_shell \"$@\"\n"
  },
  {
    "path": "scripts/changelog.sh",
    "content": "#!/usr/bin/env bash\n\nexport CARBONYL_ROOT=$(cd $(dirname -- \"$0\") && dirname -- $(pwd))\n\ncd \"$CARBONYL_ROOT\"\nsource \"scripts/env.sh\"\n\ngit cliff a69e8b609625b67a3e52e18f73ba5d0f49ceb7c3..HEAD \"$@\" > changelog.md\n"
  },
  {
    "path": "scripts/copy-binaries.sh",
    "content": "#!/usr/bin/env bash\n\nexport CARBONYL_ROOT=$(cd $(dirname -- \"$0\") && dirname -- $(pwd))\n\ncd \"$CARBONYL_ROOT\"\nsource \"scripts/env.sh\"\n\ntarget=\"$1\"\ncpu=\"$2\"\n\ntriple=$(scripts/platform-triple.sh \"$cpu\")\ndest=\"build/pre-built/$triple\"\nsrc=\"$CHROMIUM_SRC/out/$target\"\n\nlib_ext=\"so\"\nif [ -f \"$src\"/libEGL.dylib ]; then\n    lib_ext=\"dylib\"\nfi\n\nrm -rf \"$dest\"\nmkdir -p \"$dest\"\ncd \"$dest\"\n\ncp \"$src/headless_shell\" carbonyl\ncp \"$src/icudtl.dat\" .\ncp \"$src/libEGL.$lib_ext\" .\ncp \"$src/libGLESv2.$lib_ext\" .\ncp \"$src\"/v8_context_snapshot*.bin .\ncp \"$CARBONYL_ROOT/build/$triple/release/libcarbonyl.$lib_ext\" .\n\nfiles=\"carbonyl \"\n\nif [ \"$lib_ext\" == \"so\" ]; then\n    cp \"$src/libvk_swiftshader.so\" .\n    cp \"$src/libvulkan.so.1\" .\n    cp \"$src/vk_swiftshader_icd.json\" .\n\n    files+=$(echo *.so *.so.1)\nfi\n\nif [[ \"$cpu\" == \"arm64\" ]] && command -v aarch64-linux-gnu-strip; then\n    aarch64-linux-gnu-strip $files\nelse\n    strip $files\nfi\n\necho \"Binaries copied to $dest\"\n"
  },
  {
    "path": "scripts/docker-build.sh",
    "content": "#!/usr/bin/env bash\n\nexport CARBONYL_ROOT=$(cd $(dirname -- \"$0\") && dirname -- \"$(pwd)\")\n\ncd \"$CARBONYL_ROOT\"\nsource \"scripts/env.sh\"\n\ncpu=\"$1\"\n\ntriple=$(scripts/platform-triple.sh \"$cpu\" linux)\nbuild_dir=\"build/docker/$triple\"\n\nrm -rf \"$build_dir\"\nmkdir -p \"build/docker\"\ncp -r \"$CARBONYL_ROOT/build/pre-built/$triple\" \"$build_dir\"\ncp \"$CARBONYL_ROOT/Dockerfile\" \"$build_dir\"\n\ntag=\"fathyb/carbonyl:$cpu\"\n\ndocker buildx build \"$build_dir\" --load --platform \"linux/$cpu\" --tag \"$tag\"\n\necho \"Image tag: $tag\"\n"
  },
  {
    "path": "scripts/docker-push.sh",
    "content": "#!/usr/bin/env bash\n\nexport CARBONYL_ROOT=$(cd $(dirname -- \"$0\") && dirname -- \"$(pwd)\")\n\nsource \"$CARBONYL_ROOT/scripts/env.sh\"\n\ntag=\"fathyb/carbonyl\"\nversion=\"$1\"\n\necho \"Pushing arm64 image as $tag:$version-arm64\"\ndocker tag \"$tag:arm64\" \"$tag:$version-arm64\"\ndocker push \"$tag:$version-arm64\"\n\necho \"Pushing amd64 image as $tag:$version-amd64\"\ndocker tag \"$tag:amd64\" \"$tag:$version-amd64\"\ndocker push \"$tag:$version-amd64\"\n\ndocker manifest create \"$tag:$version\" \\\n    --amend \"$tag:$version-arm64\" \\\n    --amend \"$tag:$version-amd64\"\n\ndocker manifest push \"$tag:$version\" --purge\n"
  },
  {
    "path": "scripts/env.sh",
    "content": "#!/usr/bin/env bash\n\nset -eo pipefail\n\nif [ -z \"$CARBONYL_ROOT\" ]; then\n    echo \"CARBONYL_ROOT should be defined\"\n\n    exit 2\nfi\n\nif [ -z \"$CHROMIUM_ROOT\" ]; then\n    export CHROMIUM_ROOT=\"$CARBONYL_ROOT/chromium\"\nfi\nif [ -z \"$CHROMIUM_SRC\" ]; then\n    export CHROMIUM_SRC=\"$CHROMIUM_ROOT/src\"\nfi\nif [ -z \"$DEPOT_TOOLS_ROOT\" ]; then\n    export DEPOT_TOOLS_ROOT=\"$CHROMIUM_ROOT/depot_tools\"\nfi\n\nexport PATH=\"$PATH:$DEPOT_TOOLS_ROOT\"\n\nif [ \"$INSTALL_DEPOT_TOOLS\" = \"true\" ] && [ ! -f \"$DEPOT_TOOLS_ROOT/README.md\" ]; then\n    echo \"depot_tools not found, fetching submodule..\"\n\n    git -C \"$CARBONYL_ROOT\" submodule update --init --recursive\nfi\n"
  },
  {
    "path": "scripts/gclient.sh",
    "content": "#!/usr/bin/env bash\n\nexport CARBONYL_ROOT=$(cd $(dirname -- \"$0\") && dirname -- \"$(pwd)\")\nexport INSTALL_DEPOT_TOOLS=\"true\"\n\nsource \"$CARBONYL_ROOT/scripts/env.sh\"\n\n(\n    cd \"$CHROMIUM_ROOT\" &&\n    gclient \"$@\"\n)\n"
  },
  {
    "path": "scripts/gn.sh",
    "content": "#!/usr/bin/env bash\n\nexport CARBONYL_ROOT=$(cd $(dirname -- \"$0\") && dirname -- \"$(pwd)\")\nexport INSTALL_DEPOT_TOOLS=\"true\"\n\nsource \"$CARBONYL_ROOT/scripts/env.sh\"\n\n(\n    cd \"$CHROMIUM_SRC\" &&\n    gn \"$@\"\n)\n"
  },
  {
    "path": "scripts/npm-package.mjs",
    "content": "import fs from \"fs/promises\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\n\nconst dirname = path.dirname(fileURLToPath(import.meta.url));\nconst pkg = JSON.parse(\n  await fs.readFile(path.resolve(dirname, \"../package.json\"), \"utf-8\")\n);\nconst version = process.env.RELEASE_MODE\n  ? pkg.version\n  : `${pkg.version}-next.${process.env.VERSION_ID}`;\nconst manifest = {\n  version,\n  license: \"BSD-3-Clause\",\n  description: \"Chromium running in your terminal\",\n  homepage: \"https://github.com/fathyb/carbonyl\",\n  repository: \"fathyb/carbonyl\",\n  bugs: \"https://github.com/fathyb/carbonyl/issues\",\n  author: {\n    name: \"Fathy Boundjadj\",\n    email: \"hey@fathy.fr\",\n    url: \"https://fathy.fr\",\n  },\n};\n\nasync function buildMain() {\n  const root = path.resolve(dirname, \"../build/packages/carbonyl\");\n\n  await fs.rm(root, { recursive: true, force: true });\n  await fs.mkdir(root, { recursive: true });\n  await Promise.all([\n    Promise.all(\n      [\"readme.md\", \"license.md\"].map((file) =>\n        fs.cp(path.join(dirname, \"..\", file), path.join(root, file))\n      )\n    ),\n    fs.writeFile(\n      path.join(root, \"package.json\"),\n      JSON.stringify(\n        {\n          name: \"carbonyl\",\n          ...manifest,\n          files: [\"index.sh\", \"index.sh.js\", \"index.js\"],\n          bin: { carbonyl: \"index.sh\" },\n          optionalDependencies: {\n            \"@fathyb/carbonyl-linux-amd64\": version,\n            \"@fathyb/carbonyl-linux-arm64\": version,\n            \"@fathyb/carbonyl-macos-amd64\": version,\n            \"@fathyb/carbonyl-macos-arm64\": version,\n          },\n        },\n        null,\n        4\n      )\n    ),\n    fs.writeFile(\n      path.join(root, \"index.sh\"),\n      [\"#!/usr/bin/env bash\", `\"$(node \"$(realpath \"$0\")\".js)\" \"$@\"`].join(\n        \"\\n\"\n      ),\n      { mode: \"755\" }\n    ),\n    fs.writeFile(\n      path.join(root, \"index.sh.js\"),\n      `process.stdout.write(require('.'))`\n    ),\n    fs.writeFile(\n      path.join(root, \"index.js\"),\n      `\n        const archs = {\n            x64: 'amd64',\n            arm64: 'arm64',\n        }\n        const platforms = {\n            linux: 'linux',\n            darwin: 'macos',\n        }\n\n        const arch = archs[process.arch]\n        const platform = platforms[process.platform]\n\n        if (!arch) {\n            throw new Error('Processor architecture not supported: ' + process.arch)\n        }\n        if (!platform) {\n            throw new Error('Platform not supported: ' + process.platform)\n        }\n\n        module.exports = require('@fathyb/carbonyl-' + platform + '-' + arch)\n      `\n    ),\n  ]);\n\n  return root;\n}\nasync function buildPlatform([os, npmOs, llvmOs], [cpu, npmCpu, llvmCpu]) {\n  const pkg = `carbonyl-${os}-${cpu}`;\n  const root = path.resolve(dirname, `../build/packages/${pkg}`);\n\n  await fs.rm(root, { recursive: true, force: true });\n  await fs.mkdir(root, { recursive: true });\n  await Promise.all([\n    Promise.all(\n      [\"readme.md\", \"license.md\"].map((file) =>\n        fs.cp(path.join(dirname, \"..\", file), path.join(root, file))\n      )\n    ),\n    fs.cp(\n      path.join(dirname, `../build/pre-built/${llvmCpu}-${llvmOs}`),\n      path.join(root, \"build\"),\n      { recursive: true }\n    ),\n    fs.writeFile(\n      path.join(root, \"package.json\"),\n      JSON.stringify(\n        {\n          name: `@fathyb/${pkg}`,\n          ...manifest,\n          files: [\"build\", \"index.js\"],\n          os: [npmOs],\n          cpu: [npmCpu],\n        },\n        null,\n        4\n      )\n    ),\n    fs.writeFile(\n      path.join(root, \"index.js\"),\n      `module.exports = __dirname + '/build/carbonyl'`\n    ),\n  ]);\n\n  return root;\n}\n\nconst [root, platforms] = await Promise.all([\n  buildMain(),\n\n  Promise.all(\n    [\n      [\"macos\", \"darwin\", \"apple-darwin\"],\n      [\"linux\", \"linux\", \"unknown-linux-gnu\"],\n    ].map(\n      async (os) =>\n        await Promise.all(\n          [\n            [\"arm64\", \"arm64\", \"aarch64\"],\n            [\"amd64\", \"x64\", \"x86_64\"],\n          ].map(async (cpu) => await buildPlatform(os, cpu))\n        )\n    )\n  ),\n]);\n"
  },
  {
    "path": "scripts/npm-package.sh",
    "content": "#!/usr/bin/env bash\n\nexport CARBONYL_ROOT=$(cd $(dirname -- \"$0\") && dirname -- $(pwd))\n\ncd \"$CARBONYL_ROOT\"\nsource \"scripts/env.sh\"\n\nVERSION_ID=\"$(git rev-parse --short HEAD)\" \\\n    node \"$CARBONYL_ROOT/scripts/npm-package.mjs\"\n"
  },
  {
    "path": "scripts/npm-publish.sh",
    "content": "#!/usr/bin/env bash\n\nexport CARBONYL_ROOT=$(cd $(dirname -- \"$0\") && dirname -- $(pwd))\n\ncd \"$CARBONYL_ROOT\"\nsource \"scripts/env.sh\"\n\ncd \"build/packages\"\n\nif [ -z \"$CARBONYL_PUBLISH_PLATFORM\" ] && [ -z \"$CARBONYL_PUBLISH_ARCH\" ]; then\n    cd \"carbonyl\"\nelse\n    cd \"carbonyl-$CARBONYL_PUBLISH_PLATFORM-$CARBONYL_PUBLISH_ARCH\"\nfi\n\nyarn publish --non-interactive --access public \"$@\"\n"
  },
  {
    "path": "scripts/patches.sh",
    "content": "#!/usr/bin/env bash\n\nexport CARBONYL_ROOT=$(cd $(dirname -- \"$0\") && dirname -- \"$(pwd)\")\n\nsource \"$CARBONYL_ROOT/scripts/env.sh\"\n\ncd \"$CHROMIUM_SRC\"\n\nchromium_upstream=\"92da8189788b1b373cbd3348f73d695dfdc521b6\"\nskia_upstream=\"486deb23bc2a4d3d09c66fef52c2ad64d8b4f761\"\nwebrtc_upstream=\"727080cbacd58a2f303ed8a03f0264fe1493e47a\"\n\nif [[ \"$1\" == \"apply\" ]]; then\n    echo \"Stashing Chromium changes..\"\n    git add -A .\n    git stash\n\n    echo \"Applying Chromium patches..\"\n    git checkout \"$chromium_upstream\"\n    git am --committer-date-is-author-date \"$CARBONYL_ROOT/chromium/patches/chromium\"/*\n    \"$CARBONYL_ROOT/scripts/restore-mtime.sh\" \"$chromium_upstream\"\n\n    echo \"Stashing Skia changes..\"\n    cd \"$CHROMIUM_SRC/third_party/skia\"\n    git add -A .\n    git stash\n\n    echo \"Applying Skia patches..\"\n    git checkout \"$skia_upstream\"\n    git am --committer-date-is-author-date \"$CARBONYL_ROOT/chromium/patches/skia\"/*\n    \"$CARBONYL_ROOT/scripts/restore-mtime.sh\" \"$skia_upstream\"\n\n    echo \"Stashing WebRTC changes..\"\n    cd \"$CHROMIUM_SRC/third_party/webrtc\"\n    git add -A .\n    git stash\n\n    echo \"Applying WebRTC patches..\"\n    git checkout \"$webrtc_upstream\"\n    git am --committer-date-is-author-date \"$CARBONYL_ROOT/chromium/patches/webrtc\"/*\n    \"$CARBONYL_ROOT/scripts/restore-mtime.sh\" \"$webrtc_upstream\"\n\n    echo \"Patches successfully applied\"\nelif [[ \"$1\" == \"save\" ]]; then\n    if [[ -d carbonyl ]]; then\n        git add -A carbonyl\n    fi\n\n    echo \"Updating Chromium patches..\"\n    rm -rf \"$CARBONYL_ROOT/chromium/patches/chromium\"\n    git format-patch --no-signature --output-directory \"$CARBONYL_ROOT/chromium/patches/chromium\" \"$chromium_upstream\"\n\n    echo \"Updating Skia patches..\"\n    cd \"$CHROMIUM_SRC/third_party/skia\"\n    rm -rf \"$CARBONYL_ROOT/chromium/patches/skia\"\n    git format-patch --no-signature --output-directory \"$CARBONYL_ROOT/chromium/patches/skia\" \"$skia_upstream\"\n\n    echo \"Updating WebRTC patches..\"\n    cd \"$CHROMIUM_SRC/third_party/webrtc\"\n    rm -rf \"$CARBONYL_ROOT/chromium/patches/webrtc\"\n    git format-patch --no-signature --output-directory \"$CARBONYL_ROOT/chromium/patches/webrtc\" \"$webrtc_upstream\"\n\n    echo \"Patches successfully updated\"\nelse\n    echo \"Unknown argument: $1\"\n\n    exit 2\nfi\n"
  },
  {
    "path": "scripts/platform-triple.sh",
    "content": "#!/usr/bin/env bash\n\nexport CARBONYL_ROOT=$(cd $(dirname -- \"$0\") && dirname -- $(pwd))\n\nsource \"$CARBONYL_ROOT/scripts/env.sh\"\n\ncpu=\"$1\"\nplatform=\"$2\"\n\nif [ -z \"$platform\" ]; then\n    if [[ \"$OSTYPE\" == \"linux-gnu\"* ]]; then\n        platform=\"linux\"\n    elif [[ \"$OSTYPE\" == \"darwin\"* ]]; then\n        platform=\"macos\"\n    else\n        echo \"Unsupported platform: $OSTYPE\"\n\n        exit 2\n    fi\nfi\n\nif [ \"$platform\" == \"linux\" ]; then\n    platform=\"unknown-linux-gnu\"\nelif  [ \"$platform\" == \"macos\" ]; then\n    platform=\"apple-darwin\"\nfi\n\nif [ -z \"$cpu\" ]; then\n    cpu=\"$(uname -m)\"\nfi\n\nif [[ \"$cpu\" == \"arm64\" ]]; then\n    cpu=\"aarch64\"\nelif [[ \"$cpu\" == \"amd64\" ]]; then\n    cpu=\"x86_64\"\nfi\n\necho -n \"$cpu-$platform\"\n"
  },
  {
    "path": "scripts/release.sh",
    "content": "#!/usr/bin/env bash\n\nexport CARBONYL_ROOT=$(cd $(dirname -- \"$0\") && dirname -- $(pwd))\n\ncd \"$CARBONYL_ROOT\"\nsource \"scripts/env.sh\"\n\nnpm version \"$1\" --no-git-tag-version\n\"$CARBONYL_ROOT/scripts/changelog.sh\" --tag \"$1\"\ngit add -A .\ngit commit -m \"chore(release): $1\"\ngit tag -a \"v$1\" -m \"chore(release): $1\"\n"
  },
  {
    "path": "scripts/restore-mtime.sh",
    "content": "#!/usr/bin/env bash\n\nexport CARBONYL_ROOT=$(cd $(dirname -- \"$0\") && dirname -- $(pwd))\n\nsource \"$CARBONYL_ROOT/scripts/env.sh\"\n\nfor file in $(git diff --name-only HEAD \"$1\"); do\n    mtime=\"$(git log --pretty=format:%cd -n 1 --date=format:%Y%m%d%H%M.%S \"$file\")\"\n\n    touch -t \"$mtime\" \"$file\"\ndone\n"
  },
  {
    "path": "scripts/run.sh",
    "content": "#!/usr/bin/env bash\n\nexport CARBONYL_ROOT=$(cd $(dirname -- \"$0\") && dirname -- \"$(pwd)\")\n\nsource \"$CARBONYL_ROOT/scripts/env.sh\"\n\ntarget=\"$1\"\nshift\n\n\"$CHROMIUM_SRC/out/$target/headless_shell\" \"$@\"\n"
  },
  {
    "path": "scripts/runtime-hash.sh",
    "content": "#!/usr/bin/env bash\n\nexport CARBONYL_ROOT=$(cd $(dirname -- \"$0\") && dirname -- $(pwd))\n\ncd \"$CARBONYL_ROOT\"\nsource \"scripts/env.sh\"\n\nfor file in chromium/.gclient chromium/patches/*/*.patch src/browser/*.{cc,h,gn,mojom}; do\n    file_sha=$(cat \"$file\" | openssl sha256)\n    result=$(echo -n \"$sha/${file_sha: -64}\" | openssl sha256)\n    sha+=\"${file_sha: -64} ${file}\"$'\\n'\ndone\n\nhash=$(echo \"$sha\" | sort | openssl sha256)\n\necho -n \"${hash: -16}\"\n"
  },
  {
    "path": "scripts/runtime-pull.sh",
    "content": "#!/usr/bin/env bash\n\nexport CARBONYL_ROOT=$(cd $(dirname -- \"$0\") && dirname -- $(pwd))\n\ncd \"$CARBONYL_ROOT\"\nsource \"scripts/env.sh\"\n\necho \"Computing Chromium patches sha..\"\n\nsha=\"$(scripts/runtime-hash.sh)\"\ntriple=\"$(scripts/platform-triple.sh \"$@\")\"\n\nif [ ! -f \"build/pre-built/$triple.tgz\" ]; then\n    url=\"https://carbonyl.fathy.fr/runtime/$sha/$triple.tgz\"\n\n    echo \"Downloading pre-built binaries from $url\"\n\n    mkdir -p build/pre-built\n\n    if ! curl --silent --fail --output \"build/pre-built/$triple.tgz\" \"$url\"; then\n        echo \"Pre-built binaries not available\"\n\n        exit 1\n    fi\nfi\n\necho \"Pre-build binaries available, extracting..\"\n\ncd build/pre-built\nrm -rf \"$triple\"\ntar -xvzf \"$triple.tgz\"\n"
  },
  {
    "path": "scripts/runtime-push.sh",
    "content": "#!/usr/bin/env bash\n\nexport CARBONYL_ROOT=$(cd $(dirname -- \"$0\") && dirname -- $(pwd))\n\ncd \"$CARBONYL_ROOT\"\nsource \"scripts/env.sh\"\n\necho \"Computing Chromium patches hash..\"\n\nhash=$(scripts/runtime-hash.sh)\ntriple=$(scripts/platform-triple.sh \"$1\")\n\necho \"Archiving binaries..\"\n\ncd build/pre-built\ntar cvzf \"$triple.tgz\" \"$triple\"\n\necho \"Binaries archived to build/pre-built/$triple.tgz\"\n\necho \"Pushing $triple.tgz to object storage..\"\n\nAWS_PAGER=\"\" \\\nAWS_ACCESS_KEY_ID=\"$CDN_ACCESS_KEY_ID\" \\\nAWS_SECRET_ACCESS_KEY=\"$CDN_SECRET_ACCESS_KEY\" \\\n    aws s3api put-object \\\n        --endpoint-url \"https://7985f304d3a79d71fb63aeb17a31fe30.r2.cloudflarestorage.com\" \\\n        --bucket \"carbonyl-runtime\" \\\n        --key \"runtime/$hash/$triple.tgz\" \\\n        --body \"$triple.tgz\"\n"
  },
  {
    "path": "src/browser/BUILD.gn",
    "content": "import(\"//build/config/compiler/compiler.gni\")\nimport(\"//mojo/public/tools/bindings/mojom.gni\")\n\nmojom(\"mojom\") {\n  sources = [ \"carbonyl.mojom\" ]\n\n  deps = [\n    \"//ui/gfx/geometry/mojom\",\n    \"//skia/public/mojom\",\n  ]\n}\n\ncomponent(\"bridge\") {\n  output_name = \"carbonyl_bridge\"\n  defines = [ \"CARBONYL_BRIDGE_IMPLEMENTATION\" ]\n  sources = [\n    \"bridge.cc\",\n    \"bridge.h\",\n  ]\n}\n\ncomponent(\"viz\") {\n  output_name = \"carbonyl_viz\"\n  defines = [ \"CARBONYL_VIZ_IMPLEMENTATION\" ]\n  sources = [\n    \"host_display_client.cc\",\n    \"host_display_client.h\",\n  ]\n\n  deps = [\n    \":renderer\",\n    \"//base\",\n    \"//components/viz/host\",\n    \"//services/viz/privileged/mojom\",\n  ]\n}\n\nconfig(\"lib\") {\n  target = \"\"\n\n  if (current_cpu == \"x64\") {\n    target += \"x86_64-\"\n  } else if (current_cpu == \"arm64\") {\n    target += \"aarch64-\"\n  }\n\n  if (is_mac) {\n    target += \"apple-darwin\"\n  } else if (is_linux) {\n    target += \"unknown-linux-gnu\"\n  }\n\n  libs = [\"carbonyl\"]\n  lib_dirs = [\"//carbonyl/build/$target/release\"]\n}\n\ncomponent(\"renderer\") {\n  output_name = \"carbonyl_renderer\"\n  defines = [ \"CARBONYL_RENDERER_IMPLEMENTATION\" ]\n  sources = [\n    \"render_service_impl.cc\",\n    \"render_service_impl.h\",\n    \"renderer.cc\",\n    \"renderer.h\",\n  ]\n\n  configs += [ \":lib\" ]\n  deps = [\n    \":mojom\",\n    \":bridge\",\n    \"//base\",\n    \"//skia\"\n  ]\n}\n"
  },
  {
    "path": "src/browser/args.gn",
    "content": "headless_enable_commands = false\nheadless_use_embedded_resources = true\n\n# Enable proprietary codecs such as H.264\nffmpeg_branding = \"Chrome\"\nproprietary_codecs = true\n\n# Disable unused dependencies\nozone_platform = \"headless\"\nozone_platform_x11 = false\n\nuse_static_angle = true\n\nuse_qt = false\nuse_gio = false\nuse_gtk = false\nuse_cups = false\nuse_dbus = false\nuse_glib = false\nuse_libpci = false\nuse_kerberos = false\nuse_vaapi_x11 = false\nuse_xkbcommon = false\n\nangle_use_x11 = false\nangle_use_wayland = false\n\nrtc_use_x11 = false\nrtc_use_pipewire = false\nrtc_use_x11_extensions = false\n\n# Linux only\n# use_wayland_gbm = false\n# use_system_libdrm = false\n# use_system_minigbm = false\n\n# Disable unused features\nenable_pdf = false\nenable_nacl = false\nenable_ppapi = false\nenable_printing = false\nenable_print_content_analysis = false\nenable_plugins = false\nenable_rust_json = false\nenable_tagged_pdf = false\nenable_media_remoting = false\nenable_speech_service = false\nenable_component_updater = false\nenable_screen_ai_service = false\nenable_system_notifications = false\nenable_browser_speech_service = false\nenable_webui_certificate_viewer = false\n"
  },
  {
    "path": "src/browser/bridge.cc",
    "content": "#include \"carbonyl/src/browser/bridge.h\"\n\nnamespace {\n\nfloat dpi_ = 0.0;\nbool bitmap_mode_ = false;\n\n}\n\nnamespace carbonyl {\n\nvoid Bridge::Resize() {}\n\nfloat Bridge::GetDPI() {\n    return dpi_;\n}\n\nbool Bridge::BitmapMode() {\n    return bitmap_mode_;\n}\n\nvoid Bridge::Configure(float dpi, bool bitmap_mode) {\n    dpi_ = dpi;\n    bitmap_mode_ = bitmap_mode;\n}\n\n}\n"
  },
  {
    "path": "src/browser/bridge.h",
    "content": "#ifndef CARBONYL_SRC_BROWSER_BRIDGE_H_\n#define CARBONYL_SRC_BROWSER_BRIDGE_H_\n\n#include \"carbonyl/src/browser/export.h\"\n\nnamespace carbonyl {\n\nclass Renderer;\n\nclass CARBONYL_BRIDGE_EXPORT Bridge {\npublic:\n  static float GetDPI();\n  static bool BitmapMode();\n\nprivate:\n  friend class Renderer;\n\n  static void Resize();\n  static void Configure(float dpi, bool bitmap_mode);\n};\n\n}\n\n#endif  // CARBONYL_SRC_BROWSER_BRIDGE_H_\n"
  },
  {
    "path": "src/browser/bridge.rs",
    "content": "use std::ffi::{CStr, CString};\nuse std::io::Write;\nuse std::process::{Command, Stdio};\nuse std::sync::{mpsc, Mutex};\nuse std::{env, io, thread};\n\nuse libc::{c_char, c_float, c_int, c_uchar, c_uint, c_void, size_t};\n\nuse crate::cli::{CommandLine, CommandLineProgram, EnvVar};\nuse crate::gfx::{Cast, Color, Point, Rect, Size};\nuse crate::output::{RenderThread, Window};\nuse crate::ui::navigation::NavigationAction;\nuse crate::{input, utils::log};\n\n#[repr(C)]\n#[derive(Copy, Clone)]\npub struct CSize {\n    width: c_uint,\n    height: c_uint,\n}\n#[repr(C)]\n#[derive(Copy, Clone)]\npub struct CPoint {\n    x: c_uint,\n    y: c_uint,\n}\n#[repr(C)]\n#[derive(Copy, Clone)]\npub struct CRect {\n    origin: CPoint,\n    size: CSize,\n}\n#[repr(C)]\n#[derive(Copy, Clone)]\npub struct CColor {\n    r: u8,\n    g: u8,\n    b: u8,\n}\n#[repr(C)]\n#[derive(Copy, Clone)]\npub struct CText {\n    text: *const c_char,\n    rect: CRect,\n    color: CColor,\n}\n\n#[repr(C)]\npub struct RendererBridge {\n    cmd: CommandLine,\n    window: Window,\n    renderer: RenderThread,\n}\n\nunsafe impl Send for RendererBridge {}\nunsafe impl Sync for RendererBridge {}\n\npub type RendererPtr = *const Mutex<RendererBridge>;\n\nimpl<T: Copy> From<CPoint> for Point<T>\nwhere\n    c_uint: Cast<T>,\n{\n    fn from(value: CPoint) -> Self {\n        Point::new(value.x, value.y).cast()\n    }\n}\nimpl From<Size<c_uint>> for CSize {\n    fn from(value: Size<c_uint>) -> Self {\n        Self {\n            width: value.width,\n            height: value.height,\n        }\n    }\n}\nimpl<T: Copy> From<CSize> for Size<T>\nwhere\n    c_uint: Cast<T>,\n{\n    fn from(value: CSize) -> Self {\n        Size::new(value.width, value.height).cast()\n    }\n}\nimpl From<CColor> for Color {\n    fn from(value: CColor) -> Self {\n        Color::new(value.r, value.g, value.b)\n    }\n}\n\n#[repr(C)]\n#[derive(Copy, Clone)]\npub struct BrowserDelegate {\n    shutdown: extern \"C\" fn(),\n    refresh: extern \"C\" fn(),\n    go_to: extern \"C\" fn(*const c_char),\n    go_back: extern \"C\" fn(),\n    go_forward: extern \"C\" fn(),\n    scroll: extern \"C\" fn(c_int),\n    key_press: extern \"C\" fn(c_char),\n    mouse_up: extern \"C\" fn(c_uint, c_uint),\n    mouse_down: extern \"C\" fn(c_uint, c_uint),\n    mouse_move: extern \"C\" fn(c_uint, c_uint),\n    post_task: extern \"C\" fn(extern \"C\" fn(*mut c_void), *mut c_void),\n}\n\nfn main() -> io::Result<Option<i32>> {\n    let cmd = match CommandLineProgram::parse_or_run() {\n        None => return Ok(Some(0)),\n        Some(cmd) => cmd,\n    };\n\n    if cmd.shell_mode {\n        return Ok(None);\n    }\n\n    let mut terminal = input::Terminal::setup();\n    let mut command = Command::new(env::current_exe()?);\n\n    if !cmd.bitmap {\n        command\n            .arg(\"--disable-threaded-scrolling\")\n            .arg(\"--disable-threaded-animation\");\n    }\n\n    let output = command\n        .args(cmd.args)\n        .env(EnvVar::ShellMode, \"1\")\n        .stdin(Stdio::inherit())\n        .stdout(Stdio::inherit())\n        .stderr(Stdio::piped())\n        .output()?;\n\n    terminal.teardown();\n\n    let code = output.status.code().unwrap_or(127);\n\n    if code != 0 || cmd.debug {\n        io::stderr().write_all(&output.stderr)?;\n    }\n\n    Ok(Some(code))\n}\n\n#[no_mangle]\npub extern \"C\" fn carbonyl_bridge_main() {\n    if let Some(code) = main().unwrap() {\n        std::process::exit(code)\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn carbonyl_bridge_bitmap_mode() -> bool {\n    CommandLine::parse().bitmap\n}\n\n#[no_mangle]\npub extern \"C\" fn carbonyl_bridge_get_dpi() -> c_float {\n    Window::read().dpi\n}\n\n#[no_mangle]\npub extern \"C\" fn carbonyl_renderer_create() -> RendererPtr {\n    let bridge = RendererBridge {\n        cmd: CommandLine::parse(),\n        window: Window::read(),\n        renderer: RenderThread::new(),\n    };\n\n    Box::into_raw(Box::new(Mutex::new(bridge)))\n}\n\n#[no_mangle]\npub extern \"C\" fn carbonyl_renderer_start(bridge: RendererPtr) {\n    {\n        let bridge = unsafe { bridge.as_ref() };\n        let mut bridge = bridge.unwrap().lock().unwrap();\n\n        bridge.renderer.enable()\n    }\n\n    carbonyl_renderer_resize(bridge);\n}\n\n#[no_mangle]\npub extern \"C\" fn carbonyl_renderer_resize(bridge: RendererPtr) {\n    let bridge = unsafe { bridge.as_ref() };\n    let mut bridge = bridge.unwrap().lock().unwrap();\n    let window = bridge.window.update();\n    let cells = window.cells.clone();\n\n    log::debug!(\"resizing renderer, terminal window: {:?}\", window);\n\n    bridge\n        .renderer\n        .render(move |renderer| renderer.set_size(cells));\n}\n\n#[no_mangle]\npub extern \"C\" fn carbonyl_renderer_push_nav(\n    bridge: RendererPtr,\n    url: *const c_char,\n    can_go_back: bool,\n    can_go_forward: bool,\n) {\n    let (bridge, url) = unsafe { (bridge.as_ref(), CStr::from_ptr(url)) };\n    let (mut bridge, url) = (bridge.unwrap().lock().unwrap(), url.to_owned());\n\n    bridge.renderer.render(move |renderer| {\n        renderer.push_nav(url.to_str().unwrap(), can_go_back, can_go_forward)\n    });\n}\n\n#[no_mangle]\npub extern \"C\" fn carbonyl_renderer_set_title(bridge: RendererPtr, title: *const c_char) {\n    let (bridge, title) = unsafe { (bridge.as_ref(), CStr::from_ptr(title)) };\n    let (mut bridge, title) = (bridge.unwrap().lock().unwrap(), title.to_owned());\n\n    bridge\n        .renderer\n        .render(move |renderer| renderer.set_title(title.to_str().unwrap()).unwrap());\n}\n\n#[no_mangle]\npub extern \"C\" fn carbonyl_renderer_draw_text(\n    bridge: RendererPtr,\n    text: *const CText,\n    text_size: size_t,\n) {\n    let (bridge, text) = unsafe { (bridge.as_ref(), std::slice::from_raw_parts(text, text_size)) };\n    let mut bridge = bridge.unwrap().lock().unwrap();\n    let mut vec = text\n        .iter()\n        .map(|text| {\n            let str = unsafe { CStr::from_ptr(text.text) };\n\n            (\n                str.to_str().unwrap().to_owned(),\n                text.rect.origin.into(),\n                text.rect.size.into(),\n                text.color.into(),\n            )\n        })\n        .collect::<Vec<(String, Point, Size, Color)>>();\n\n    bridge.renderer.render(move |renderer| {\n        renderer.clear_text();\n\n        for (text, origin, size, color) in std::mem::take(&mut vec) {\n            renderer.draw_text(&text, origin, size, color)\n        }\n    });\n}\n\n#[derive(Clone, Copy)]\nstruct CallbackData(*const c_void);\n\nimpl CallbackData {\n    pub fn as_ptr(&self) -> *const c_void {\n        self.0\n    }\n}\n\nunsafe impl Send for CallbackData {}\nunsafe impl Sync for CallbackData {}\n\n#[no_mangle]\npub extern \"C\" fn carbonyl_renderer_draw_bitmap(\n    bridge: RendererPtr,\n    pixels: *const c_uchar,\n    pixels_size: CSize,\n    rect: CRect,\n    callback: extern \"C\" fn(*const c_void),\n    callback_data: *const c_void,\n) {\n    let length = (pixels_size.width * pixels_size.height * 4) as usize;\n    let (bridge, pixels) = unsafe { (bridge.as_ref(), std::slice::from_raw_parts(pixels, length)) };\n    let callback_data = CallbackData(callback_data);\n    let mut bridge = bridge.unwrap().lock().unwrap();\n\n    bridge.renderer.render(move |renderer| {\n        renderer.draw_background(\n            pixels,\n            pixels_size.into(),\n            Rect {\n                size: rect.size.into(),\n                origin: rect.origin.into(),\n            },\n        );\n\n        callback(callback_data.as_ptr());\n    });\n}\n\n#[no_mangle]\npub extern \"C\" fn carbonyl_renderer_get_size(bridge: RendererPtr) -> CSize {\n    let bridge = unsafe { bridge.as_ref() };\n    let bridge = bridge.unwrap().lock().unwrap();\n\n    log::debug!(\"terminal size: {:?}\", bridge.window.browser);\n\n    bridge.window.browser.into()\n}\n\nextern \"C\" fn post_task_handler(callback: *mut c_void) {\n    let mut closure = unsafe { Box::from_raw(callback as *mut Box<dyn FnMut()>) };\n\n    closure()\n}\n\nunsafe fn post_task<F>(handle: extern \"C\" fn(extern \"C\" fn(*mut c_void), *mut c_void), run: F)\nwhere\n    F: FnMut() + Send + 'static,\n{\n    let closure: *mut Box<dyn FnMut()> = Box::into_raw(Box::new(Box::new(run)));\n\n    handle(post_task_handler, closure as *mut c_void);\n}\n\n/// Function called by the C++ code to listen for input events.\n///\n/// This will block so the calling code should start and own a dedicated thread.\n/// It will panic if there is any error.\n#[no_mangle]\npub extern \"C\" fn carbonyl_renderer_listen(bridge: RendererPtr, delegate: *mut BrowserDelegate) {\n    let bridge = unsafe { &*bridge };\n    let delegate = unsafe { *delegate };\n\n    use input::*;\n\n    thread::spawn(move || {\n        macro_rules! emit {\n            ($event:ident($($args:expr),*) => $closure:expr) => {{\n                let run = move || {\n                    (delegate.$event)($($args),*);\n\n                    $closure\n                };\n\n                unsafe { post_task(delegate.post_task, run) }\n            }};\n            ($event:ident($($args:expr),*)) => {{\n                emit!($event($($args),*) => {})\n            }};\n        }\n\n        listen(|mut events| {\n            bridge.lock().unwrap().renderer.render(move |renderer| {\n                let get_scale = || bridge.lock().unwrap().window.scale;\n                let scale = |col, row| {\n                    let scale = get_scale();\n\n                    scale\n                        .mul(((col as f32 + 0.5), (row as f32 - 0.5)))\n                        .floor()\n                        .cast()\n                        .into()\n                };\n                let dispatch = |action| {\n                    match action {\n                        NavigationAction::Ignore => (),\n                        NavigationAction::Forward => return true,\n                        NavigationAction::GoBack() => emit!(go_back()),\n                        NavigationAction::GoForward() => emit!(go_forward()),\n                        NavigationAction::Refresh() => emit!(refresh()),\n                        NavigationAction::GoTo(url) => {\n                            let c_str = CString::new(url).unwrap();\n\n                            emit!(go_to(c_str.as_ptr()))\n                        }\n                    };\n\n                    return false;\n                };\n\n                for event in std::mem::take(&mut events) {\n                    use Event::*;\n\n                    match event {\n                        Exit => (),\n                        Scroll { delta } => {\n                            let scale = get_scale();\n\n                            emit!(scroll((delta as f32 * scale.height) as c_int))\n                        }\n                        KeyPress { key } => {\n                            if dispatch(renderer.keypress(&key).unwrap()) {\n                                emit!(key_press(key.char as c_char))\n                            }\n                        }\n                        MouseUp { col, row } => {\n                            if dispatch(renderer.mouse_up((col as _, row as _).into()).unwrap()) {\n                                let (width, height) = scale(col, row);\n\n                                emit!(mouse_up(width, height))\n                            }\n                        }\n                        MouseDown { col, row } => {\n                            if dispatch(renderer.mouse_down((col as _, row as _).into()).unwrap()) {\n                                let (width, height) = scale(col, row);\n\n                                emit!(mouse_down(width, height))\n                            }\n                        }\n                        MouseMove { col, row } => {\n                            if dispatch(renderer.mouse_move((col as _, row as _).into()).unwrap()) {\n                                let (width, height) = scale(col, row);\n\n                                emit!(mouse_move(width, height))\n                            }\n                        }\n                        Terminal(terminal) => match terminal {\n                            TerminalEvent::Name(name) => log::debug!(\"terminal name: {name}\"),\n                            TerminalEvent::TrueColorSupported => renderer.enable_true_color(),\n                        },\n                    }\n                }\n            })\n        })\n        .unwrap();\n\n        // Setup single-use channel\n        let (tx, rx) = mpsc::channel();\n\n        // Signal the browser to shutdown and notify our thread\n        emit!(shutdown() => tx.send(()).unwrap());\n        rx.recv().unwrap();\n\n        // Shutdown rendering thread\n        // if let Some(handle) = { bridge.lock().unwrap().renderer().stop() } {\n        //     handle.join().unwrap()\n        // }\n    });\n}\n"
  },
  {
    "path": "src/browser/carbonyl.mojom",
    "content": "// Our C++ bindings will be in the carbonyl::mojom namespace\nmodule carbonyl.mojom;\n\n// Import existing bindings to common structures\nimport \"ui/gfx/geometry/mojom/geometry.mojom\";\nimport \"skia/public/mojom/skcolor.mojom\";\n\n// Define a structure to hold text to render\nstruct TextData {\n    // An UTF-8 string with the contents\n    string contents;\n    // Bounds, size only defined for clearing\n    gfx.mojom.RectF bounds;\n    // Color of the text\n    skia.mojom.SkColor color;\n};\n\n// The browser process runs this service\ninterface CarbonylRenderService {\n    // The renderer process calls this method\n    DrawText(array<TextData> data);\n};\n"
  },
  {
    "path": "src/browser/export.h",
    "content": "#ifndef CARBONYL_SRC_BROWSER_BRIDGE_EXPORT_H_\n#define CARBONYL_SRC_BROWSER_BRIDGE_EXPORT_H_\n\n// CARBONYL_BRIDGE_EXPORT\n#if defined(COMPONENT_BUILD)\n\n#if defined(WIN32)\n\n#if defined(CARBONYL_BRIDGE_IMPLEMENTATION)\n#define CARBONYL_BRIDGE_EXPORT __declspec(dllexport)\n#else\n#define CARBONYL_BRIDGE_EXPORT __declspec(dllimport)\n#endif\n\n#else  // !defined(WIN32)\n\n#if defined(CARBONYL_BRIDGE_IMPLEMENTATION)\n#define CARBONYL_BRIDGE_EXPORT __attribute__((visibility(\"default\")))\n#else\n#define CARBONYL_BRIDGE_EXPORT\n#endif\n\n#endif\n\n#else  // !defined(COMPONENT_BUILD)\n\n#define CARBONYL_BRIDGE_EXPORT\n\n#endif\n\n// CARBONYL_RENDERER_EXPORT\n#if defined(COMPONENT_BUILD)\n\n#if defined(WIN32)\n\n#if defined(CARBONYL_RENDERER_IMPLEMENTATION)\n#define CARBONYL_RENDERER_EXPORT __declspec(dllexport)\n#else\n#define CARBONYL_RENDERER_EXPORT __declspec(dllimport)\n#endif\n\n#else  // !defined(WIN32)\n\n#if defined(CARBONYL_RENDERER_IMPLEMENTATION)\n#define CARBONYL_RENDERER_EXPORT __attribute__((visibility(\"default\")))\n#else\n#define CARBONYL_RENDERER_EXPORT\n#endif\n\n#endif\n\n#else  // !defined(COMPONENT_BUILD)\n\n#define CARBONYL_RENDERER_EXPORT\n\n#endif\n\n// CARBONYL_VIZ_EXPORT\n#if defined(COMPONENT_BUILD)\n\n#if defined(WIN32)\n\n#if defined(CARBONYL_VIZ_IMPLEMENTATION)\n#define CARBONYL_VIZ_EXPORT __declspec(dllexport)\n#else\n#define CARBONYL_VIZ_EXPORT __declspec(dllimport)\n#endif\n\n#else  // !defined(WIN32)\n\n#if defined(CARBONYL_VIZ_IMPLEMENTATION)\n#define CARBONYL_VIZ_EXPORT __attribute__((visibility(\"default\")))\n#else\n#define CARBONYL_VIZ_EXPORT\n#endif\n\n#endif\n\n#else  // !defined(COMPONENT_BUILD)\n\n#define CARBONYL_VIZ_EXPORT\n\n#endif\n\n#endif  // CARBONYL_SRC_BROWSER_BRIDGE_EXPORT_H_\n"
  },
  {
    "path": "src/browser/host_display_client.cc",
    "content": "#include \"carbonyl/src/browser/host_display_client.h\"\n\n#include <utility>\n\n#include \"components/viz/common/resources/resource_format.h\"\n#include \"components/viz/common/resources/resource_sizes.h\"\n#include \"mojo/public/cpp/system/platform_handle.h\"\n#include \"skia/ext/platform_canvas.h\"\n#include \"third_party/skia/include/core/SkColor.h\"\n#include \"third_party/skia/include/core/SkRect.h\"\n#include \"third_party/skia/src/core/SkDevice.h\"\n#include \"ui/gfx/skia_util.h\"\n\n#if BUILDFLAG(IS_WIN)\n#include \"skia/ext/skia_utils_win.h\"\n#endif\n\n#include \"carbonyl/src/browser/renderer.h\"\n\nnamespace carbonyl {\n\nLayeredWindowUpdater::LayeredWindowUpdater(\n    mojo::PendingReceiver<viz::mojom::LayeredWindowUpdater> receiver\n)\n  :\n    receiver_(this, std::move(receiver)),\n    task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault())\n  {}\n\nLayeredWindowUpdater::~LayeredWindowUpdater() = default;\n\nvoid LayeredWindowUpdater::OnAllocatedSharedMemory(\n    const gfx::Size& pixel_size,\n    base::UnsafeSharedMemoryRegion region) {\n  if (region.IsValid())\n    shm_mapping_ = region.Map();\n\n  pixel_size_ = pixel_size;\n}\n\nvoid LayeredWindowUpdater::Draw(const gfx::Rect& damage_rect,\n                                DrawCallback callback) {\n  Renderer::GetCurrent()->DrawBitmap(\n    shm_mapping_.GetMemoryAs<uint8_t>(),\n    pixel_size_,\n    damage_rect,\n    base::BindOnce(\n      [](\n        scoped_refptr<base::SingleThreadTaskRunner> task_runner,\n        DrawCallback callback\n      ) {\n        task_runner->PostTask(FROM_HERE, std::move(callback));\n      },\n      task_runner_,\n      std::move(callback)\n    )\n  );\n}\n\nHostDisplayClient::HostDisplayClient()\n    : viz::HostDisplayClient(gfx::kNullAcceleratedWidget) {}\nHostDisplayClient::~HostDisplayClient() = default;\n\nvoid HostDisplayClient::CreateLayeredWindowUpdater(\n    mojo::PendingReceiver<viz::mojom::LayeredWindowUpdater> receiver) {\n  layered_window_updater_ =\n      std::make_unique<LayeredWindowUpdater>(std::move(receiver));\n}\n\n#if BUILDFLAG(IS_MAC)\nvoid HostDisplayClient::OnDisplayReceivedCALayerParams(\n    const gfx::CALayerParams& ca_layer_params) {}\n#endif\n\n#if BUILDFLAG(IS_LINUX) && !BUILDFLAG(IS_CHROMEOS)\nvoid HostDisplayClient::DidCompleteSwapWithNewSize(\n    const gfx::Size& size) {}\n#endif\n\n}  // namespace carbonyl\n"
  },
  {
    "path": "src/browser/host_display_client.h",
    "content": "#ifndef CARBONYL_SRC_BROWSER_HOST_DISPLAY_CLIENT_H_\n#define CARBONYL_SRC_BROWSER_HOST_DISPLAY_CLIENT_H_\n\n#include <memory>\n\n#include \"base/callback.h\"\n#include \"base/memory/shared_memory_mapping.h\"\n#include \"carbonyl/src/browser/export.h\"\n#include \"components/viz/host/host_display_client.h\"\n#include \"services/viz/privileged/mojom/compositing/layered_window_updater.mojom.h\"\n#include \"ui/gfx/native_widget_types.h\"\n\nnamespace carbonyl {\n\ntypedef base::RepeatingCallback<void(const gfx::Rect&, const SkBitmap&)>\n    OnPaintCallback;\n\nclass CARBONYL_VIZ_EXPORT LayeredWindowUpdater : public viz::mojom::LayeredWindowUpdater {\n public:\n  explicit LayeredWindowUpdater(\n      mojo::PendingReceiver<viz::mojom::LayeredWindowUpdater> receiver);\n  ~LayeredWindowUpdater() override;\n\n  // disable copy\n  LayeredWindowUpdater(const LayeredWindowUpdater&) = delete;\n  LayeredWindowUpdater& operator=(const LayeredWindowUpdater&) = delete;\n\n  // viz::mojom::LayeredWindowUpdater implementation.\n  void OnAllocatedSharedMemory(const gfx::Size& pixel_size,\n                               base::UnsafeSharedMemoryRegion region) override;\n  void Draw(const gfx::Rect& damage_rect, DrawCallback draw_callback) override;\n\n private:\n  mojo::Receiver<viz::mojom::LayeredWindowUpdater> receiver_;\n  base::WritableSharedMemoryMapping shm_mapping_;\n  gfx::Size pixel_size_;\n  DrawCallback callback_;\n  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;\n  base::WeakPtrFactory<LayeredWindowUpdater> weak_ptr_factory_ { this };\n};\n\nclass CARBONYL_VIZ_EXPORT HostDisplayClient : public viz::HostDisplayClient {\n public:\n  explicit HostDisplayClient();\n  ~HostDisplayClient() override;\n\n  // disable copy\n  HostDisplayClient(const HostDisplayClient&) = delete;\n  HostDisplayClient& operator=(const HostDisplayClient&) =\n      delete;\n\n private:\n#if BUILDFLAG(IS_MAC)\n  void OnDisplayReceivedCALayerParams(\n      const gfx::CALayerParams& ca_layer_params) override;\n#endif\n\n  void CreateLayeredWindowUpdater(\n      mojo::PendingReceiver<viz::mojom::LayeredWindowUpdater> receiver)\n      override;\n\n#if BUILDFLAG(IS_LINUX) && !BUILDFLAG(IS_CHROMEOS)\n  void DidCompleteSwapWithNewSize(const gfx::Size& size) override;\n#endif\n\n  std::unique_ptr<LayeredWindowUpdater> layered_window_updater_;\n  OnPaintCallback callback_;\n};\n\n}  // namespace carbonyl\n\n#endif  // CARBONYL_SRC_BROWSER_HOST_DISPLAY_CLIENT_H_\n"
  },
  {
    "path": "src/browser/render_service_impl.cc",
    "content": "#include \"carbonyl/src/browser/render_service_impl.h\"\n\n#include <iostream>\n\n#include \"carbonyl/src/browser/renderer.h\"\n\nnamespace carbonyl {\n\nCarbonylRenderServiceImpl::CarbonylRenderServiceImpl(\n    mojo::PendingReceiver<mojom::CarbonylRenderService> receiver):\n    receiver_(this, std::move(receiver))\n{}\n\nCarbonylRenderServiceImpl::~CarbonylRenderServiceImpl() = default;\n\nvoid CarbonylRenderServiceImpl::DrawText(std::vector<mojom::TextDataPtr> data) {\n    std::vector<Text> mapped;\n\n    for (auto& text: data) {\n        mapped.emplace_back(text->contents, text->bounds, text->color);\n    }\n\n    Renderer::GetCurrent()->DrawText(mapped);\n}\n\n}\n"
  },
  {
    "path": "src/browser/render_service_impl.h",
    "content": "#ifndef CARBONYL_SRC_BROWSER_RENDER_SERVICE_IMPL_H_\n#define CARBONYL_SRC_BROWSER_RENDER_SERVICE_IMPL_H_\n\n#include \"carbonyl/src/browser/export.h\"\n#include \"carbonyl/src/browser/carbonyl.mojom.h\"\n#include \"mojo/public/cpp/bindings/pending_receiver.h\"\n#include \"mojo/public/cpp/bindings/receiver.h\"\n\nnamespace carbonyl {\n\nclass CARBONYL_RENDERER_EXPORT CarbonylRenderServiceImpl: public mojom::CarbonylRenderService {\n public:\n  explicit CarbonylRenderServiceImpl(mojo::PendingReceiver<mojom::CarbonylRenderService> receiver);\n  CarbonylRenderServiceImpl(const CarbonylRenderServiceImpl&) = delete;\n  CarbonylRenderServiceImpl& operator=(const CarbonylRenderServiceImpl&) = delete;\n\n  ~CarbonylRenderServiceImpl() override;\n\n  // carbonyl::mojom::CarbonylRenderService:\n  void DrawText(std::vector<mojom::TextDataPtr> data) override;\n\n private:\n  mojo::Receiver<mojom::CarbonylRenderService> receiver_;\n};\n\n}  // namespace carbonyl\n\n#endif  // CARBONYL_SRC_BROWSER_RENDER_SERVICE_IMPL_H_"
  },
  {
    "path": "src/browser/renderer.cc",
    "content": "#include \"carbonyl/src/browser/renderer.h\"\n\n#include <memory>\n#include <iostream>\n#include <stdio.h>\n\n#include \"base/functional/callback.h\"\n#include \"carbonyl/src/browser/bridge.h\"\n#include \"ui/gfx/geometry/rect_f.h\"\n#include \"third_party/skia/include/core/SkColor.h\"\n\nextern \"C\" {\n\nstruct carbonyl_renderer_size {\n    unsigned int width;\n    unsigned int height;\n};\nstruct carbonyl_renderer_point {\n    unsigned int x;\n    unsigned int y;\n};\nstruct carbonyl_renderer_rect {\n    struct carbonyl_renderer_point origin;\n    struct carbonyl_renderer_size size;\n};\nstruct carbonyl_renderer_color {\n    uint8_t r;\n    uint8_t g;\n    uint8_t b;\n};\nstruct carbonyl_renderer_text {\n    const char* text;\n    carbonyl_renderer_rect rect;\n    carbonyl_renderer_color color;\n};\n\nvoid carbonyl_bridge_main();\nbool carbonyl_bridge_bitmap_mode();\nfloat carbonyl_bridge_get_dpi();\n\nstruct carbonyl_renderer* carbonyl_renderer_create();\nvoid carbonyl_renderer_start(struct carbonyl_renderer* renderer);\nvoid carbonyl_renderer_resize(struct carbonyl_renderer* renderer);\nstruct carbonyl_renderer_size carbonyl_renderer_get_size(struct carbonyl_renderer* renderer);\nvoid carbonyl_renderer_push_nav(struct carbonyl_renderer* renderer, const char* url, bool can_go_back, bool can_go_forward);\nvoid carbonyl_renderer_set_title(struct carbonyl_renderer* renderer, const char* title);\nvoid carbonyl_renderer_clear_text(struct carbonyl_renderer* renderer);\nvoid carbonyl_renderer_listen(struct carbonyl_renderer* renderer, const struct carbonyl_renderer_browser_delegate* delegate);\nvoid carbonyl_renderer_draw_text(\n    struct carbonyl_renderer* renderer,\n    const struct carbonyl_renderer_text* text,\n    size_t text_size\n);\nvoid carbonyl_renderer_draw_bitmap(\n    struct carbonyl_renderer* renderer,\n    const unsigned char* pixels,\n    const struct carbonyl_renderer_size size,\n    const struct carbonyl_renderer_rect rect,\n    void (*callback) (void*),\n    void* callback_data\n);\n\n}\n\nnamespace carbonyl {\n\nnamespace {\n    static std::unique_ptr<Renderer> globalInstance;\n}\n\nRenderer::Renderer(struct carbonyl_renderer* ptr): ptr_(ptr) {}\n\nvoid Renderer::Main() {\n    carbonyl_bridge_main();\n\n    Bridge::Configure(\n        carbonyl_bridge_get_dpi(),\n        carbonyl_bridge_bitmap_mode()\n    );\n}\n\nRenderer* Renderer::GetCurrent() {\n    if (!globalInstance) {\n        globalInstance = std::unique_ptr<Renderer>(\n            new Renderer(carbonyl_renderer_create())\n        );\n    }\n\n    return globalInstance.get();\n}\n\nvoid Renderer::StartRenderer() {\n    carbonyl_renderer_start(ptr_);\n}\n\ngfx::Size Renderer::GetSize() {\n    auto size = carbonyl_renderer_get_size(ptr_);\n\n    return gfx::Size(size.width, size.height);\n}\n\ngfx::Size Renderer::Resize() {\n    carbonyl_renderer_resize(ptr_);\n    Bridge::Resize();\n\n    return GetSize();\n}\n\nvoid Renderer::Listen(const struct carbonyl_renderer_browser_delegate* delegate) {\n    carbonyl_renderer_listen(ptr_, delegate);\n}\n\nvoid Renderer::PushNav(const std::string& url, bool can_go_back, bool can_go_forward) {\n    if (!url.size()) {\n        return;\n    }\n\n    carbonyl_renderer_push_nav(ptr_, url.c_str(), can_go_back, can_go_forward);\n}\n\nvoid Renderer::SetTitle(const std::string& title) {\n    if (!title.size()) {\n        return;\n    }\n\n    carbonyl_renderer_set_title(ptr_, title.c_str());\n}\n\nvoid Renderer::DrawText(const std::vector<Text>& text) {\n    struct carbonyl_renderer_text data[text.size()];\n\n    for (size_t i = 0; i < text.size(); i++) {\n        data[i].text = text[i].text.c_str();\n        data[i].color.r = SkColorGetR(text[i].color);\n        data[i].color.g = SkColorGetG(text[i].color);\n        data[i].color.b = SkColorGetB(text[i].color);\n        data[i].rect.origin.x = text[i].rect.x();\n        data[i].rect.origin.y = text[i].rect.y();\n        data[i].rect.size.width = std::ceil(text[i].rect.width());\n        data[i].rect.size.height = std::ceil(text[i].rect.height());\n    }\n\n    carbonyl_renderer_draw_text(ptr_, data, text.size());\n}\n\nvoid Renderer::DrawBitmap(\n    const unsigned char* pixels,\n    const gfx::Size& pixels_size,\n    const gfx::Rect& damage,\n    base::OnceCallback<void()> callback\n) {\n    auto* box = new base::OnceCallback<void()>(std::move(callback));\n\n    carbonyl_renderer_draw_bitmap(\n        ptr_,\n        pixels,\n        {\n            .width = (unsigned int)pixels_size.width(),\n            .height = (unsigned int)pixels_size.height(),\n        },\n        {\n            .origin = {\n                .x = (unsigned int)damage.x(),\n                .y = (unsigned int)damage.y(),\n            },\n            .size = {\n                .width = (unsigned int)damage.width(),\n                .height = (unsigned int)damage.height(),\n            },\n        },\n        [](void* box) {\n            auto* ptr = static_cast<base::OnceCallback<void()>*>(box);\n\n            std::move(*ptr).Run();\n            delete ptr;\n        },\n        box\n    );\n}\n\n}\n"
  },
  {
    "path": "src/browser/renderer.h",
    "content": "#ifndef CARBONYL_SRC_BROWSER_RENDERER_H_\n#define CARBONYL_SRC_BROWSER_RENDERER_H_\n\n#include <cstdint>\n#include <functional>\n\n#include \"base/functional/callback.h\"\n#include \"carbonyl/src/browser/export.h\"\n#include \"ui/gfx/geometry/rect_f.h\"\n\nextern \"C\" {\n\nstruct carbonyl_renderer;\nstruct carbonyl_renderer_browser_delegate {\n    void (*shutdown) ();\n    void (*refresh) ();\n    void (*go_to) (const char* url);\n    void (*go_back) ();\n    void (*go_forward) ();\n    void (*scroll) (int);\n    void (*key_press) (char);\n    void (*mouse_up) (unsigned int, unsigned int);\n    void (*mouse_down) (unsigned int, unsigned int);\n    void (*mouse_move) (unsigned int, unsigned int);\n    void (*post_task) (void (*)(void*), void*);\n};\n\n} /* end extern \"C\" */\n\nnamespace carbonyl {\n\nstruct CARBONYL_RENDERER_EXPORT Text {\n    Text(\n        std::string text,\n        gfx::RectF rect,\n        uint32_t color\n    ):\n        text(text),\n        rect(rect),\n        color(color)\n    {}\n\n    std::string text;\n    gfx::RectF rect;\n    uint32_t color;\n};\n\nclass CARBONYL_RENDERER_EXPORT Renderer {\npublic:\n    static void Main();\n    static Renderer* GetCurrent();\n\n    gfx::Size GetSize();\n\n    gfx::Size Resize();\n    void StartRenderer();\n    void Listen(const struct carbonyl_renderer_browser_delegate* delegate);\n    void PushNav(const std::string& url, bool can_go_back, bool can_go_forward);\n    void SetTitle(const std::string& title);\n    void DrawText(const std::vector<Text>& text);\n    void DrawBitmap(\n        const unsigned char* pixels,\n        const gfx::Size& size,\n        const gfx::Rect& damage,\n        base::OnceCallback<void()> callback\n    );\n\nprivate:\n    Renderer(struct carbonyl_renderer* ptr);\n\n    struct carbonyl_renderer* ptr_;\n};\n\n}\n\n#endif  // CARBONYL_SRC_BROWSER_RENDERER_H_\n"
  },
  {
    "path": "src/browser.rs",
    "content": "mod bridge;\n\npub use bridge::*;\n"
  },
  {
    "path": "src/cli/cli.rs",
    "content": "use std::{env, ffi::OsStr};\n\nuse super::CommandLineProgram;\n\n#[derive(Clone, Debug)]\npub struct CommandLine {\n    pub args: Vec<String>,\n    pub fps: f32,\n    pub zoom: f32,\n    pub debug: bool,\n    pub bitmap: bool,\n    pub program: CommandLineProgram,\n    pub shell_mode: bool,\n}\n\npub enum EnvVar {\n    Debug,\n    Bitmap,\n    ShellMode,\n}\n\nimpl EnvVar {\n    pub fn as_str(&self) -> &'static str {\n        match self {\n            EnvVar::Debug => \"CARBONYL_ENV_DEBUG\",\n            EnvVar::Bitmap => \"CARBONYL_ENV_BITMAP\",\n            EnvVar::ShellMode => \"CARBONYL_ENV_SHELL_MODE\",\n        }\n    }\n}\n\nimpl AsRef<OsStr> for EnvVar {\n    fn as_ref(&self) -> &OsStr {\n        self.as_str().as_ref()\n    }\n}\n\nimpl CommandLine {\n    pub fn parse() -> CommandLine {\n        let mut fps = 60.0;\n        let mut zoom = 1.0;\n        let mut debug = false;\n        let mut bitmap = false;\n        let mut shell_mode = false;\n        let mut program = CommandLineProgram::Main;\n        let args = env::args().skip(1).collect::<Vec<String>>();\n\n        for arg in &args {\n            let split: Vec<&str> = arg.split(\"=\").collect();\n            let default = arg.as_str();\n            let (key, value) = (split.get(0).unwrap_or(&default), split.get(1));\n\n            macro_rules! set {\n                ($var:ident, $enum:ident) => {{\n                    $var = true;\n\n                    env::set_var(EnvVar::$enum, \"1\");\n                }};\n            }\n\n            macro_rules! set_f32 {\n                ($var:ident = $expr:expr) => {{\n                    if let Some(value) = value {\n                        if let Some(value) = value.parse::<f32>().ok() {\n                            $var = {\n                                let $var = value;\n\n                                $expr\n                            };\n                        }\n                    }\n                }};\n            }\n\n            match *key {\n                \"-f\" | \"--fps\" => set_f32!(fps = fps),\n                \"-z\" | \"--zoom\" => set_f32!(zoom = zoom / 100.0),\n                \"-d\" | \"--debug\" => set!(debug, Debug),\n                \"-b\" | \"--bitmap\" => set!(bitmap, Bitmap),\n\n                \"-h\" | \"--help\" => program = CommandLineProgram::Help,\n                \"-v\" | \"--version\" => program = CommandLineProgram::Version,\n                _ => (),\n            }\n        }\n\n        if env::var(EnvVar::Debug).is_ok() {\n            debug = true;\n        }\n\n        if env::var(EnvVar::Bitmap).is_ok() {\n            bitmap = true;\n        }\n\n        if env::var(EnvVar::ShellMode).is_ok() {\n            shell_mode = true;\n        }\n\n        CommandLine {\n            args,\n            fps,\n            zoom,\n            debug,\n            bitmap,\n            program,\n            shell_mode,\n        }\n    }\n}\n"
  },
  {
    "path": "src/cli/program.rs",
    "content": "use super::CommandLine;\n\n#[derive(Clone, Debug)]\npub enum CommandLineProgram {\n    Main,\n    Help,\n    Version,\n}\n\nimpl CommandLineProgram {\n    pub fn parse_or_run() -> Option<CommandLine> {\n        let cmd = CommandLine::parse();\n\n        match cmd.program {\n            CommandLineProgram::Main => return Some(cmd),\n            CommandLineProgram::Help => {\n                println!(\"{}\", include_str!(\"usage.txt\"))\n            }\n            CommandLineProgram::Version => {\n                println!(\"Carbonyl {}\", env!(\"CARGO_PKG_VERSION\"))\n            }\n        }\n\n        None\n    }\n}\n"
  },
  {
    "path": "src/cli/usage.txt",
    "content": "   O    O      Carbonyl is a Chromium based browser for the terminal.\n    \\  /       \nO —— Cr —— O   In addition to the following options,\n    /  \\       Carbonyl also supports most Chromium options.\n   O    O      \n\nUsage: carbonyl [options] [url]\n\nOptions:\n    -f, --fps=<fps>            set the maximum number of frames per second (default: 60)\n    -z, --zoom=<zoom>          set the zoom level in percent (default: 100)\n    -b, --bitmap               render text as bitmaps\n    -d, --debug                enable debug logs\n    -h, --help                 display this help message\n    -v, --version              output the version number\n"
  },
  {
    "path": "src/cli.rs",
    "content": "mod cli;\nmod program;\n\npub use cli::*;\npub use program::*;\n"
  },
  {
    "path": "src/gfx/color.rs",
    "content": "use super::Vector3;\nuse crate::impl_vector_overload;\n\n#[derive(Clone, Copy, Debug, PartialEq)]\npub struct Color<T: Copy = u8> {\n    pub r: T,\n    pub g: T,\n    pub b: T,\n}\n\nimpl Color {\n    pub fn from_iter<'a, T>(iter: &mut T) -> Option<Color>\n    where\n        T: Iterator<Item = &'a u8>,\n    {\n        let (b, g, r, _) = (iter.next(), iter.next(), iter.next(), iter.next());\n\n        Some(Color::<u8>::new(*r?, *g?, *b?))\n    }\n\n    pub fn black() -> Color {\n        Color::<u8>::new(0, 0, 0)\n    }\n}\n\nimpl_vector_overload!(Color r g b);\n"
  },
  {
    "path": "src/gfx/point.rs",
    "content": "use super::{Rect, Vector2};\nuse crate::impl_vector_overload;\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub struct Point<T: Copy = i32> {\n    pub x: T,\n    pub y: T,\n}\n\nimpl Point {\n    pub fn inside(&self, rect: Rect) -> bool {\n        self.x >= rect.origin.x\n            && self.y >= rect.origin.y\n            && self.x < rect.origin.x + rect.size.width as i32\n            && self.y < rect.origin.y + rect.size.height as i32\n    }\n}\n\nimpl_vector_overload!(Point x y);\n"
  },
  {
    "path": "src/gfx/rect.rs",
    "content": "use super::{Point, Size};\n\n#[derive(Clone, Copy, Debug, PartialEq)]\npub struct Rect<P: Copy = i32, S: Copy = u32> {\n    pub origin: Point<P>,\n    pub size: Size<S>,\n}\n\nimpl<P: Copy, S: Copy> Rect<P, S> {\n    pub fn new(x: P, y: P, width: S, height: S) -> Self {\n        Self {\n            origin: Point::new(x, y),\n            size: Size::new(width, height),\n        }\n    }\n}\n"
  },
  {
    "path": "src/gfx/size.rs",
    "content": "use super::Vector2;\nuse crate::impl_vector_overload;\n\n#[derive(Clone, Copy, Debug, PartialEq, Default)]\npub struct Size<T: Copy = u32> {\n    pub width: T,\n    pub height: T,\n}\n\nimpl_vector_overload!(Size width height);\n"
  },
  {
    "path": "src/gfx/vector.rs",
    "content": "pub trait Vector2<T>\nwhere\n    T: Copy,\n{\n    fn x(&self) -> T;\n    fn y(&self) -> T;\n}\n\npub trait Vector3<T>\nwhere\n    T: Copy,\n{\n    fn x(&self) -> T;\n    fn y(&self) -> T;\n    fn z(&self) -> T;\n}\npub trait Cast<T> {\n    fn cast(self) -> T;\n}\n\npub trait ToIntUnchecked<T> {\n    unsafe fn to_int_unchecked(self) -> T;\n}\n\nmacro_rules! impl_cast_trait {\n    () => {\n        impl_cast_trait!(u8 as int);\n        impl_cast_trait!(i8 as int);\n        impl_cast_trait!(u16 as int);\n        impl_cast_trait!(i16 as int);\n        impl_cast_trait!(u32 as int);\n        impl_cast_trait!(i32 as int);\n        impl_cast_trait!(u64 as int);\n        impl_cast_trait!(i64 as int);\n        impl_cast_trait!(f32 as float);\n        impl_cast_trait!(f64 as float);\n        impl_cast_trait!(usize as int);\n        impl_cast_trait!(isize as int);\n    };\n    ($from:ty as int) => {\n        impl_cast_trait!($from);\n\n        impl ToIntUnchecked<$from> for f32 {\n            unsafe fn to_int_unchecked(self) -> $from {\n                f32::to_int_unchecked(self)\n            }\n        }\n        impl ToIntUnchecked<$from> for f64 {\n            unsafe fn to_int_unchecked(self) -> $from {\n                f64::to_int_unchecked(self)\n            }\n        }\n    };\n    ($from:ty as float) => {\n        impl_cast_trait!($from);\n    };\n    ($from:ty) => {\n        impl_cast_trait!($from => u8);\n        impl_cast_trait!($from => i8);\n        impl_cast_trait!($from => u16);\n        impl_cast_trait!($from => i16);\n        impl_cast_trait!($from => u32);\n        impl_cast_trait!($from => i32);\n        impl_cast_trait!($from => u64);\n        impl_cast_trait!($from => i64);\n        impl_cast_trait!($from => f32);\n        impl_cast_trait!($from => f64);\n        impl_cast_trait!($from => usize);\n        impl_cast_trait!($from => isize);\n    };\n    ($from:ty => $to:ty) => {\n        impl Cast<$to> for $from {\n            fn cast(self) -> $to {\n                self as $to\n            }\n        }\n    };\n}\n\nimpl_cast_trait!();\n\n#[macro_export]\nmacro_rules! impl_vector_overload {\n    ($struct:ident $x:ident $y:ident) => (\n        impl<T: Copy> $struct<T> {\n            pub const fn new($x: T, $y: T) -> Self {\n                Self { $x, $y }\n            }\n\n            pub const fn splat(value: T) -> Self {\n                Self::new(value, value)\n            }\n\n            pub const fn to_array(&self) -> [T; 2] {\n                [self.$x, self.$y]\n            }\n\n            pub fn iter(&self) -> std::array::IntoIter<T, 2> {\n                self.to_array().into_iter()\n            }\n\n            pub fn reduce<F>(&self, mut f: F) -> T\n            where\n                F: FnMut(T, T) -> T\n            {\n                f(self.$x, self.$y)\n            }\n        }\n\n        impl<T: Copy> Vector2<T> for $struct<T> {\n            fn x(&self) -> T {\n                self.$x\n            }\n            fn y(&self) -> T {\n                self.$y\n            }\n        }\n\n        impl<T: Copy> std::iter::FromIterator<T> for $struct<T> {\n            fn from_iter<I>(iter: I) -> Self\n            where\n                I: IntoIterator<Item = T>\n            {\n                let mut iter = iter.into_iter();\n                let expect = \"initialized a vector with a small iter\";\n\n                Self::new(\n                    iter.next().expect(expect),\n                    iter.next().expect(expect)\n                )\n            }\n        }\n\n        impl<T: Copy> From<T> for $struct<T> {\n            fn from(value: T) -> Self {\n                Self::new(value, value)\n            }\n        }\n\n        impl<T: Copy> From<$struct<T>> for (T, T) {\n            fn from(vector: $struct<T>) -> Self {\n                (vector.$x, vector.$y)\n            }\n        }\n\n        impl<T: Copy> From<(T, T)> for $struct<T> {\n            fn from((x, y): (T, T)) -> Self {\n                Self::new(x, y)\n            }\n        }\n\n        impl<T: Copy> From<[T; 2]> for $struct<T> {\n            fn from(array: [T; 2]) -> Self {\n                Self::new(array[0], array[1])\n            }\n        }\n\n        crate::impl_vector_traits!($struct Vector2);\n    );\n    ($struct:ident $x:ident $y:ident $z:ident) => (\n        impl<T: Copy> $struct<T> {\n            pub const fn new($x: T, $y: T, $z: T) -> Self {\n                $struct { $x, $y, $z }\n            }\n\n            pub const fn splat(value: T) -> Self {\n                Self::new(value, value, value)\n            }\n\n            pub const fn to_array(&self) -> [T; 3] {\n                [self.$x, self.$y, self.$z]\n            }\n\n            pub fn iter(&self) -> std::array::IntoIter<T, 3> {\n                self.to_array().into_iter()\n            }\n\n            pub fn reduce<F>(&self, mut f: F) -> T\n            where\n                F: FnMut(T, T) -> T\n            {\n                let a = f(self.$x, self.$y);\n\n                f(a, self.$z)\n            }\n        }\n\n        impl<T: Copy> Vector3<T> for $struct<T> {\n            fn x(&self) -> T {\n                self.$x\n            }\n            fn y(&self) -> T {\n                self.$y\n            }\n            fn z(&self) -> T {\n                self.$z\n            }\n        }\n\n        impl<T: Copy> std::iter::FromIterator<T> for $struct<T> {\n            fn from_iter<I>(iter: I) -> Self\n            where\n                I: IntoIterator<Item = T>\n            {\n                let mut iter = iter.into_iter();\n                let expect = \"initialized a vector with a small iter\";\n\n                Self::new(\n                    iter.next().expect(expect),\n                    iter.next().expect(expect),\n                    iter.next().expect(expect)\n                )\n            }\n        }\n\n        impl<T: Copy> From<T> for $struct<T> {\n            fn from(value: T) -> Self {\n                Self::new(value, value, value)\n            }\n        }\n\n        impl<T: Copy> From<(T, T, T)> for $struct<T> {\n            fn from((x, y, z): (T, T, T)) -> Self {\n                Self::new(x, y, z)\n            }\n        }\n\n        impl<T: Copy> From<$struct<T>> for (T, T, T) {\n            fn from(vector: $struct<T>) -> Self {\n                (vector.$x, vector.$y, vector.$z)\n            }\n        }\n\n        impl<T: Copy> From<[T; 3]> for $struct<T> {\n            fn from(array: [T; 3]) -> Self {\n                Self::new(array[0], array[1], array[2])\n            }\n        }\n\n        crate::impl_vector_traits!($struct Vector3);\n    );\n}\n\n#[macro_export]\nmacro_rules! impl_vector_traits {\n    ($struct:ident $vector:ident) => {\n        impl<T: Copy> $struct<T> {\n            pub fn dot<U>(&self, rhs: U) -> T\n            where\n                U: Into<$struct<T>>,\n                T: std::ops::Mul<T, Output = T> + std::iter::Sum,\n            {\n                (self * rhs.into()).sum()\n            }\n\n            pub fn sum(&self) -> T\n            where\n                T: std::iter::Sum,\n            {\n                self.iter().sum::<T>()\n            }\n\n            pub fn cast<U>(&self) -> $struct<U>\n            where\n                T: super::Cast<U>,\n                U: Copy\n            {\n                self.map(|v| v.cast())\n            }\n\n            pub fn map<U, F>(&self, f: F) -> $struct<U>\n            where\n                U: Copy,\n                F: FnMut(T) -> U\n            {\n                self.iter().map(f).collect()\n            }\n\n            pub fn min_val(&self) -> T\n            where\n                T: Default + Ord\n            {\n                self.reduce(|a, b| a.min(b))\n            }\n\n            pub fn max_val(&self) -> T\n            where\n                T: Default + Ord\n            {\n                self.reduce(|a, b| a.max(b))\n            }\n        }\n\n        crate::impl_vector_traits!($struct $vector i8);\n        crate::impl_vector_traits!($struct $vector u8);\n        crate::impl_vector_traits!($struct $vector i16);\n        crate::impl_vector_traits!($struct $vector u16);\n        crate::impl_vector_traits!($struct $vector i32);\n        crate::impl_vector_traits!($struct $vector u32);\n        crate::impl_vector_traits!($struct $vector i64);\n        crate::impl_vector_traits!($struct $vector u64);\n        crate::impl_vector_traits!($struct $vector isize);\n        crate::impl_vector_traits!($struct $vector usize);\n        crate::impl_vector_traits!($struct $vector f32 float);\n        crate::impl_vector_traits!($struct $vector f64 float);\n\n        crate::impl_vector_traits!($struct $vector Add add);\n        crate::impl_vector_traits!($struct $vector Sub sub);\n        crate::impl_vector_traits!($struct $vector Mul mul);\n        crate::impl_vector_traits!($struct $vector Div div);\n        crate::impl_vector_traits!($struct $vector BitOr bitor);\n        crate::impl_vector_traits!($struct $vector BitXor bitxor);\n        crate::impl_vector_traits!($struct $vector BitAnd bitand);\n    };\n    ($struct:ident $vector:ident $type:ident) => (\n        impl $struct<$type> {\n            pub fn avg_with<T>(&self, rhs: T) -> Self\n            where\n                T: Into<Self>\n            {\n                let rhs = rhs.into();\n\n                (self & rhs) + (self ^ rhs) / 2\n            }\n        }\n    );\n    ($struct:ident $vector:ident $type:ident float) => (\n        impl $struct<$type> {\n            pub unsafe fn to_int_unchecked<U>(&self) -> $struct<U>\n            where\n                $type: super::ToIntUnchecked<U>,\n                U: Copy\n            {\n                self.map(|x| <$type as super::ToIntUnchecked<U>>::to_int_unchecked(x))\n            }\n\n            pub fn mul_add<M, A>(&self, mul: M, add: A) -> Self\n            where\n                M: Into<Self>,\n                A: Into<Self>,\n            {\n                self.iter()\n                    .zip(mul.into().iter())\n                    .zip(add.into().iter())\n                    .map(|((x, y), z)| x.mul_add(y, z))\n                    .collect()\n            }\n\n            pub fn round(&self) -> Self {\n                self.map(|v| v.round())\n            }\n\n            pub fn floor(&self) -> Self {\n                self.map(|v| v.floor())\n            }\n\n            pub fn ceil(&self) -> Self {\n                self.map(|v| v.ceil())\n            }\n\n            pub fn min<U>(&self, min: U) -> Self\n            where\n                U: Into<Self>\n            {\n                self.iter()\n                    .zip(min.into().iter())\n                    .map(|(x, y)| x.min(y))\n                    .collect()\n            }\n\n            pub fn max<U>(&self, max: U) -> Self\n            where\n                U: Into<Self>\n            {\n                self.iter()\n                    .zip(max.into().iter())\n                    .map(|(x, y)| x.max(y))\n                    .collect()\n            }\n\n            pub fn clamp<U>(&self, min: U, max: U) -> Self\n            where\n                U: Into<Self>\n            {\n                self.iter()\n                    .zip(min.into().iter())\n                    .zip(max.into().iter())\n                    .map(|((x, y), z)| x.clamp(y, z))\n                    .collect()\n            }\n        }\n    );\n    ($struct:ident $vector:ident $trait:ident $name:ident) => {\n        impl<T: Copy> $struct<T> {\n            pub fn $name<U>(&self, rhs: U) -> Self\n            where\n                T: std::ops::$trait<T, Output = T>,\n                U: Copy + Into<Self>\n            {\n                self.iter()\n                    .zip(rhs.into().iter())\n                    .map(|(x, y)| x.$name(y))\n                    .collect()\n            }\n        }\n\n        impl<T, U> std::ops::$trait<U> for $struct<T>\n        where\n            T: Copy + std::ops::$trait<T, Output = T>,\n            U: Copy + Into<$struct<T>>,\n        {\n            type Output = $struct<T>;\n\n            fn $name(self, rhs: U) -> Self::Output {\n                $struct::$name(&self, rhs)\n            }\n        }\n\n        impl<'a, T, U> std::ops::$trait<U> for &'a $struct<T>\n        where\n            T: Copy + std::ops::$trait<T, Output = T>,\n            U: Copy + Into<$struct<T>>,\n        {\n            type Output = $struct<T>;\n\n            fn $name(self, rhs: U) -> Self::Output {\n                $struct::$name(&self, rhs)\n            }\n        }\n    };\n}\n"
  },
  {
    "path": "src/gfx.rs",
    "content": "mod color;\nmod point;\nmod rect;\nmod size;\nmod vector;\n\npub use color::*;\npub use point::*;\npub use rect::*;\npub use size::*;\npub use vector::*;\n"
  },
  {
    "path": "src/input/dcs/control_flow.rs",
    "content": "#[macro_export]\nmacro_rules! control_flow {\n    (break) => {\n        std::ops::ControlFlow::Break(None)\n    };\n    ($expr:expr; break) => {{\n        $expr;\n\n        std::ops::ControlFlow::Break(None)\n    }};\n    (break $expr:expr) => {\n        std::ops::ControlFlow::Break($expr.into())\n    };\n\n    (continue) => {\n        std::ops::ControlFlow::Continue(None)\n    };\n    ($expr:expr; continue) => {{\n        $expr;\n\n        std::ops::ControlFlow::Continue(None)\n    }};\n    (continue $expr:expr) => {\n        std::ops::ControlFlow::Continue($expr.into())\n    };\n}\n"
  },
  {
    "path": "src/input/dcs/parser.rs",
    "content": "use crate::{control_flow, input::ParseControlFlow};\n\nuse super::{resource::*, status::*};\n\n#[derive(Default, Clone)]\nenum Sequence {\n    #[default]\n    Code,\n    Type(u8),\n    Status(StatusParser),\n    Resource(ResourceParser),\n}\n\n#[derive(Default, Clone)]\npub struct DeviceControl {\n    sequence: Sequence,\n}\n\nimpl DeviceControl {\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    pub fn parse(&mut self, key: u8) -> ParseControlFlow {\n        use Sequence::*;\n\n        self.sequence = match self.sequence {\n            Code => match key {\n                b'0' | b'1' => Type(key),\n                _ => control_flow!(break)?,\n            },\n            Type(code) => match key {\n                b'$' => Status(StatusParser::new(code)),\n                b'+' => Resource(ResourceParser::new(code)),\n                _ => control_flow!(break)?,\n            },\n            Status(ref mut status) => return status.parse(key),\n            Resource(ref mut resource) => return resource.parse(key),\n        };\n\n        control_flow!(continue)\n    }\n}\n"
  },
  {
    "path": "src/input/dcs/resource.rs",
    "content": "use crate::{\n    control_flow,\n    input::{Event, ParseControlFlow, TerminalEvent},\n};\n\n#[derive(Copy, Clone)]\nenum Sequence {\n    Start,\n    Name,\n    Value,\n    Terminator,\n}\n\n#[derive(Clone)]\npub struct ResourceParser {\n    code: u8,\n    sequence: Sequence,\n    name: Vec<u8>,\n    value: Vec<u8>,\n}\n\nimpl ResourceParser {\n    pub fn new(code: u8) -> Self {\n        Self {\n            code,\n            sequence: Sequence::Start,\n            name: Vec::new(),\n            value: Vec::new(),\n        }\n    }\n\n    pub fn parse(&mut self, key: u8) -> ParseControlFlow {\n        use Sequence::*;\n\n        self.sequence = match self.sequence {\n            Start => match key {\n                b'r' => Name,\n                _ => control_flow!(break)?,\n            },\n            Name => match key {\n                0x1b => Terminator,\n                b'=' => Value,\n                key => self.push_char(key),\n            },\n            Value => match key {\n                0x1b => Terminator,\n                key => self.push_char(key),\n            },\n            Terminator => control_flow!(break self.parse_event(key))?,\n        };\n\n        control_flow!(continue)\n    }\n\n    fn push_char(&mut self, key: u8) -> Sequence {\n        match self.sequence {\n            Sequence::Name => self.name.push(key),\n            Sequence::Value => self.value.push(key),\n            _ => (),\n        }\n\n        self.sequence\n    }\n\n    fn parse_event(&self, key: u8) -> Option<Event> {\n        if key == b'\\\\' && self.code == b'1' {\n            let name = read_hex_string(self.name.as_slice());\n            let value = read_hex_string(self.value.as_slice());\n\n            if let (Some(name), Some(value)) = (name, value) {\n                if name == \"TN\" {\n                    return Some(Event::Terminal(TerminalEvent::Name(value)));\n                }\n            }\n        }\n\n        None\n    }\n}\n\nfn read_hex_string(str: &[u8]) -> Option<String> {\n    let mut iter = str.into_iter();\n    let mut vec = Vec::with_capacity(str.len() / 2);\n\n    loop {\n        match (iter.next(), iter.next()) {\n            (Some(left), Some(right)) => {\n                let chunk = [*left, *right];\n                let hex = std::str::from_utf8(&chunk).ok()?;\n\n                vec.push(u8::from_str_radix(hex, 16).ok()?)\n            }\n            _ => break,\n        }\n    }\n\n    Some(std::str::from_utf8(&vec).ok()?.to_owned())\n}\n"
  },
  {
    "path": "src/input/dcs/status.rs",
    "content": "use crate::{\n    control_flow,\n    input::{Event, ParseControlFlow, TerminalEvent},\n};\n\n#[derive(Default, Clone)]\nenum Sequence {\n    #[default]\n    Start,\n    Value,\n    Terminator,\n}\n\n#[derive(Default, Clone)]\npub struct StatusParser {\n    code: u8,\n    op: Option<u8>,\n    sequence: Sequence,\n    buffer: Vec<u8>,\n    values: Vec<String>,\n}\n\nimpl StatusParser {\n    pub fn new(code: u8) -> Self {\n        let mut parser = Self::default();\n\n        parser.code = code;\n\n        parser\n    }\n\n    pub fn parse(&mut self, key: u8) -> ParseControlFlow {\n        use Sequence::*;\n\n        self.sequence = match self.sequence {\n            Start => match key {\n                b'r' => Value,\n                _ => control_flow!(break)?,\n            },\n            Value => match key {\n                0x1b => self.terminate(),\n                b';' => self.push_value(),\n                char => self.push_char(char),\n            },\n            Terminator => control_flow!(break self.parse_event(key))?,\n        };\n\n        control_flow!(continue)\n    }\n\n    fn terminate(&mut self) -> Sequence {\n        self.op = self.buffer.pop();\n\n        self.push_value();\n\n        Sequence::Terminator\n    }\n\n    fn push_char(&mut self, key: u8) -> Sequence {\n        self.buffer.push(key);\n\n        Sequence::Value\n    }\n\n    fn push_value(&mut self) -> Sequence {\n        if let Ok(str) = String::from_utf8(std::mem::take(&mut self.buffer)) {\n            self.values.push(str);\n        }\n\n        Sequence::Value\n    }\n\n    fn parse_event(&self, key: u8) -> Option<Event> {\n        if key == b'\\\\' && self.code == b'1' && self.op == Some(b'm') {\n            for value in &self.values {\n                let mut val = 0;\n                let mut set = Vec::new();\n\n                for &char in value.as_bytes() {\n                    match char {\n                        b'0'..=b'9' => val = val * 10 + char - b'0',\n                        b':' => set.push(std::mem::take(&mut val)),\n                        _ => break,\n                    }\n                }\n\n                set.push(val);\n\n                if set.len() > 4 && set[1] == 2 && (set[0] == 38 || set[0] == 48) {\n                    return Some(Event::Terminal(TerminalEvent::TrueColorSupported));\n                }\n            }\n        }\n\n        None\n    }\n}\n"
  },
  {
    "path": "src/input/dcs.rs",
    "content": "mod control_flow;\nmod parser;\nmod resource;\nmod status;\n\npub use parser::*;\n"
  },
  {
    "path": "src/input/keyboard.rs",
    "content": "use crate::control_flow;\n\nuse super::{Event, ParseControlFlow};\n\npub struct Keyboard {\n    state: State,\n}\n\n#[derive(Clone, Debug)]\npub struct Key {\n    pub char: u8,\n    pub modifiers: KeyModifiers,\n}\n\n#[derive(Clone, Debug, Default)]\npub struct KeyModifiers {\n    pub alt: bool,\n    pub meta: bool,\n    pub shift: bool,\n    pub control: bool,\n}\n\nenum State {\n    Separator,\n    Modifier(u8),\n}\n\nimpl Keyboard {\n    pub fn new() -> Self {\n        Self {\n            state: State::Separator,\n        }\n    }\n    pub fn key(key: u8, modifiers: u8) -> Option<Event> {\n        let modifiers = KeyModifiers::parse(modifiers);\n        let char = match key {\n            // Up\n            b'A' => 0x11,\n            // Down\n            b'B' => 0x12,\n            // Right\n            b'C' => 0x13,\n            // Left\n            b'D' => 0x14,\n            _ => return None,\n        };\n\n        Some(Event::KeyPress {\n            key: Key { char, modifiers },\n        })\n    }\n\n    pub fn parse(&mut self, key: u8) -> ParseControlFlow {\n        self.state = match self.state {\n            State::Separator => match key {\n                b';' => State::Modifier(0),\n                _ => control_flow!(break)?,\n            },\n            State::Modifier(code) => match key {\n                b'0'..=b'9' => State::Modifier(code * 10 + key - b'0'),\n                key => control_flow!(break Self::key(key, code))?,\n            },\n        };\n\n        control_flow!(continue)\n    }\n}\n\nimpl From<u8> for Key {\n    fn from(char: u8) -> Self {\n        Self {\n            char,\n            modifiers: KeyModifiers::default(),\n        }\n    }\n}\n\nimpl KeyModifiers {\n    pub fn parse(key: u8) -> Self {\n        let (alt, meta, shift, control) = (0b1000, 0b0100, 0b0010, 0b0001);\n        let mask = match key {\n            2 => shift,\n            3 => alt,\n            4 => shift | alt,\n            5 => control,\n            6 => shift | control,\n            7 => alt | control,\n            8 => shift | alt | control,\n            9 => meta,\n            10 => meta | shift,\n            11 => meta | alt,\n            12 => meta | alt | shift,\n            13 => meta | control,\n            14 => meta | control | shift,\n            15 => meta | control | alt,\n            16 => meta | control | alt | shift,\n            _ => 0,\n        };\n\n        KeyModifiers {\n            alt: alt & mask != 0,\n            meta: meta & mask != 0,\n            shift: shift & mask != 0,\n            control: control & mask != 0,\n        }\n    }\n}\n"
  },
  {
    "path": "src/input/listen.rs",
    "content": "use std::io::{self, Read};\n\nuse crate::input::*;\n\n/// Listen for input events in stdin.\n/// This will block, so it should run from a dedicated thread.\npub fn listen<F>(mut callback: F) -> io::Result<()>\nwhere\n    F: FnMut(Vec<Event>),\n{\n    let mut buf = [0u8; 1024];\n    let mut stdin = io::stdin();\n    let mut parser = Parser::new();\n\n    loop {\n        // Wait for some input\n        let size = stdin.read(&mut buf)?;\n        let read = parser.parse(&buf[0..size]);\n        let mut scroll = 0;\n        let mut events = Vec::with_capacity(read.len());\n\n        for event in read {\n            match event {\n                Event::Exit => return Ok(()),\n                Event::Scroll { delta } => scroll += delta,\n                event => events.push(event),\n            }\n        }\n\n        if scroll != 0 {\n            events.push(Event::Scroll { delta: scroll })\n        }\n\n        callback(events)\n    }\n}\n"
  },
  {
    "path": "src/input/mouse.rs",
    "content": "use std::ops::BitAnd;\n\nuse crate::{control_flow, utils::log};\n\nuse super::{Event, ParseControlFlow};\n\n#[derive(Default, Clone, Debug)]\npub struct Mouse {\n    buf: Vec<u8>,\n    btn: Option<u32>,\n    col: Option<u32>,\n    row: Option<u32>,\n}\n\nimpl Mouse {\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    pub fn parse(&mut self, key: u8) -> ParseControlFlow {\n        match key {\n            b'm' | b'M' => control_flow!(break self.get(key)),\n            b';' => match self.read() {\n                None => control_flow!(break),\n                Some(()) => control_flow!(continue),\n            },\n            key => control_flow!(self.buf.push(key); continue),\n        }\n    }\n\n    fn read(&mut self) -> Option<()> {\n        let buf = std::mem::take(&mut self.buf);\n        let str = std::str::from_utf8(&buf).ok()?;\n        let num = Some(str.parse().ok()?);\n\n        match (self.btn, self.col, self.row) {\n            (None, _, _) => self.btn = num,\n            (_, None, _) => self.col = num,\n            (_, _, None) => self.row = num,\n            _ => {\n                log::warning!(\"Malformed mouse sequence\");\n\n                return None;\n            }\n        }\n\n        return Some(());\n    }\n\n    fn get(&mut self, key: u8) -> Option<Event> {\n        let (btn, col, row) = {\n            self.read()?;\n\n            (self.btn?, self.col?, self.row?)\n        };\n\n        Some({\n            if Mask::ScrollDown & btn {\n                Event::Scroll { delta: -1 }\n            } else if Mask::ScrollUp & btn {\n                Event::Scroll { delta: 1 }\n            } else {\n                let col = col as usize - 1;\n                let row = row as usize - 1;\n\n                if key == b'm' {\n                    Event::MouseUp { row, col }\n                } else if Mask::MouseMove & btn {\n                    Event::MouseMove { row, col }\n                } else {\n                    Event::MouseDown { row, col }\n                }\n            }\n        })\n    }\n}\n\nenum Mask {\n    MouseMove = 0x20,\n    ScrollUp = 0x40,\n    ScrollDown = 0x41,\n}\n\nimpl BitAnd<u32> for Mask {\n    type Output = bool;\n\n    fn bitand(self, rhs: u32) -> bool {\n        let mask = self as u32;\n\n        mask & rhs == mask\n    }\n}\n"
  },
  {
    "path": "src/input/parser.rs",
    "content": "use std::ops::ControlFlow;\n\nuse crate::input::*;\n\n#[derive(Default)]\npub struct Parser {\n    events: Vec<Event>,\n    sequence: Sequence,\n}\n\n#[derive(Default)]\nenum Sequence {\n    #[default]\n    Char,\n    Escape,\n    Control,\n    Mouse(Mouse),\n    Keyboard(Keyboard),\n    DeviceControl(DeviceControl),\n}\n\n#[derive(Clone, Debug)]\npub enum TerminalEvent {\n    Name(String),\n    TrueColorSupported,\n}\n\n#[derive(Clone, Debug)]\npub enum Event {\n    KeyPress { key: Key },\n    MouseUp { row: usize, col: usize },\n    MouseDown { row: usize, col: usize },\n    MouseMove { row: usize, col: usize },\n    Scroll { delta: isize },\n    Terminal(TerminalEvent),\n    Exit,\n}\n\npub type ParseControlFlow = ControlFlow<Option<Event>, Option<Event>>;\n\nimpl Parser {\n    pub fn new() -> Parser {\n        Self::default()\n    }\n\n    pub fn parse(&mut self, input: &[u8]) -> Vec<Event> {\n        let mut sequence = std::mem::take(&mut self.sequence);\n\n        macro_rules! emit {\n            ($event:expr) => {{\n                if let Some(event) = $event.into() {\n                    self.events.push(event);\n                }\n\n                Sequence::Char\n            }};\n            ($event:expr; continue) => {{\n                if let Some(event) = $event.into() {\n                    self.events.push(event);\n                }\n\n                continue;\n            }};\n        }\n        macro_rules! parse {\n            ($parser:expr, $key:expr) => (\n                match $parser.parse($key) {\n                    ControlFlow::Break(None) => Sequence::Char,\n                    ControlFlow::Break(Some(event)) => emit!(event),\n                    ControlFlow::Continue(None) => continue,\n                    ControlFlow::Continue(Some(event)) => emit!(event; continue),\n                }\n            );\n        }\n\n        for &key in input {\n            sequence = match sequence {\n                Sequence::Char => match key {\n                    0x1b => Sequence::Escape,\n                    0x03 => emit!(Event::Exit),\n                    key => emit!(Event::KeyPress { key: key.into() }),\n                },\n                Sequence::Escape => match key {\n                    b'[' => Sequence::Control,\n                    b'P' => Sequence::DeviceControl(DeviceControl::new()),\n                    0x1b => emit!(Event::KeyPress { key: 0x1b.into() }; continue),\n                    key => {\n                        emit!(Event::KeyPress { key: 0x1b.into() });\n                        emit!(Event::KeyPress { key: key.into() })\n                    }\n                },\n                Sequence::Control => match key {\n                    b'<' => Sequence::Mouse(Mouse::new()),\n                    b'1' => Sequence::Keyboard(Keyboard::new()),\n                    key => emit!(Keyboard::key(key, 0)),\n                },\n                Sequence::Mouse(ref mut mouse) => parse!(mouse, key),\n                Sequence::Keyboard(ref mut keyboard) => parse!(keyboard, key),\n                Sequence::DeviceControl(ref mut dcs) => parse!(dcs, key),\n            }\n        }\n\n        self.sequence = sequence;\n\n        std::mem::take(&mut self.events)\n    }\n}\n"
  },
  {
    "path": "src/input/tty.rs",
    "content": "use std::fs::File;\nuse std::io;\nuse std::io::Write;\nuse std::mem::MaybeUninit;\nuse std::os::fd::RawFd;\nuse std::os::unix::prelude::AsRawFd;\n\nuse crate::utils::log;\n\npub struct Terminal {\n    settings: Option<TerminalSettings>,\n    alt_screen: bool,\n}\n\nimpl Drop for Terminal {\n    fn drop(&mut self) {\n        self.teardown()\n    }\n}\n\nimpl Terminal {\n    /// Setup the input stream to operate in raw mode.\n    /// Returns an object that'll revert terminal settings.\n    pub fn setup() -> Self {\n        Self {\n            settings: match TerminalSettings::open_raw() {\n                Ok(settings) => Some(settings),\n                Err(error) => {\n                    log::error!(\"Failed to setup terminal: {error}\");\n\n                    None\n                }\n            },\n            alt_screen: if let Err(error) = TTY::enter_alt_screen() {\n                log::error!(\"Failed to enter alternative screen: {error}\");\n\n                false\n            } else {\n                true\n            },\n        }\n    }\n\n    pub fn teardown(&mut self) {\n        if let Some(ref settings) = self.settings {\n            if let Err(error) = settings.apply() {\n                log::error!(\"Failed to revert terminal settings: {error}\");\n            }\n\n            self.settings = None;\n        }\n\n        if self.alt_screen {\n            if let Err(error) = TTY::quit_alt_screen() {\n                log::error!(\"Failed to quit alternative screen: {error}\");\n            }\n\n            self.alt_screen = false;\n        }\n    }\n}\n\nenum TTY {\n    Raw(RawFd),\n    File(File),\n}\n\nconst SEQUENCES: [(u32, bool); 4] = [(1049, true), (1003, true), (1006, true), (25, false)];\n\nimpl TTY {\n    fn stdin() -> TTY {\n        let isatty = unsafe { libc::isatty(libc::STDIN_FILENO) };\n\n        if isatty != 1 {\n            if let Ok(file) = File::open(\"/dev/tty\") {\n                return TTY::File(file);\n            }\n        }\n\n        TTY::Raw(libc::STDIN_FILENO)\n    }\n\n    fn enter_alt_screen() -> io::Result<()> {\n        let mut out = io::stdout();\n\n        for (sequence, enable) in SEQUENCES {\n            write!(out, \"\\x1b[?{}{}\", sequence, if enable { \"h\" } else { \"l\" })?;\n        }\n\n        // Set the current foreground color to black\n        write!(out, \"\\x1b[48;2;0;0;0m\")?;\n        // Query current foreground color to for true-color support detection\n        write!(out, \"\\x1bP$qm\\x1b\\\\\")?;\n        // Query current terminal name\n        write!(out, \"\\x1bP+q544e\\x1b\\\\\")?;\n\n        out.flush()\n    }\n\n    fn quit_alt_screen() -> io::Result<()> {\n        let mut out = io::stdout();\n\n        for (sequence, enable) in SEQUENCES {\n            write!(out, \"\\x1b[?{}{}\", sequence, if enable { \"l\" } else { \"h\" })?;\n        }\n\n        out.flush()\n    }\n\n    fn as_raw_fd(self) -> RawFd {\n        match self {\n            TTY::Raw(fd) => fd,\n            TTY::File(file) => file.as_raw_fd(),\n        }\n    }\n}\n\ntrait ToErr {\n    fn to_err(self) -> io::Result<()>;\n}\nimpl ToErr for libc::c_int {\n    fn to_err(self) -> io::Result<()> {\n        if self == 0 {\n            Ok(())\n        } else {\n            Err(io::Error::last_os_error())\n        }\n    }\n}\n\n/// Safe wrapper around libc::termios\n#[derive(Clone)]\nstruct TerminalSettings {\n    data: libc::termios,\n}\n\nimpl TerminalSettings {\n    /// Fetch settings from the current TTY\n    fn open() -> io::Result<Self> {\n        let tty = TTY::stdin();\n        let mut term = MaybeUninit::uninit();\n        let data = unsafe {\n            libc::tcgetattr(tty.as_raw_fd(), term.as_mut_ptr()).to_err()?;\n\n            term.assume_init()\n        };\n\n        Ok(Self { data })\n    }\n\n    fn open_raw() -> io::Result<TerminalSettings> {\n        let mut raw = Self::open()?;\n        let settings = raw.clone();\n\n        raw.make_raw();\n        raw.apply()?;\n\n        Ok(settings)\n    }\n\n    /// Enable raw input\n    fn make_raw(&mut self) {\n        let c_oflag = self.data.c_oflag;\n\n        // Set the terminal to raw mode\n        unsafe { libc::cfmakeraw(&mut self.data) }\n\n        // Restore output flags, ensures carriage returns are consistent\n        self.data.c_oflag = c_oflag;\n    }\n\n    /// Apply the settings to the current TTY\n    fn apply(&self) -> io::Result<()> {\n        let tty = TTY::stdin();\n\n        unsafe { libc::tcsetattr(tty.as_raw_fd(), libc::TCSANOW, &self.data).to_err() }\n    }\n}\n"
  },
  {
    "path": "src/input.rs",
    "content": "mod dcs;\nmod keyboard;\nmod listen;\nmod mouse;\nmod parser;\nmod tty;\n\npub use dcs::*;\npub use keyboard::*;\npub use listen::*;\npub use mouse::*;\npub use parser::*;\npub use tty::*;\n"
  },
  {
    "path": "src/lib.rs",
    "content": "pub mod browser;\npub mod cli;\npub mod gfx;\npub mod input;\npub mod output;\npub mod ui;\n\nmod utils;\n"
  },
  {
    "path": "src/output/cell.rs",
    "content": "use std::rc::Rc;\n\nuse crate::gfx::{Color, Point};\n\n#[derive(Clone, PartialEq)]\npub struct Grapheme {\n    /// Unicode character in UTF-8, might contain multiple code points (Emoji, CJK).\n    pub char: String,\n    pub index: usize,\n    pub width: usize,\n    pub color: Color,\n}\n\n/// Terminal cell with `height = width * 2`\n#[derive(PartialEq)]\npub struct Cell {\n    pub cursor: Point<u32>,\n    /// Text grapheme if any\n    pub grapheme: Option<Rc<Grapheme>>,\n    pub quadrant: (Color, Color, Color, Color),\n}\n\nimpl Cell {\n    pub fn new(x: u32, y: u32) -> Cell {\n        Cell {\n            cursor: Point::new(x, y),\n            grapheme: None,\n            quadrant: (\n                Color::black(),\n                Color::black(),\n                Color::black(),\n                Color::black(),\n            ),\n        }\n    }\n}\n"
  },
  {
    "path": "src/output/frame_sync.rs",
    "content": "use std::time::{Duration, Instant};\n\n/// A utility to synchronize rendering with a given FPS\npub struct FrameSync {\n    render_start: Option<Instant>,\n    frame_duration: Duration,\n}\n\nimpl FrameSync {\n    pub fn new(fps: f32) -> Self {\n        Self {\n            render_start: None,\n            frame_duration: Duration::from_micros((1_000_000.0 / fps) as u64),\n        }\n    }\n\n    /// Mark the beginning of the render\n    pub fn start(&mut self) {\n        self.render_start = Some(Instant::now());\n    }\n\n    /// Get a deadline until the next frame\n    pub fn deadline(&self) -> Instant {\n        match self.render_start {\n            // We never rendered yet, render now!\n            None => Instant::now(),\n            // Else we should render `frame_duration` after the last render start.\n            // If we render at 60 FPS, this should be 16ms after the render start.\n            // If the render takes more than the frame duration, this will always\n            // return a deadline in a the past, making render happen immediately.\n            Some(render_start) => render_start + self.frame_duration,\n        }\n    }\n}\n"
  },
  {
    "path": "src/output/kd_tree.rs",
    "content": "use std::ops::Mul;\n\nuse crate::gfx::Color;\n\nstruct KDNode {\n    left: Option<Box<KDNode>>,\n    right: Option<Box<KDNode>>,\n    normal: Color<f64>,\n    middle: (usize, Color<f64>),\n}\n\nimpl KDNode {\n    fn new(colors: &[Color]) {\n        let (sum, sum_squared) = colors.iter().fold(\n            (Color::black(), Color::black()),\n            |(sum, sum_squared), color| (sum + color, sum_squared + color * color),\n        );\n    }\n\n    fn nearest(&self, color: Color<f64>, mut limit: f64) -> Option<(usize, f64)> {\n        let diff = color - self.middle.1;\n        let distance = diff.mul(&diff).sum().sqrt();\n        let mut result = None;\n\n        if distance < limit {\n            limit = distance;\n        }\n\n        let dot = diff.mul(self.normal).sum();\n\n        if dot <= 0.0 {\n            if let Some(ref left) = self.left {\n                if let Some(nearest) = left.nearest(color, limit) {\n                    limit = nearest.1;\n                    result = Some(nearest);\n                }\n            }\n\n            if -dot < limit {\n                if let Some(ref right) = self.right {\n                    if let Some(nearest) = right.nearest(color, limit) {\n                        result = Some(nearest);\n                    }\n                }\n            }\n        } else {\n            if let Some(ref right) = self.right {\n                if let Some(nearest) = right.nearest(color, limit) {\n                    limit = nearest.1;\n                    result = Some(nearest);\n                }\n            }\n\n            if dot < limit {\n                if let Some(ref left) = self.left {\n                    if let Some(nearest) = left.nearest(color, limit) {\n                        result = Some(nearest);\n                    }\n                }\n            }\n        }\n\n        result\n    }\n}\n"
  },
  {
    "path": "src/output/painter.rs",
    "content": "use std::io::{self, Stdout, Write};\n\nuse crate::gfx::{Color, Point};\n\nuse super::{binarize_quandrant, Cell};\n\npub struct Painter {\n    output: Stdout,\n    buffer: Vec<u8>,\n    cursor: Option<Point<u32>>,\n    true_color: bool,\n    background: Option<Color>,\n    foreground: Option<Color>,\n    background_code: Option<u8>,\n    foreground_code: Option<u8>,\n}\n\nimpl Painter {\n    pub fn new() -> Painter {\n        Painter {\n            buffer: Vec::new(),\n            cursor: None,\n            output: io::stdout(),\n            background: None,\n            foreground: None,\n            background_code: None,\n            foreground_code: None,\n            true_color: match std::env::var(\"COLORTERM\").unwrap_or_default().as_str() {\n                \"truecolor\" | \"24bit\" => true,\n                _ => false,\n            },\n        }\n    }\n\n    pub fn true_color(&self) -> bool {\n        self.true_color\n    }\n\n    pub fn set_true_color(&mut self, true_color: bool) {\n        self.true_color = true_color\n    }\n\n    pub fn begin(&mut self) -> io::Result<()> {\n        write!(self.buffer, \"\\x1b[?25l\\x1b[?12l\")\n    }\n\n    pub fn end(&mut self, cursor: Option<Point>) -> io::Result<()> {\n        if let Some(cursor) = cursor {\n            write!(\n                self.buffer,\n                \"\\x1b[{};{}H\\x1b[?25h\\x1b[?12h\",\n                cursor.y + 1,\n                cursor.x + 1\n            )?;\n        }\n\n        self.output.write(self.buffer.as_slice())?;\n        self.output.flush()?;\n        self.buffer.clear();\n        self.cursor = None;\n\n        Ok(())\n    }\n\n    pub fn paint(&mut self, cell: &Cell) -> io::Result<()> {\n        let &Cell {\n            cursor,\n            quadrant,\n            ref grapheme,\n        } = cell;\n\n        let (char, background, foreground, width) = if let Some(grapheme) = grapheme {\n            if grapheme.index > 0 {\n                return Ok(());\n            }\n\n            (\n                grapheme.char.as_str(),\n                quadrant\n                    .0\n                    .avg_with(quadrant.1)\n                    .avg_with(quadrant.2)\n                    .avg_with(quadrant.3),\n                grapheme.color,\n                grapheme.width as u32,\n            )\n        } else {\n            let (char, background, foreground) = binarize_quandrant(quadrant);\n\n            (char, background, foreground, 1)\n        };\n\n        if self.cursor != Some(cursor) {\n            write!(self.buffer, \"\\x1b[{};{}H\", cursor.y + 1, cursor.x + 1)?;\n        };\n\n        self.cursor = Some(cursor + Point::new(width, 0));\n\n        if self.background != Some(background) {\n            self.background = Some(background);\n\n            if self.true_color {\n                write!(\n                    self.buffer,\n                    \"\\x1b[48;2;{};{};{}m\",\n                    background.r, background.g, background.b,\n                )?\n            } else {\n                let code = background.to_xterm();\n\n                if self.background_code != Some(code) {\n                    self.background_code = Some(code);\n\n                    write!(self.buffer, \"\\x1b[48;5;{code}m\")?\n                }\n            }\n        }\n\n        if self.foreground != Some(foreground) {\n            self.foreground = Some(foreground);\n\n            if self.true_color {\n                write!(\n                    self.buffer,\n                    \"\\x1b[38;2;{};{};{}m\",\n                    foreground.r, foreground.g, foreground.b,\n                )?\n            } else {\n                let code = foreground.to_xterm();\n\n                if self.foreground_code != Some(code) {\n                    self.foreground_code = Some(code);\n\n                    write!(self.buffer, \"\\x1b[38;5;{code}m\")?\n                }\n            }\n        }\n\n        self.buffer.write_all(char.as_bytes())?;\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/output/quad.rs",
    "content": "use crate::gfx::Color;\nuse crate::utils::FourBits::{self, *};\n\n/// Turn a quadrant of four colors into two colors and a quadrant unicode character.\npub fn binarize_quandrant(\n    (x, y, z, w): (Color, Color, Color, Color),\n) -> (&'static str, Color, Color) {\n    // Step 1: grayscale\n    const LUMA: Color<f32> = Color::new(0.299, 0.587, 0.114);\n    let (a, b, c, d) = (\n        LUMA.dot(x.cast()),\n        LUMA.dot(y.cast()),\n        LUMA.dot(z.cast()),\n        LUMA.dot(w.cast()),\n    );\n    // Step 2: luminance middlepoint\n    let min = a.min(b).min(c).min(d);\n    let max = a.max(b).max(c).max(d);\n    let mid = min + (max - min) / 2.0;\n\n    // Step 3: average colors based on binary mask\n    match FourBits::new(a > mid, b > mid, c > mid, d > mid) {\n        B0000 => (\"▄\", x.avg_with(y), z.avg_with(w)),\n        B0001 => (\"▖\", x.avg_with(y).avg_with(z), w),\n        B0010 => (\"▗\", x.avg_with(y).avg_with(w), z),\n        B0011 => (\"▄\", x.avg_with(y), z.avg_with(w)),\n        B0100 => (\"▝\", x.avg_with(z).avg_with(w), y),\n        B0101 => (\"▞\", x.avg_with(z), y.avg_with(w)),\n        B0110 => (\"▐\", x.avg_with(w), y.avg_with(z)),\n        B0111 => (\"▘\", y.avg_with(z).avg_with(w), x),\n        B1000 => (\"▘\", y.avg_with(z).avg_with(w), x),\n        B1001 => (\"▌\", y.avg_with(z), x.avg_with(w)),\n        B1010 => (\"▚\", y.avg_with(w), x.avg_with(z)),\n        B1011 => (\"▝\", x.avg_with(z).avg_with(w), y),\n        B1100 => (\"▄\", x.avg_with(y), z.avg_with(w)),\n        B1101 => (\"▗\", x.avg_with(y).avg_with(w), z),\n        B1110 => (\"▖\", x.avg_with(y).avg_with(z), w),\n        B1111 => (\"▄\", x.avg_with(y), z.avg_with(w)),\n    }\n}\n"
  },
  {
    "path": "src/output/quantizer.rs",
    "content": "use crate::gfx::Color;\n\n#[derive(Clone, Copy)]\nenum Channel {\n    R,\n    G,\n    B,\n}\n\nconst COLOR_BUCKETS: usize = 8;\nconst COLORS: usize = 2_usize.pow(COLOR_BUCKETS as u32);\n\n/// Find the closest color to `color` on `palette` using a binary search\npub fn palette_color(palette: &[Color; COLORS], color: Color) {\n    let mut size = palette.len() / 2;\n    let mut iter = palette.iter();\n    let mut prev = iter.next();\n}\n\nfn distance(a: Color, b: Color) {}\n\npub fn quantize(pixels: &[u8]) -> [Color; COLORS] {\n    let mut min = Color::black();\n    let mut max = Color::black();\n    let mut bucket = Vec::<Color>::new();\n    let mut pixels_iter = pixels.iter();\n    let mut bucket_iter = bucket.iter_mut();\n\n    // Step 1: find the dominant channel\n    loop {\n        match (\n            bucket_iter.next(),\n            pixels_iter.next(),\n            pixels_iter.next(),\n            pixels_iter.next(),\n            pixels_iter.next(),\n        ) {\n            (Some(color), Some(r), Some(g), Some(b), Some(_)) => {\n                // Save the color in a bucket\n                color.set_r(*r);\n                color.set_g(*g);\n                color.set_b(*b);\n\n                min.set_r(min.r().min(color.r()));\n                min.set_g(min.g().min(color.g()));\n                min.set_b(min.b().min(color.b()));\n\n                max.set_r(max.r().max(color.r()));\n                max.set_g(max.g().max(color.g()));\n                max.set_b(max.b().max(color.b()));\n            }\n            _ => break,\n        }\n    }\n\n    let ranges = [\n        (Channel::R, max.r() - min.r()),\n        (Channel::G, max.g() - min.g()),\n        (Channel::B, max.b() - min.b()),\n    ];\n    let (channel, _) = ranges\n        .iter()\n        .reduce(|a, b| if a.1 > b.1 { a } else { b })\n        .unwrap();\n\n    // Step 2: perform median-cut\n    for i in 1..=COLOR_BUCKETS {\n        let buckets = 2_usize.pow(i as u32);\n        let size = bucket.len() / buckets;\n\n        for j in 0..buckets {\n            let start = j * size;\n            let end = start + size;\n            let slice = &mut bucket[start..end];\n\n            slice.sort_unstable_by(match channel {\n                Channel::R => |a: &Color, b: &Color| a.r().cmp(&b.r()),\n                Channel::G => |a: &Color, b: &Color| a.g().cmp(&b.g()),\n                Channel::B => |a: &Color, b: &Color| a.b().cmp(&b.b()),\n            });\n        }\n    }\n\n    // Step 3: get the average color in each bucket\n    let mut palette = [Color::black(); COLORS];\n    let size = bucket.len() / palette.len();\n\n    for (i, color) in palette.iter_mut().enumerate() {\n        let start = i * size;\n        let end = start + size;\n        let slice = &bucket[start..end];\n        let mut sum = None;\n\n        for color in slice.into_iter() {\n            sum = Some(match sum {\n                None => color.cast(),\n                Some(sum) => color.cast() + sum,\n            })\n        }\n\n        if let Some(sum) = sum {\n            let avg = sum / size as u32;\n\n            color.set_r(avg.r() as u8);\n            color.set_g(avg.g() as u8);\n            color.set_b(avg.b() as u8);\n        }\n    }\n\n    palette\n}\n"
  },
  {
    "path": "src/output/render_thread.rs",
    "content": "use std::{\n    sync::mpsc::{self, Receiver, Sender},\n    thread::{self, JoinHandle},\n    time::Instant,\n};\n\nuse crate::cli::CommandLine;\n\nuse super::{FrameSync, Renderer};\n\n/// Control a rendering thread that lazily starts.\n/// This allows the `Bridge` struct to be used in places\n/// where we do not expected the rendering thread to start.\npub struct RenderThread {\n    thread: Option<(Sender<Message>, JoinHandle<()>)>,\n    enabled: bool,\n}\n\ntype RenderClosure = Box<dyn FnMut(&mut Renderer) + Send>;\nenum Message {\n    Run(RenderClosure),\n    Shutdown,\n}\n\nimpl RenderThread {\n    pub fn new() -> Self {\n        Self {\n            thread: None,\n            enabled: false,\n        }\n    }\n\n    /// Enable the rendering thread.\n    /// Allows the thread to be lazily initiated.\n    pub fn enable(&mut self) {\n        self.enabled = true\n    }\n\n    /// Stop the rendering thread.\n    /// Returns a `JoinHandle` if a thread was started.\n    pub fn stop(&mut self) -> Option<JoinHandle<()>> {\n        self.enabled = false;\n        self.send(Message::Shutdown);\n\n        let (_, handle) = self.thread.take()?;\n\n        Some(handle)\n    }\n\n    /// Run a closure on the rendering thread.\n    pub fn render<F>(&mut self, run: F)\n    where\n        F: FnMut(&mut Renderer) + Send + 'static,\n    {\n        self.send(Message::Run(Box::new(run)))\n    }\n\n    /// Boot the rendering thread, contains a simple event loop.\n    fn boot(rx: Receiver<Message>) {\n        let cmd = CommandLine::parse();\n        let mut sync = FrameSync::new(cmd.fps);\n        let mut renderer = Renderer::new();\n        let mut needs_render = false;\n\n        loop {\n            // Get a deadline for the next frame\n            let deadline = sync.deadline();\n            let mut wait = true;\n\n            loop {\n                let message = if wait {\n                    // On the first iteration, we want to block indefinitely\n                    // until we get a message, after which we schedule a render.\n                    wait = false;\n\n                    rx.recv().ok()\n                } else {\n                    // On subsequence iterations, we want to process a maximum\n                    // number of events until the deadline for the next frame.\n                    rx.recv_timeout(deadline - Instant::now()).ok()\n                };\n\n                match message {\n                    // Timeout and no message, render if needed\n                    None => break,\n                    // Shutdown the thread\n                    Some(Message::Shutdown) => return,\n                    // Run a closure and schedule a render\n                    Some(Message::Run(mut closure)) => {\n                        closure(&mut renderer);\n\n                        needs_render = true;\n                    }\n                }\n            }\n\n            // Render if needed\n            if needs_render {\n                needs_render = false;\n\n                // Update the frame sync timings\n                sync.start();\n                renderer.render().unwrap();\n            }\n        }\n    }\n\n    /// Send a message to the rendering thread.\n    /// Creates a new thread if enabled and needed.\n    fn send(&mut self, message: Message) {\n        if let Some((tx, _)) = &self.thread {\n            tx.send(message).unwrap()\n        } else if self.enabled {\n            let (tx, rx) = mpsc::channel();\n\n            tx.send(message).unwrap();\n\n            self.thread = Some((tx.clone(), thread::spawn(move || Self::boot(rx))));\n        }\n    }\n}\n"
  },
  {
    "path": "src/output/renderer.rs",
    "content": "use std::{\n    io::{self, Write},\n    rc::Rc,\n};\n\nuse unicode_segmentation::UnicodeSegmentation;\nuse unicode_width::UnicodeWidthStr;\n\nuse crate::{\n    gfx::{Color, Point, Rect, Size},\n    input::Key,\n    ui::navigation::{Navigation, NavigationAction},\n    utils::log,\n};\n\nuse super::{Cell, Grapheme, Painter};\n\npub struct Renderer {\n    nav: Navigation,\n    cells: Vec<(Cell, Cell)>,\n    painter: Painter,\n    size: Size,\n}\n\nimpl Renderer {\n    pub fn new() -> Renderer {\n        Renderer {\n            nav: Navigation::new(),\n            cells: Vec::with_capacity(0),\n            painter: Painter::new(),\n            size: Size::new(0, 0),\n        }\n    }\n\n    pub fn enable_true_color(&mut self) {\n        self.painter.set_true_color(true)\n    }\n\n    pub fn keypress(&mut self, key: &Key) -> io::Result<NavigationAction> {\n        let action = self.nav.keypress(key);\n\n        Ok(action)\n    }\n    pub fn mouse_up(&mut self, origin: Point) -> io::Result<NavigationAction> {\n        let action = self.nav.mouse_up(origin);\n\n        Ok(action)\n    }\n    pub fn mouse_down(&mut self, origin: Point) -> io::Result<NavigationAction> {\n        let action = self.nav.mouse_down(origin);\n\n        Ok(action)\n    }\n    pub fn mouse_move(&mut self, origin: Point) -> io::Result<NavigationAction> {\n        let action = self.nav.mouse_move(origin);\n\n        Ok(action)\n    }\n\n    pub fn push_nav(&mut self, url: &str, can_go_back: bool, can_go_forward: bool) {\n        self.nav.push(url, can_go_back, can_go_forward)\n    }\n\n    pub fn get_size(&self) -> Size {\n        self.size\n    }\n\n    pub fn set_size(&mut self, size: Size) {\n        self.nav.set_size(size);\n        self.size = size;\n\n        let mut x = 0;\n        let mut y = 0;\n        let bound = size.width - 1;\n        let cells = (size.width + size.width * size.height) as usize;\n\n        self.cells.clear();\n        self.cells.resize_with(cells, || {\n            let cell = (Cell::new(x, y), Cell::new(x, y));\n\n            if x < bound {\n                x += 1;\n            } else {\n                x = 0;\n                y += 1;\n            }\n\n            cell\n        });\n    }\n\n    pub fn render(&mut self) -> io::Result<()> {\n        let size = self.size;\n\n        for (origin, element) in self.nav.render(size) {\n            self.fill_rect(\n                Rect::new(origin.x, origin.y, element.text.width() as u32, 1),\n                element.background,\n            );\n            self.draw_text(\n                &element.text,\n                origin * (2, 1),\n                Size::splat(0),\n                element.foreground,\n            );\n        }\n\n        self.painter.begin()?;\n\n        for (previous, current) in self.cells.iter_mut() {\n            if current == previous {\n                continue;\n            }\n\n            previous.quadrant = current.quadrant;\n            previous.grapheme = current.grapheme.clone();\n\n            self.painter.paint(current)?;\n        }\n\n        self.painter.end(self.nav.cursor())?;\n\n        Ok(())\n    }\n\n    /// Draw the background from a pixel array encoded in RGBA8888\n    pub fn draw_background(&mut self, pixels: &[u8], pixels_size: Size, rect: Rect) {\n        let viewport = self.size.cast::<usize>();\n\n        if pixels.len() < viewport.width * viewport.height * 8 * 4 {\n            log::debug!(\n                \"unexpected size, actual: {}, expected: {}\",\n                pixels.len(),\n                viewport.width * viewport.height * 8 * 4\n            );\n            return;\n        }\n\n        let origin = rect.origin.cast::<f32>().max(0.0) / (2.0, 4.0);\n        let size = rect.size.cast::<f32>().max(0.0) / (2.0, 4.0);\n        let top = (origin.y.floor() as usize).min(viewport.height);\n        let left = (origin.x.floor() as usize).min(viewport.width);\n        let right = ((origin.x + size.width).ceil() as usize)\n            .min(viewport.width)\n            .max(left);\n        let bottom = ((origin.y + size.height).ceil() as usize)\n            .min(viewport.height)\n            .max(top);\n        let row_length = pixels_size.width as usize;\n        let pixel = |x, y| {\n            Color::new(\n                pixels[((x + y * row_length) * 4 + 2) as usize],\n                pixels[((x + y * row_length) * 4 + 1) as usize],\n                pixels[((x + y * row_length) * 4 + 0) as usize],\n            )\n        };\n        let pair = |x, y| pixel(x, y).avg_with(pixel(x, y + 1));\n\n        for y in top..bottom {\n            let index = (y + 1) * viewport.width;\n            let start = index + left;\n            let end = index + right;\n            let (mut x, y) = (left * 2, y * 4);\n\n            for (_, cell) in &mut self.cells[start..end] {\n                cell.quadrant = (\n                    pair(x + 0, y + 0),\n                    pair(x + 1, y + 0),\n                    pair(x + 1, y + 2),\n                    pair(x + 0, y + 2),\n                );\n\n                x += 2;\n            }\n        }\n    }\n\n    pub fn clear_text(&mut self) {\n        for (_, cell) in self.cells.iter_mut() {\n            cell.grapheme = None\n        }\n    }\n\n    pub fn set_title(&self, title: &str) -> io::Result<()> {\n        let mut stdout = io::stdout();\n\n        write!(stdout, \"\\x1b]0;{title}\\x07\")?;\n        write!(stdout, \"\\x1b]1;{title}\\x07\")?;\n        write!(stdout, \"\\x1b]2;{title}\\x07\")?;\n\n        stdout.flush()\n    }\n\n    pub fn fill_rect(&mut self, rect: Rect, color: Color) {\n        self.draw(rect, |cell| {\n            cell.grapheme = None;\n            cell.quadrant = (color, color, color, color);\n        })\n    }\n\n    pub fn draw<F>(&mut self, bounds: Rect, mut draw: F)\n    where\n        F: FnMut(&mut Cell),\n    {\n        let origin = bounds.origin.cast::<usize>();\n        let size = bounds.size.cast::<usize>();\n        let viewport_width = self.size.width as usize;\n        let top = origin.y;\n        let bottom = top + size.height;\n\n        // Iterate over each row\n        for y in top..bottom {\n            let left = y * viewport_width + origin.x;\n            let right = left + size.width;\n\n            for (_, current) in self.cells[left..right].iter_mut() {\n                draw(current)\n            }\n        }\n    }\n\n    /// Render some text into the terminal output\n    pub fn draw_text(&mut self, string: &str, origin: Point, size: Size, color: Color) {\n        // Get an iterator starting at the text origin\n        let len = self.cells.len();\n        let viewport = &self.size.cast::<usize>();\n\n        if size.width > 2 && size.height > 2 {\n            let origin = (origin.cast::<f32>() / (2.0, 4.0) + (0.0, 1.0)).round();\n            let size = (size.cast::<f32>() / (2.0, 4.0)).round();\n            let left = (origin.x.max(0.0) as usize).min(viewport.width);\n            let right = ((origin.x + size.width).max(0.0) as usize).min(viewport.width);\n            let top = (origin.y.max(0.0) as usize).min(viewport.height);\n            let bottom = ((origin.y + size.height).max(0.0) as usize).min(viewport.height);\n\n            for y in top..bottom {\n                let index = y * viewport.width;\n                let start = index + left;\n                let end = index + right;\n\n                for (_, cell) in self.cells[start..end].iter_mut() {\n                    cell.grapheme = None\n                }\n            }\n        } else {\n            // Compute the buffer index based on the position\n            let index = origin.x / 2 + (origin.y + 1) / 4 * (viewport.width as i32);\n            let mut iter = self.cells[len.min(index as usize)..].iter_mut();\n\n            // Get every Unicode grapheme in the input string\n            for grapheme in UnicodeSegmentation::graphemes(string, true) {\n                let width = grapheme.width();\n\n                for index in 0..width {\n                    // Get the next terminal cell at the given position\n                    match iter.next() {\n                        // Stop if we're at the end of the buffer\n                        None => return,\n                        // Set the cell to the current grapheme\n                        Some((_, cell)) => {\n                            let next = Grapheme {\n                                // Create a new shared reference to the text\n                                color,\n                                index,\n                                width,\n                                // Export the set of unicode code points for this graphene into an UTF-8 string\n                                char: grapheme.to_string(),\n                            };\n\n                            if match cell.grapheme {\n                                None => true,\n                                Some(ref previous) => {\n                                    previous.color != next.color || previous.char != next.char\n                                }\n                            } {\n                                cell.grapheme = Some(Rc::new(next))\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/output/window.rs",
    "content": "use core::mem::MaybeUninit;\nuse std::str::FromStr;\n\nuse crate::{cli::CommandLine, gfx::Size, utils::log};\n\n/// A terminal window.\n#[derive(Clone, Debug)]\npub struct Window {\n    /// Device pixel ratio\n    pub dpi: f32,\n    /// Size of a terminal cell in pixels\n    pub scale: Size<f32>,\n    /// Size of the termina window in cells\n    pub cells: Size,\n    /// Size of the browser window in pixels\n    pub browser: Size,\n    /// Command line arguments\n    pub cmd: CommandLine,\n}\n\nimpl Window {\n    /// Read the window\n    pub fn read() -> Window {\n        let mut window = Self {\n            dpi: 1.0,\n            scale: (0.0, 0.0).into(),\n            cells: (0, 0).into(),\n            browser: (0, 0).into(),\n            cmd: CommandLine::parse(),\n        };\n\n        window.update();\n\n        window\n    }\n\n    pub fn update(&mut self) -> &Self {\n        let (mut term, mut cell) = unsafe {\n            let mut ptr = MaybeUninit::<libc::winsize>::uninit();\n\n            if libc::ioctl(libc::STDOUT_FILENO, libc::TIOCGWINSZ, ptr.as_mut_ptr()) == 0 {\n                let size = ptr.assume_init();\n\n                (\n                    Size::new(size.ws_col, size.ws_row),\n                    Size::new(size.ws_xpixel, size.ws_ypixel),\n                )\n            } else {\n                (Size::splat(0), Size::splat(0))\n            }\n        };\n\n        if cell.width == 0 || cell.height == 0 {\n            cell.width = 8;\n            cell.height = 16;\n        }\n\n        if term.width == 0 || term.height == 0 {\n            let cols = match parse_var(\"COLUMNS\").unwrap_or(0) {\n                0 => 80,\n                x => x,\n            };\n            let rows = match parse_var(\"LINES\").unwrap_or(0) {\n                0 => 24,\n                x => x,\n            };\n\n            log::warning!(\n                \"TIOCGWINSZ returned an empty size ({}x{}), defaulting to {}x{}\",\n                term.width,\n                term.height,\n                cols,\n                rows\n            );\n\n            term.width = cols;\n            term.height = rows;\n        }\n\n        let zoom = 1.5 * self.cmd.zoom;\n        let cells = Size::new(term.width.max(1), term.height.max(2) - 1);\n        let auto_scale = false;\n        let cell_pixels = if auto_scale {\n            Size::new(cell.width as f32, cell.height as f32) / cells.cast()\n        } else {\n            Size::new(8.0, 16.0)\n        };\n        // Normalize the cells dimensions for an aspect ratio of 1:2\n        let cell_width = (cell_pixels.width + cell_pixels.height / 2.0) / 2.0;\n\n        // Round DPI to 2 decimals for proper viewport computations\n        self.dpi = (2.0 / cell_width * zoom * 100.0).ceil() / 100.0;\n        // A virtual cell should contain a 2x4 pixel quadrant\n        self.scale = Size::new(2.0, 4.0) / self.dpi;\n        // Keep some space for the UI\n        self.cells = Size::new(term.width.max(1), term.height.max(2) - 1).cast();\n        self.browser = self.cells.cast::<f32>().mul(self.scale).ceil().cast();\n\n        self\n    }\n}\n\nfn parse_var<T: FromStr>(var: &str) -> Option<T> {\n    std::env::var(var).ok()?.parse().ok()\n}\n"
  },
  {
    "path": "src/output/xterm.rs",
    "content": "use crate::gfx::Color;\n\nimpl Color {\n    pub fn to_xterm(&self) -> u8 {\n        if self.max_val() - self.min_val() < 8 {\n            match self.r {\n                0..=4 => 16,\n                5..=8 => 232,\n                238..=246 => 255,\n                247..=255 => 231,\n                r => 232 + (r - 8 + 5) / 10,\n            }\n        } else {\n            let scale = 5.0 / 200.0;\n\n            (16.0\n                + self\n                    .cast::<f32>()\n                    .mul_add(scale, scale * -55.0)\n                    .max(0.0)\n                    .round()\n                    .dot((36.0, 6.0, 1.0))) as u8\n        }\n    }\n}\n"
  },
  {
    "path": "src/output.rs",
    "content": "// mod kd_tree;\n// mod quantizer;\nmod cell;\nmod frame_sync;\nmod painter;\nmod quad;\nmod render_thread;\nmod renderer;\nmod window;\nmod xterm;\n\npub use cell::*;\npub use frame_sync::*;\npub use painter::*;\npub use quad::*;\npub use render_thread::*;\npub use renderer::*;\npub use window::*;\n"
  },
  {
    "path": "src/ui/navigation.rs",
    "content": "use std::env;\n\nuse unicode_width::UnicodeWidthStr;\n\nuse crate::{\n    gfx::{Color, Point, Size},\n    input::Key,\n    utils::log,\n};\n\npub enum NavigationAction {\n    Ignore,\n    Forward,\n    GoTo(String),\n    GoBack(),\n    GoForward(),\n    Refresh(),\n}\n\n#[derive(Debug)]\npub struct NavigationElement {\n    pub text: String,\n    pub background: Color,\n    pub foreground: Color,\n}\n\npub struct Navigation {\n    url: Option<String>,\n    size: Size,\n    cursor: Option<usize>,\n    can_go_back: bool,\n    can_go_forward: bool,\n}\n\nimpl Navigation {\n    pub fn new() -> Self {\n        Self {\n            url: None,\n            size: (0, 0).into(),\n            cursor: None,\n            can_go_back: false,\n            can_go_forward: false,\n        }\n    }\n\n    pub fn cursor(&self) -> Option<Point> {\n        Some((11 + self.cursor? as i32, 0).into())\n    }\n\n    pub fn keypress(&mut self, key: &Key) -> NavigationAction {\n        let modifier_key = match env::consts::OS {\n            \"macos\" => key.modifiers.meta,\n            _ => key.modifiers.alt,\n        };\n\n        match self.cursor {\n            None => match (modifier_key, key.char) {\n                (true, 0x14) => NavigationAction::GoBack(),\n                (true, 0x13) => NavigationAction::GoForward(),\n                _ => NavigationAction::Forward,\n            },\n            Some(cursor) => {\n                if let Some(url) = &mut self.url {\n                    // TODO: Unicode\n                    match key.char {\n                        // Return\n                        0x0d => return NavigationAction::GoTo(url.clone()),\n                        // Up\n                        0x11 => self.cursor = Some(0),\n                        // Down\n                        0x12 => self.cursor = Some(url.width()),\n                        // Right\n                        0x13 => self.cursor = Some((cursor + 1).min(url.width())),\n                        // Left\n                        0x14 => self.cursor = Some(if cursor > 0 { cursor - 1 } else { 0 }),\n                        // Backspace\n                        0x7f => {\n                            if cursor > 0 {\n                                url.remove(cursor - 1);\n\n                                self.cursor = Some(cursor - 1);\n                            }\n                        }\n                        key => {\n                            url.insert(cursor, key as char);\n\n                            self.cursor = Some((cursor + 1).min(url.width()))\n                        }\n                    }\n\n                    NavigationAction::Ignore\n                } else {\n                    NavigationAction::Forward\n                }\n            }\n        }\n    }\n\n    pub fn display_url(&self) -> &str {\n        match &self.url {\n            None => \"about:blank\",\n            Some(url) => url,\n        }\n    }\n\n    pub fn url_size(&self) -> usize {\n        self.display_url().width()\n    }\n\n    pub fn mouse_up(&mut self, origin: Point) -> NavigationAction {\n        if origin.y != 0 {\n            self.cursor = None;\n\n            NavigationAction::Forward\n        } else {\n            NavigationAction::Ignore\n        }\n    }\n    pub fn mouse_down(&mut self, origin: Point) -> NavigationAction {\n        if origin.y != 0 {\n            self.cursor = None;\n\n            return NavigationAction::Forward;\n        }\n\n        self.cursor = None;\n\n        return match origin.x {\n            0..=2 => NavigationAction::GoBack(),\n            3..=5 => NavigationAction::GoForward(),\n            6..=8 => NavigationAction::Refresh(),\n            11.. => {\n                self.cursor = Some(self.url_size().min(origin.x as usize - 11));\n\n                log::debug!(\"setting cursor to {:?}\", self.cursor);\n\n                NavigationAction::Ignore\n            }\n            _ => NavigationAction::Ignore,\n        };\n    }\n    pub fn mouse_move(&mut self, _origin: Point) -> NavigationAction {\n        NavigationAction::Forward\n    }\n\n    pub fn push(&mut self, url: &str, can_go_back: bool, can_go_forward: bool) {\n        if match (self.cursor, &self.url) {\n            (None, _) => false,\n            (_, None) => true,\n            (_, Some(current)) => current != url,\n        } {\n            self.cursor = Some(url.len())\n        }\n\n        self.url = Some(url.to_owned());\n        self.can_go_back = can_go_back;\n        self.can_go_forward = can_go_forward;\n    }\n\n    pub fn set_size(&mut self, size: Size) {\n        self.size = size\n    }\n\n    pub fn render_btn(&self, icon: &str, enabled: bool) -> [NavigationElement; 3] {\n        let background = Color::splat(255);\n        let foreground = Color::splat(0);\n\n        [\n            NavigationElement {\n                text: \"[\".to_owned(),\n                background,\n                foreground,\n            },\n            NavigationElement {\n                text: icon.to_owned(),\n                background,\n                foreground: if enabled {\n                    foreground\n                } else {\n                    Color::splat(200)\n                },\n            },\n            NavigationElement {\n                text: \"]\".to_owned(),\n                background,\n                foreground,\n            },\n        ]\n    }\n\n    pub fn render(&self, size: Size) -> Vec<(Point, NavigationElement)> {\n        let ui_elements = 13;\n        let space = if size.width >= ui_elements {\n            (size.width - ui_elements) as usize\n        } else {\n            0\n        };\n        let url: String = self.display_url().chars().take(space).collect();\n        let width = url.width();\n        let padded = format!(\" {}{} \", url, \" \".repeat(space - width));\n        let mut elements = Vec::new();\n        let mut point = Point::splat(0);\n\n        for list in [\n            self.render_btn(\"\\u{276e}\", self.can_go_back),\n            self.render_btn(\"\\u{276f}\", self.can_go_forward),\n            self.render_btn(\"↻\", true),\n            self.render_btn(&padded, true),\n        ] {\n            for element in list {\n                let width = element.text.width() as i32;\n\n                elements.push((point.clone(), element));\n\n                point = point + (width, 0);\n            }\n        }\n\n        elements\n    }\n}\n"
  },
  {
    "path": "src/ui.rs",
    "content": "pub mod navigation;\n"
  },
  {
    "path": "src/utils/four_bits.rs",
    "content": "pub enum FourBits {\n    B0000 = 0b0000,\n    B0001 = 0b0001,\n    B0010 = 0b0010,\n    B0011 = 0b0011,\n    B0100 = 0b0100,\n    B0101 = 0b0101,\n    B0110 = 0b0110,\n    B0111 = 0b0111,\n    B1000 = 0b1000,\n    B1001 = 0b1001,\n    B1010 = 0b1010,\n    B1011 = 0b1011,\n    B1100 = 0b1100,\n    B1101 = 0b1101,\n    B1110 = 0b1110,\n    B1111 = 0b1111,\n}\n\nimpl FourBits {\n    pub fn new(x: bool, y: bool, z: bool, w: bool) -> Self {\n        use FourBits::*;\n\n        match (x as u8) << 3 | (y as u8) << 2 | (z as u8) << 1 | (w as u8) << 0 {\n            0b0000 => B0000,\n            0b0001 => B0001,\n            0b0010 => B0010,\n            0b0011 => B0011,\n            0b0100 => B0100,\n            0b0101 => B0101,\n            0b0110 => B0110,\n            0b0111 => B0111,\n            0b1000 => B1000,\n            0b1001 => B1001,\n            0b1010 => B1010,\n            0b1011 => B1011,\n            0b1100 => B1100,\n            0b1101 => B1101,\n            0b1110 => B1110,\n            0b1111 => B1111,\n            _ => panic!(\"Unexpected mask value\"),\n        }\n    }\n}\n"
  },
  {
    "path": "src/utils/log.rs",
    "content": "use std::path::Path;\n\nuse chrono::prelude::*;\n\nuse crate::utils::try_block;\n\nmacro_rules! debug {\n    ($($args:expr),+) => {\n        crate::utils::log::write(\n            \"DEBUG\",\n            file!(),\n            line!(),\n            &format!($($args),*)\n        )\n    };\n}\nmacro_rules! warning {\n    ($($args:expr),+) => {\n        crate::utils::log::write(\n            \"WARNING\",\n            file!(),\n            line!(),\n            &format!($($args),*)\n        )\n    };\n}\nmacro_rules! error {\n    ($($args:expr),+) => {\n        crate::utils::log::write(\n            \"ERROR\",\n            file!(),\n            line!(),\n            &format!($($args),*)\n        )\n    };\n}\n\npub(crate) use debug;\npub(crate) use error;\npub(crate) use warning;\n\npub fn write(level: &str, file: &str, line: u32, message: &str) {\n    let date = Utc::now();\n\n    eprintln!(\n        \"[{:02}{:02}/{:02}{:02}{:02}.{:06}:{}:{}({})] {}\",\n        date.month(),\n        date.day(),\n        date.hour(),\n        date.minute(),\n        date.second(),\n        date.nanosecond() / 1000,\n        level,\n        try_block!(Path::new(file).file_name()?.to_str()).unwrap_or(\"default\"),\n        line,\n        message\n    );\n}\n"
  },
  {
    "path": "src/utils/try_block.rs",
    "content": "macro_rules! try_block {\n    ($block:expr) => {\n        (|| $block)()\n    };\n}\n\npub(crate) use try_block;\n"
  },
  {
    "path": "src/utils.rs",
    "content": "mod four_bits;\nmod try_block;\n\npub mod log;\n\nuse try_block::*;\n\npub use four_bits::*;\n"
  }
]