diff --git a/chromium/patches/chromium/0001-Add-Carbonyl-library.patch b/chromium/patches/chromium/0001-Add-Carbonyl-library.patch index 26f1f45..d0f6988 100644 --- a/chromium/patches/chromium/0001-Add-Carbonyl-library.patch +++ b/chromium/patches/chromium/0001-Add-Carbonyl-library.patch @@ -1,7 +1,7 @@ From 0ed9a390f25d73492ce1170ce229b95772fd458d Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Thu, 9 Feb 2023 03:20:50 +0100 -Subject: [PATCH 1/9] Add Carbonyl library +Subject: [PATCH 01/11] Add Carbonyl library --- carbonyl/build | 1 + diff --git a/chromium/patches/chromium/0002-Add-Carbonyl-service.patch b/chromium/patches/chromium/0002-Add-Carbonyl-service.patch index ab08758..70030f4 100644 --- a/chromium/patches/chromium/0002-Add-Carbonyl-service.patch +++ b/chromium/patches/chromium/0002-Add-Carbonyl-service.patch @@ -1,7 +1,7 @@ From 795b29828fd7ac95548c4dcab483cbc3b6c1d361 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Thu, 9 Feb 2023 03:21:59 +0100 -Subject: [PATCH 2/9] Add Carbonyl service +Subject: [PATCH 02/11] Add Carbonyl service --- cc/trees/layer_tree_host.cc | 21 ++ diff --git a/chromium/patches/chromium/0003-Setup-shared-software-rendering-surface.patch b/chromium/patches/chromium/0003-Setup-shared-software-rendering-surface.patch index e2ea9ed..e5253af 100644 --- a/chromium/patches/chromium/0003-Setup-shared-software-rendering-surface.patch +++ b/chromium/patches/chromium/0003-Setup-shared-software-rendering-surface.patch @@ -1,7 +1,7 @@ From eea9662f4ba08a655057143d320e4e692fd92469 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Thu, 9 Feb 2023 03:24:29 +0100 -Subject: [PATCH 3/9] Setup shared software rendering surface +Subject: [PATCH 03/11] Setup shared software rendering surface --- components/viz/host/host_display_client.cc | 4 +++- diff --git a/chromium/patches/chromium/0004-Setup-browser-default-settings.patch b/chromium/patches/chromium/0004-Setup-browser-default-settings.patch index 0c5deeb..9eda88b 100644 --- a/chromium/patches/chromium/0004-Setup-browser-default-settings.patch +++ b/chromium/patches/chromium/0004-Setup-browser-default-settings.patch @@ -1,7 +1,7 @@ From c960c9b1f7ef3f16b27e4eaa4896e3563c88ea91 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Thu, 9 Feb 2023 03:27:27 +0100 -Subject: [PATCH 4/9] Setup browser default settings +Subject: [PATCH 04/11] Setup browser default settings --- headless/public/headless_browser.cc | 4 ++-- diff --git a/chromium/patches/chromium/0005-Remove-some-debug-assertions.patch b/chromium/patches/chromium/0005-Remove-some-debug-assertions.patch index e7e6c01..fc7bffd 100644 --- a/chromium/patches/chromium/0005-Remove-some-debug-assertions.patch +++ b/chromium/patches/chromium/0005-Remove-some-debug-assertions.patch @@ -1,7 +1,7 @@ From 481ff19118891fe65e80b8be0e1f4498874d3b56 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Thu, 9 Feb 2023 03:28:35 +0100 -Subject: [PATCH 5/9] Remove some debug assertions +Subject: [PATCH 05/11] Remove some debug assertions --- .../browser/web_contents/web_contents_impl.cc | 1 - diff --git a/chromium/patches/chromium/0006-Setup-display-DPI.patch b/chromium/patches/chromium/0006-Setup-display-DPI.patch index c01f24e..76fd549 100644 --- a/chromium/patches/chromium/0006-Setup-display-DPI.patch +++ b/chromium/patches/chromium/0006-Setup-display-DPI.patch @@ -1,19 +1,28 @@ -From 5693445bf736c51d3255c10115767ba8886d68da Mon Sep 17 00:00:00 2001 +From cc9c37adb3ad2613a114bd37e1fde43f83951d88 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj -Date: Thu, 9 Feb 2023 03:29:34 +0100 -Subject: [PATCH 6/9] Setup display DPI +Date: Sun, 12 Feb 2023 01:00:43 +0100 +Subject: [PATCH 06/11] Setup display DPI --- - .../lib/browser/headless_browser_impl_aura.cc | 9 +--- - headless/lib/browser/headless_screen.cc | 2 +- - ui/display/display.cc | 50 +++++++++---------- - 3 files changed, 28 insertions(+), 33 deletions(-) + .../lib/browser/headless_browser_impl_aura.cc | 11 ++-- + headless/lib/browser/headless_screen.cc | 5 +- + ui/display/display.cc | 52 ++++++++++--------- + 3 files changed, 35 insertions(+), 33 deletions(-) diff --git a/headless/lib/browser/headless_browser_impl_aura.cc b/headless/lib/browser/headless_browser_impl_aura.cc -index 81261215c702f..b8eb5fc429a2a 100644 +index 81261215c702f..508660db32151 100644 --- a/headless/lib/browser/headless_browser_impl_aura.cc +++ b/headless/lib/browser/headless_browser_impl_aura.cc -@@ -57,13 +57,8 @@ void HeadlessBrowserImpl::PlatformSetWebContentsBounds( +@@ -19,6 +19,8 @@ + #include "ui/events/devices/device_data_manager.h" + #include "ui/gfx/geometry/rect.h" + ++#include "carbonyl/src/browser/bridge.h" ++ + namespace headless { + + void HeadlessBrowserImpl::PlatformInitialize() { +@@ -57,13 +59,8 @@ void HeadlessBrowserImpl::PlatformSetWebContentsBounds( const gfx::Rect& bounds) { // Browser's window bounds should contain all web contents, so that we're sure // that we will actually produce visible damage when taking a screenshot. @@ -24,29 +33,48 @@ index 81261215c702f..b8eb5fc429a2a 100644 - std::max(old_host_bounds.height(), bounds.y() + bounds.height())); - web_contents->window_tree_host()->SetBoundsInPixels(new_host_bounds); - web_contents->window_tree_host()->window()->SetBounds(new_host_bounds); -+ web_contents->window_tree_host()->SetBoundsInPixels(ScaleToEnclosedRect(bounds, 1.0 / 7.0)); ++ web_contents->window_tree_host()->SetBoundsInPixels(ScaleToEnclosedRect(bounds, carbonyl::Renderer::GetDPI())); + web_contents->window_tree_host()->window()->SetBounds(bounds); gfx::NativeView native_view = web_contents->web_contents()->GetNativeView(); native_view->SetBounds(bounds); diff --git a/headless/lib/browser/headless_screen.cc b/headless/lib/browser/headless_screen.cc -index 28f1a65f6dce5..e595735be187b 100644 +index 28f1a65f6dce5..8bf00ef5e036a 100644 --- a/headless/lib/browser/headless_screen.cc +++ b/headless/lib/browser/headless_screen.cc -@@ -49,7 +49,7 @@ display::Display HeadlessScreen::GetDisplayNearestWindow( +@@ -13,6 +13,8 @@ + #include "ui/gfx/geometry/size_conversions.h" + #include "ui/gfx/native_widget_types.h" + ++#include "carbonyl/src/browser/bridge.h" ++ + namespace headless { + + // static +@@ -49,7 +51,8 @@ display::Display HeadlessScreen::GetDisplayNearestWindow( HeadlessScreen::HeadlessScreen(const gfx::Rect& screen_bounds) { static int64_t synthesized_display_id = 2000; display::Display display(synthesized_display_id++); - display.SetScaleAndBounds(1.0f, screen_bounds); -+ display.SetScaleAndBounds(1.0f / 7.0, ScaleToEnclosedRect(screen_bounds, 1.0 / 7.0)); ++ float dpi = carbonyl::Renderer::GetDPI(); ++ display.SetScaleAndBounds(dpi, ScaleToEnclosedRect(screen_bounds, dpi)); ProcessDisplayChanged(display, true /* is_primary */); } diff --git a/ui/display/display.cc b/ui/display/display.cc -index 466ef1fd1fe6e..cb503dc5cda0f 100644 +index 466ef1fd1fe6e..1d71f3b4c9857 100644 --- a/ui/display/display.cc +++ b/ui/display/display.cc -@@ -39,22 +39,22 @@ float g_forced_device_scale_factor = -1.0; +@@ -21,6 +21,8 @@ + #include "ui/gfx/geometry/transform.h" + #include "ui/gfx/icc_profile.h" + ++#include "carbonyl/src/browser/bridge.h" ++ + namespace display { + namespace { + +@@ -39,22 +41,22 @@ float g_forced_device_scale_factor = -1.0; constexpr float kDisplaySizeAllowanceEpsilon = 0.01f; bool HasForceDeviceScaleFactorImpl() { @@ -78,11 +106,11 @@ index 466ef1fd1fe6e..cb503dc5cda0f 100644 + // scale_in_double = 1.0; + // } + // } -+ return 1.0 / 7.0; ++ return carbonyl::Bridge::GetCurrent()->GetDPI(); } const char* ToRotationString(display::Display::Rotation rotation) { -@@ -97,11 +97,11 @@ void Display::ResetForceDeviceScaleFactorForTesting() { +@@ -97,11 +99,11 @@ void Display::ResetForceDeviceScaleFactorForTesting() { // static void Display::SetForceDeviceScaleFactor(double dsf) { // Reset any previously set values and unset the flag. @@ -98,7 +126,7 @@ index 466ef1fd1fe6e..cb503dc5cda0f 100644 } // static -@@ -273,15 +273,15 @@ void Display::SetScaleAndBounds(float device_scale_factor, +@@ -273,15 +275,15 @@ void Display::SetScaleAndBounds(float device_scale_factor, } void Display::SetScale(float device_scale_factor) { diff --git a/chromium/patches/chromium/0007-Disable-text-effects.patch b/chromium/patches/chromium/0007-Disable-text-effects.patch index a54ddfe..b8bd9c2 100644 --- a/chromium/patches/chromium/0007-Disable-text-effects.patch +++ b/chromium/patches/chromium/0007-Disable-text-effects.patch @@ -1,7 +1,7 @@ -From 5557ea65c9769fd0f99bd1f8eb58b05634f9faa2 Mon Sep 17 00:00:00 2001 +From 4f9316128dc474b9b52d3023e1181432231106ec Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Thu, 9 Feb 2023 03:31:17 +0100 -Subject: [PATCH 7/9] Disable text effects +Subject: [PATCH 07/11] Disable text effects --- .../core/paint/ng/ng_text_painter_base.cc | 30 ++++++++-------- diff --git a/chromium/patches/chromium/0008-Fix-text-layout.patch b/chromium/patches/chromium/0008-Fix-text-layout.patch index c2817f8..34dff88 100644 --- a/chromium/patches/chromium/0008-Fix-text-layout.patch +++ b/chromium/patches/chromium/0008-Fix-text-layout.patch @@ -1,7 +1,7 @@ -From 1820cd34967c756d2459f4a2894492662edbf58b Mon Sep 17 00:00:00 2001 +From b858ee163b525247e3fb7fdb2449b75c2df055fa Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Thu, 9 Feb 2023 03:32:14 +0100 -Subject: [PATCH 8/9] Fix text layout +Subject: [PATCH 08/11] Fix text layout --- .../core/css/resolver/style_resolver.cc | 17 ++++++++++++++++- diff --git a/chromium/patches/chromium/0009-Bridge-browser-into-Carbonyl-library.patch b/chromium/patches/chromium/0009-Bridge-browser-into-Carbonyl-library.patch index 10ad73d..b178295 100644 --- a/chromium/patches/chromium/0009-Bridge-browser-into-Carbonyl-library.patch +++ b/chromium/patches/chromium/0009-Bridge-browser-into-Carbonyl-library.patch @@ -1,7 +1,7 @@ -From 0697c3ab8f52592b0a0d4241e2938b8e622a01f6 Mon Sep 17 00:00:00 2001 +From 5ced1635901a8fa649f9a534e82babb5a956d920 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Thu, 9 Feb 2023 03:32:30 +0100 -Subject: [PATCH 9/9] Bridge browser into Carbonyl library +Subject: [PATCH 09/11] Bridge browser into Carbonyl library --- headless/app/headless_shell.cc | 33 +- diff --git a/chromium/patches/chromium/0010-Conditionally-enable-text-rendering.patch b/chromium/patches/chromium/0010-Conditionally-enable-text-rendering.patch new file mode 100644 index 0000000..5c3e20c --- /dev/null +++ b/chromium/patches/chromium/0010-Conditionally-enable-text-rendering.patch @@ -0,0 +1,140 @@ +From 987c9c05601339bfff2bab0ddcfc7a04208b6c93 Mon Sep 17 00:00:00 2001 +From: Fathy Boundjadj +Date: Sun, 12 Feb 2023 00:55:33 +0100 +Subject: [PATCH 10/11] Conditionally enable text rendering + +--- + content/renderer/render_frame_impl.cc | 3 ++- + .../core/css/resolver/style_resolver.cc | 26 +++++++++++-------- + third_party/blink/renderer/platform/BUILD.gn | 1 + + .../blink/renderer/platform/fonts/font.cc | 10 +++---- + 4 files changed, 23 insertions(+), 17 deletions(-) + +diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc +index 97b61ffb954be..891efd6a9d796 100644 +--- a/content/renderer/render_frame_impl.cc ++++ b/content/renderer/render_frame_impl.cc +@@ -259,6 +259,7 @@ + // Carbonyl + #include + #include ++#include "carbonyl/src/browser/bridge.h" + #include "cc/paint/paint_recorder.h" + #include "cc/paint/skia_paint_canvas.h" + #include "cc/raster/playback_image_provider.h" +@@ -2221,7 +2222,7 @@ void RenderFrameImpl::Initialize(blink::WebFrame* parent) { + + render_callback_ = std::make_shared>( + [=]() -> bool { +- if (!IsMainFrame() || IsHidden()) { ++ if (!IsMainFrame() || IsHidden() || carbonyl::Bridge::BitmapMode()) { + return false; + } + +diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver.cc b/third_party/blink/renderer/core/css/resolver/style_resolver.cc +index 79cb8c85b697f..7129982acf4a6 100644 +--- a/third_party/blink/renderer/core/css/resolver/style_resolver.cc ++++ b/third_party/blink/renderer/core/css/resolver/style_resolver.cc +@@ -116,6 +116,8 @@ + #include "third_party/blink/renderer/platform/wtf/text/atomic_string_hash.h" + #include "third_party/blink/renderer/platform/wtf/text/string_builder.h" + ++#include "carbonyl/src/browser/bridge.h" ++ + namespace blink { + + namespace { +@@ -1041,18 +1043,20 @@ scoped_refptr StyleResolver::ResolveStyle( + UseCounter::Count(GetDocument(), WebFeature::kHasGlyphRelativeUnits); + } + +- auto font = state.StyleBuilder().GetFontDescription(); +- FontFamily family; ++ if (!carbonyl::Bridge::BitmapMode()) { ++ auto font = state.StyleBuilder().GetFontDescription(); ++ FontFamily family; + +- family.SetFamily("monospace", FontFamily::Type::kGenericFamily); +- font.SetFamily(family); +- font.SetStretch(ExtraExpandedWidthValue()); +- font.SetKerning(FontDescription::kNoneKerning); +- font.SetComputedSize(11.75 / 7.0); +- font.SetGenericFamily(FontDescription::kMonospaceFamily); +- font.SetIsAbsoluteSize(true); +- state.StyleBuilder().SetFontDescription(font); +- state.StyleBuilder().SetLineHeight(Length::Fixed(14.0 / 7.0)); ++ family.SetFamily("monospace", FontFamily::Type::kGenericFamily); ++ font.SetFamily(family); ++ font.SetStretch(ExtraExpandedWidthValue()); ++ font.SetKerning(FontDescription::kNoneKerning); ++ font.SetComputedSize(13.25 / 4.0); ++ font.SetGenericFamily(FontDescription::kMonospaceFamily); ++ font.SetIsAbsoluteSize(true); ++ state.StyleBuilder().SetFontDescription(font); ++ state.StyleBuilder().SetLineHeight(Length::Fixed(16.0 / 4.0)); ++ } + + state.LoadPendingResources(); + +diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn +index e7b1c1a52e4c9..63fc13e44b5ae 100644 +--- a/third_party/blink/renderer/platform/BUILD.gn ++++ b/third_party/blink/renderer/platform/BUILD.gn +@@ -1678,6 +1678,7 @@ component("platform") { + "//base/allocator:buildflags", + "//build:chromecast_buildflags", + "//build:chromeos_buildflags", ++ "//carbonyl/src/browser:carbonyl", + "//cc/ipc", + "//cc/mojo_embedder", + "//components/paint_preview/common", +diff --git a/third_party/blink/renderer/platform/fonts/font.cc b/third_party/blink/renderer/platform/fonts/font.cc +index dfdc79eacce3b..4625300729523 100644 +--- a/third_party/blink/renderer/platform/fonts/font.cc ++++ b/third_party/blink/renderer/platform/fonts/font.cc +@@ -49,6 +49,8 @@ + #include "third_party/skia/include/core/SkTextBlob.h" + #include "ui/gfx/geometry/rect_f.h" + ++#include "carbonyl/src/browser/bridge.h" ++ + namespace blink { + + namespace { +@@ -151,14 +153,12 @@ bool Font::operator==(const Font& other) const { + + namespace { + +-static const bool carbonyl_b64_text = true; +- + void DrawBlobs(cc::PaintCanvas* canvas, + const cc::PaintFlags& flags, + const ShapeResultBloberizer::BlobBuffer& blobs, + const gfx::PointF& point, + cc::NodeId node_id = cc::kInvalidNodeId) { +- if (carbonyl_b64_text) { ++ if (!carbonyl::Bridge::BitmapMode()) { + return; + } + +@@ -237,7 +237,7 @@ void Font::DrawText(cc::PaintCanvas* canvas, + if (ShouldSkipDrawing()) + return; + +- if (carbonyl_b64_text) { ++ if (!carbonyl::Bridge::BitmapMode()) { + auto string = StringView( + run_info.run.ToStringView(), + run_info.from, +@@ -285,7 +285,7 @@ void Font::DrawText(cc::PaintCanvas* canvas, + if (ShouldSkipDrawing()) + return; + +- if (carbonyl_b64_text) { ++ if (!carbonyl::Bridge::BitmapMode()) { + auto string = StringView( + text_info.text, + text_info.from, +-- +2.38.1 + diff --git a/chromium/patches/chromium/0011-Rename-carbonyl-Renderer-to-carbonyl-Bridge.patch b/chromium/patches/chromium/0011-Rename-carbonyl-Renderer-to-carbonyl-Bridge.patch new file mode 100644 index 0000000..9cc5e90 --- /dev/null +++ b/chromium/patches/chromium/0011-Rename-carbonyl-Renderer-to-carbonyl-Bridge.patch @@ -0,0 +1,113 @@ +From b3eb30e840840f3eb60f1a375f4c93fc08587511 Mon Sep 17 00:00:00 2001 +From: Fathy Boundjadj +Date: Sun, 12 Feb 2023 00:56:13 +0100 +Subject: [PATCH 11/11] Rename carbonyl::Renderer to carbonyl::Bridge + +--- + headless/app/headless_shell.cc | 5 ++++- + headless/app/headless_shell_main.cc | 2 +- + headless/lib/browser/headless_browser_impl.cc | 8 ++++---- + headless/lib/browser/headless_web_contents_impl.cc | 4 ++-- + 4 files changed, 11 insertions(+), 8 deletions(-) + +diff --git a/headless/app/headless_shell.cc b/headless/app/headless_shell.cc +index 5b51c22ae1da3..b6a52857e8f90 100644 +--- a/headless/app/headless_shell.cc ++++ b/headless/app/headless_shell.cc +@@ -12,6 +12,7 @@ + #include "base/bind.h" + #include "base/command_line.h" + #include "base/files/file_util.h" ++#include "base/functional/callback.h" + #include "base/i18n/rtl.h" + #include "base/task/thread_pool.h" + #include "build/branding_buildflags.h" +@@ -92,7 +93,9 @@ void HeadlessShell::OnBrowserStart(HeadlessBrowser* browser) { + HeadlessBrowserContext::Builder context_builder = + browser_->CreateBrowserContextBuilder(); + +- context_builder.SetWindowSize(carbonyl::Renderer::Main()->GetSize()); ++ carbonyl::Bridge::GetCurrent()->StartRenderer(); ++ ++ context_builder.SetWindowSize(carbonyl::Bridge::GetCurrent()->GetSize()); + + // Retrieve the locale set by InitApplicationLocale() in + // headless_content_main_delegate.cc in a way that is free of side-effects. +diff --git a/headless/app/headless_shell_main.cc b/headless/app/headless_shell_main.cc +index f9b8bac5c18a5..739df1ae1bd58 100644 +--- a/headless/app/headless_shell_main.cc ++++ b/headless/app/headless_shell_main.cc +@@ -17,7 +17,7 @@ + #include "carbonyl/src/browser/bridge.h" + + int main(int argc, const char** argv) { +- carbonyl_shell_main(); ++ carbonyl::Bridge::Main(); + + #if BUILDFLAG(IS_WIN) + sandbox::SandboxInterfaceInfo sandbox_info = {nullptr}; +diff --git a/headless/lib/browser/headless_browser_impl.cc b/headless/lib/browser/headless_browser_impl.cc +index fd45d215479ab..1df3ffe72c93d 100644 +--- a/headless/lib/browser/headless_browser_impl.cc ++++ b/headless/lib/browser/headless_browser_impl.cc +@@ -119,7 +119,7 @@ void HeadlessBrowserImpl::set_browser_main_parts( + } + + void HeadlessBrowserImpl::Resize() { +- auto size = carbonyl::Renderer::GetSize(); ++ auto size = carbonyl::Bridge::GetCurrent()->Resize(); + auto rect = gfx::Rect(0, 0, size.width(), size.height()); + + for (auto* ctx: GetAllBrowserContexts()) { +@@ -134,7 +134,7 @@ void HeadlessBrowserImpl::Resize() { + } + } + +- carbonyl::Renderer::Main()->Resize(); ++ carbonyl::Bridge::GetCurrent()->Resize(); + } + + void HeadlessBrowserImpl::OnShutdownInput() { +@@ -279,7 +279,7 @@ void HeadlessBrowserImpl::OnKeyPressInput(char key) { + blink::WebKeyboardEvent::Type::kRawKeyDown, + blink::WebInputEvent::kNoModifiers, + base::TimeTicks::Now()); +- ++ + // TODO(fathy): support IME + switch (key) { + case 0x11: +@@ -500,7 +500,7 @@ void HeadlessBrowserImpl::RunOnStartCallback() { + } + }; + +- carbonyl::Renderer::Main()->Listen(&delegate); ++ carbonyl::Bridge::GetCurrent()->Listen(&delegate); + }); + } + +diff --git a/headless/lib/browser/headless_web_contents_impl.cc b/headless/lib/browser/headless_web_contents_impl.cc +index fad8c3fdd2bfe..a166a08f6ea15 100644 +--- a/headless/lib/browser/headless_web_contents_impl.cc ++++ b/headless/lib/browser/headless_web_contents_impl.cc +@@ -400,7 +400,7 @@ void HeadlessWebContentsImpl::TitleWasSet(content::NavigationEntry* entry) { + if (!web_contents() || !web_contents()->GetPrimaryMainFrame()->IsActive()) + return; + +- carbonyl::Renderer::Main()->SetTitle(base::UTF16ToUTF8(entry->GetTitleForDisplay())); ++ carbonyl::Bridge::GetCurrent()->SetTitle(base::UTF16ToUTF8(entry->GetTitleForDisplay())); + } + + void HeadlessWebContentsImpl::DidFinishNavigation(content::NavigationHandle* handle) { +@@ -411,7 +411,7 @@ void HeadlessWebContentsImpl::DidFinishNavigation(content::NavigationHandle* han + + auto& nav = web_contents()->GetController(); + +- carbonyl::Renderer::Main()->PushNav( ++ carbonyl::Bridge::GetCurrent()->PushNav( + handle->GetURL().spec(), + nav.CanGoBack(), + nav.CanGoForward() +-- +2.38.1 + diff --git a/scripts/build.sh b/scripts/build.sh index 8b0f108..dbba892 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -14,9 +14,12 @@ fi cargo build --target "$triple" --release if [ -f "build/$triple/release/libcarbonyl.dylib" ]; then + cp "build/$triple/release/libcarbonyl.dylib" "$CHROMIUM_SRC/out/$1" install_name_tool \ -id @executable_path/libcarbonyl.dylib \ "build/$triple/release/libcarbonyl.dylib" +else + cp "build/$triple/release/libcarbonyl.so" "$CHROMIUM_SRC/out/$1" fi cd "$CHROMIUM_SRC/out/$1" diff --git a/src/browser/bridge.cc b/src/browser/bridge.cc index 06f8bc0..4674627 100644 --- a/src/browser/bridge.cc +++ b/src/browser/bridge.cc @@ -4,28 +4,60 @@ #include #include +#include "base/functional/callback.h" +#include "ui/gfx/geometry/rect_f.h" #include "third_party/skia/include/core/SkColor.h" +#include "third_party/skia/src/core/SkBitmapDevice.h" extern "C" { -struct carbonyl_renderer* carbonyl_renderer_create(); -void carbonyl_renderer_resize(struct carbonyl_renderer* renderer); -void carbonyl_output_get_size(struct carbonyl_bridge_size* size); -void carbonyl_renderer_push_nav(struct carbonyl_renderer* renderer, const char* url, bool can_go_back, bool can_go_forward); -void carbonyl_renderer_set_title(struct carbonyl_renderer* renderer, const char* title); -void carbonyl_renderer_clear_text(struct carbonyl_renderer* renderer); -void carbonyl_input_listen(struct carbonyl_renderer* renderer, const struct carbonyl_bridge_browser_delegate* delegate); -void carbonyl_renderer_draw_text( - struct carbonyl_renderer* renderer, - const char* title, - const struct carbonyl_bridge_rect* rect, - const struct carbonyl_bridge_color* color +struct carbonyl_bridge_size { + unsigned int width; + unsigned int height; +}; +struct carbonyl_bridge_point { + unsigned int x; + unsigned int y; +}; +struct carbonyl_bridge_rect { + struct carbonyl_bridge_point origin; + struct carbonyl_bridge_size size; +}; +struct carbonyl_bridge_color { + uint8_t r; + uint8_t g; + uint8_t b; +}; +struct carbonyl_bridge_text { + const char* text; + carbonyl_bridge_rect rect; + carbonyl_bridge_color color; +}; + +void carbonyl_shell_main(); +bool carbonyl_shell_bitmap_mode(); + +struct carbonyl_bridge* carbonyl_bridge_create(); +void carbonyl_bridge_start_renderer(struct carbonyl_bridge* bridge); +void carbonyl_bridge_resize(struct carbonyl_bridge* bridge); +float carbonyl_bridge_get_dpi(struct carbonyl_bridge* bridge); +struct carbonyl_bridge_size carbonyl_bridge_get_size(struct carbonyl_bridge* bridge); +void carbonyl_bridge_push_nav(struct carbonyl_bridge* bridge, const char* url, bool can_go_back, bool can_go_forward); +void carbonyl_bridge_set_title(struct carbonyl_bridge* bridge, const char* title); +void carbonyl_bridge_clear_text(struct carbonyl_bridge* bridge); +void carbonyl_bridge_listen(struct carbonyl_bridge* bridge, const struct carbonyl_bridge_browser_delegate* delegate); +void carbonyl_bridge_draw_text( + struct carbonyl_bridge* bridge, + const struct carbonyl_bridge_text* text, + size_t text_size ); -void carbonyl_renderer_draw_background( - struct carbonyl_renderer* renderer, +void carbonyl_bridge_draw_bitmap( + struct carbonyl_bridge* bridge, const unsigned char* pixels, - size_t pixels_size, - const struct carbonyl_bridge_rect* rect + const struct carbonyl_bridge_size size, + const struct carbonyl_bridge_rect rect, + void (*callback) (void*), + void* callback_data ); } @@ -33,82 +65,131 @@ void carbonyl_renderer_draw_background( namespace carbonyl { namespace { - static std::unique_ptr globalInstance; + static std::unique_ptr globalInstance; } -Renderer::Renderer(struct carbonyl_renderer* ptr): ptr_(ptr) {} +Bridge::Bridge(struct carbonyl_bridge* ptr): ptr_(ptr) {} + +void Bridge::Main() { + carbonyl_shell_main(); +} -Renderer* Renderer::Main() { +Bridge* Bridge::GetCurrent() { if (!globalInstance) { - globalInstance = std::unique_ptr( - new Renderer(carbonyl_renderer_create()) + globalInstance = std::unique_ptr( + new Bridge(carbonyl_bridge_create()) ); } return globalInstance.get(); } -gfx::Size Renderer::GetSize() { - struct carbonyl_bridge_size size; +namespace { + thread_local int bitmap_mode = -1; +} + +bool Bridge::BitmapMode() { + if (bitmap_mode == -1) { + bitmap_mode = carbonyl_shell_bitmap_mode(); + + if (!bitmap_mode) { + SkBitmapDevice::DisableTextRendering(); + } + } + + return bitmap_mode; +} + +float Bridge::GetDPI() { + return carbonyl_bridge_get_dpi(ptr_); +} + +void Bridge::StartRenderer() { + carbonyl_bridge_start_renderer(ptr_); +} - carbonyl_output_get_size(&size); +gfx::Size Bridge::GetSize() { + auto size = carbonyl_bridge_get_size(ptr_); return gfx::Size(size.width, size.height); } -void Renderer::Resize() { - carbonyl_renderer_resize(ptr_); +gfx::Size Bridge::Resize() { + carbonyl_bridge_resize(ptr_); + + return GetSize(); } -void Renderer::Listen(const struct carbonyl_bridge_browser_delegate* delegate) { - carbonyl_input_listen(ptr_, delegate); +void Bridge::Listen(const struct carbonyl_bridge_browser_delegate* delegate) { + carbonyl_bridge_listen(ptr_, delegate); } -void Renderer::PushNav(const std::string& url, bool can_go_back, bool can_go_forward) { +void Bridge::PushNav(const std::string& url, bool can_go_back, bool can_go_forward) { if (!url.size()) { return; } - carbonyl_renderer_push_nav(ptr_, url.c_str(), can_go_back, can_go_forward); + carbonyl_bridge_push_nav(ptr_, url.c_str(), can_go_back, can_go_forward); } -void Renderer::SetTitle(const std::string& title) { +void Bridge::SetTitle(const std::string& title) { if (!title.size()) { return; } - carbonyl_renderer_set_title(ptr_, title.c_str()); -} - -void Renderer::ClearText() { - carbonyl_renderer_clear_text(ptr_); + carbonyl_bridge_set_title(ptr_, title.c_str()); } -void Renderer::DrawText(const std::string& text, const gfx::RectF& bounds, uint32_t sk_color) { - struct carbonyl_bridge_rect rect; - struct carbonyl_bridge_color color; - - rect.origin.x = bounds.x(); - rect.origin.y = bounds.y(); - rect.size.width = bounds.width(); - rect.size.height = bounds.height(); - - color.r = SkColorGetR(sk_color); - color.g = SkColorGetG(sk_color); - color.b = SkColorGetB(sk_color); +void Bridge::DrawText(const std::vector& text) { + struct carbonyl_bridge_text data[text.size()]; + + for (size_t i = 0; i < text.size(); i++) { + data[i].text = text[i].text.c_str(); + data[i].color.r = SkColorGetR(text[i].color); + data[i].color.g = SkColorGetG(text[i].color); + data[i].color.b = SkColorGetB(text[i].color); + data[i].rect.origin.x = text[i].rect.x(); + data[i].rect.origin.y = text[i].rect.y(); + data[i].rect.size.width = std::ceil(text[i].rect.width()); + data[i].rect.size.height = std::ceil(text[i].rect.height()); + } - carbonyl_renderer_draw_text(ptr_, text.c_str(), &rect, &color); + carbonyl_bridge_draw_text(ptr_, data, text.size()); } -void Renderer::DrawBackground(const unsigned char* pixels, size_t pixels_size, const gfx::Rect& bounds) { - struct carbonyl_bridge_rect rect; - - rect.origin.x = bounds.x(); - rect.origin.y = bounds.y(); - rect.size.width = bounds.width(); - rect.size.height = bounds.height(); - - carbonyl_renderer_draw_background(ptr_, pixels, pixels_size, &rect); +void Bridge::DrawBitmap( + const unsigned char* pixels, + const gfx::Size& pixels_size, + const gfx::Rect& damage, + base::OnceCallback callback +) { + auto* box = new base::OnceCallback(std::move(callback)); + + carbonyl_bridge_draw_bitmap( + ptr_, + pixels, + { + .width = (unsigned int)pixels_size.width(), + .height = (unsigned int)pixels_size.height(), + }, + { + .origin = { + .x = (unsigned int)damage.x(), + .y = (unsigned int)damage.y(), + }, + .size = { + .width = (unsigned int)damage.width(), + .height = (unsigned int)damage.height(), + }, + }, + [](void* box) { + auto* ptr = static_cast*>(box); + + std::move(*ptr).Run(); + delete ptr; + }, + box + ); } } diff --git a/src/browser/bridge.h b/src/browser/bridge.h index 98a7ed9..d1e9e9e 100644 --- a/src/browser/bridge.h +++ b/src/browser/bridge.h @@ -2,30 +2,14 @@ #define CARBONYL_SRC_BROWSER_BRIDGE_H_ #include +#include +#include "base/functional/callback.h" #include "ui/gfx/geometry/rect_f.h" extern "C" { -struct carbonyl_renderer; - -struct carbonyl_bridge_size { - unsigned int width; - unsigned int height; -}; -struct carbonyl_bridge_point { - unsigned int x; - unsigned int y; -}; -struct carbonyl_bridge_rect { - struct carbonyl_bridge_point origin; - struct carbonyl_bridge_size size; -}; -struct carbonyl_bridge_color { - uint8_t r; - uint8_t g; - uint8_t b; -}; +struct carbonyl_bridge; struct carbonyl_bridge_browser_delegate { void (*shutdown) (); void (*refresh) (); @@ -40,29 +24,52 @@ struct carbonyl_bridge_browser_delegate { void (*post_task) (void (*)(void*), void*); }; -void carbonyl_shell_main(); - } /* end extern "C" */ namespace carbonyl { -class Renderer { +struct Text { + Text( + std::string text, + gfx::RectF rect, + uint32_t color + ): + text(text), + rect(rect), + color(color) + {} + + std::string text; + gfx::RectF rect; + uint32_t color; +}; + +class Bridge { public: - static Renderer* Main(); - static gfx::Size GetSize(); + static void Main(); + static Bridge* GetCurrent(); + static bool BitmapMode(); + + gfx::Size GetSize(); + float GetDPI(); - void Resize(); + gfx::Size Resize(); + void StartRenderer(); void Listen(const struct carbonyl_bridge_browser_delegate* delegate); void PushNav(const std::string& url, bool can_go_back, bool can_go_forward); void SetTitle(const std::string& title); - void ClearText(); - void DrawText(const std::string& text, const gfx::RectF& bounds, uint32_t color); - void DrawBackground(const unsigned char* pixels, size_t pixels_size, const gfx::Rect& bounds); + void DrawText(const std::vector& text); + void DrawBitmap( + const unsigned char* pixels, + const gfx::Size& size, + const gfx::Rect& damage, + base::OnceCallback callback + ); private: - Renderer(struct carbonyl_renderer* ptr); + Bridge(struct carbonyl_bridge* ptr); - struct carbonyl_renderer* ptr_; + struct carbonyl_bridge* ptr_; }; } diff --git a/src/browser/bridge.rs b/src/browser/bridge.rs index 5d49286..2fce0f3 100644 --- a/src/browser/bridge.rs +++ b/src/browser/bridge.rs @@ -1,57 +1,94 @@ use std::ffi::{CStr, CString}; use std::io::Write; use std::process::{Command, Stdio}; -use std::sync::mpsc; +use std::sync::{mpsc, Arc, Mutex}; use std::{env, io}; -use libc::{c_char, c_int, c_uchar, c_uint, c_void, size_t}; +use libc::{c_char, c_float, c_int, c_uchar, c_uint, c_void, size_t}; -use crate::cli; +use crate::cli::{CommandLine, CommandLineProgram, EnvVar}; use crate::gfx::{Cast, Color, Point, Rect, Size}; -use crate::output::Renderer; +use crate::output::{RenderThread, Window}; use crate::ui::navigation::NavigationAction; -use crate::{input, output, utils::log}; +use crate::{input, utils::log}; #[repr(C)] +#[derive(Copy, Clone)] pub struct CSize { width: c_uint, height: c_uint, } #[repr(C)] +#[derive(Copy, Clone)] pub struct CPoint { x: c_uint, y: c_uint, } #[repr(C)] +#[derive(Copy, Clone)] pub struct CRect { origin: CPoint, size: CSize, } #[repr(C)] +#[derive(Copy, Clone)] pub struct CColor { r: u8, g: u8, b: u8, } +#[repr(C)] +#[derive(Copy, Clone)] +pub struct CText { + text: *const c_char, + rect: CRect, + color: CColor, +} + +#[repr(C)] +pub struct Bridge { + cmd: CommandLine, + thread: RenderThread, + window: Window, +} + +unsafe impl Send for Bridge {} +unsafe impl Sync for Bridge {} + +pub type BridgePointer = *const Mutex; -impl From<&CPoint> for Point +impl From for Point where c_uint: Cast, { - fn from(value: &CPoint) -> Self { + fn from(value: CPoint) -> Self { Point::new(value.x, value.y).cast() } } -impl From<&CSize> for Size +impl From> for CSize { + fn from(value: Size) -> Self { + Self { + width: value.width, + height: value.height, + } + } +} +impl From for Size where c_uint: Cast, { - fn from(value: &CSize) -> Self { + fn from(value: CSize) -> Self { Size::new(value.width, value.height).cast() } } +impl From for Color { + fn from(value: CColor) -> Self { + Color::new(value.r, value.g, value.b) + } +} #[repr(C)] +#[derive(Copy, Clone)] pub struct BrowserDelegate { shutdown: extern "C" fn(), refresh: extern "C" fn(), @@ -67,23 +104,21 @@ pub struct BrowserDelegate { } fn main() -> io::Result> { - const CARBONYL_INSIDE_SHELL: &str = "CARBONYL_INSIDE_SHELL"; - - if env::vars().find(|(key, value)| key == CARBONYL_INSIDE_SHELL && value == "1") != None { - return Ok(None); - } - - let cmd = match cli::main() { + let cmd = match CommandLineProgram::parse_or_run() { None => return Ok(Some(0)), Some(cmd) => cmd, }; + if cmd.shell_mode { + return Ok(None); + } + let mut terminal = input::Terminal::setup(); let output = Command::new(env::current_exe()?) .args(cmd.args) .arg("--disable-threaded-scrolling") .arg("--disable-threaded-animation") - .env(CARBONYL_INSIDE_SHELL, "1") + .env(EnvVar::ShellMode, "1") .stdin(Stdio::inherit()) .stdout(Stdio::inherit()) .stderr(Stdio::piped()) @@ -108,119 +143,170 @@ pub extern "C" fn carbonyl_shell_main() { } #[no_mangle] -pub extern "C" fn carbonyl_renderer_create() -> *mut Renderer { - let mut renderer = Box::new(Renderer::new()); - let src = output::size().unwrap(); - - log::debug!("creating renderer, terminal size: {:?}", src); +pub extern "C" fn carbonyl_shell_bitmap_mode() -> bool { + CommandLine::parse().bitmap +} - renderer.set_size(Size::new(7, 14), src); +#[no_mangle] +pub extern "C" fn carbonyl_bridge_create() -> BridgePointer { + let bridge = Bridge { + cmd: CommandLine::parse(), + thread: RenderThread::new(), + window: Window::read().unwrap(), + }; - Box::into_raw(renderer) + Arc::into_raw(Arc::new(Mutex::new(bridge))) } #[no_mangle] -pub extern "C" fn carbonyl_renderer_resize(renderer: *mut Renderer) { - let renderer = unsafe { &mut *renderer }; - let src = output::size().unwrap(); +pub extern "C" fn carbonyl_bridge_start_renderer(bridge: BridgePointer) { + { + let bridge = unsafe { bridge.as_ref() }; + let mut bridge = bridge.unwrap().lock().unwrap(); - log::debug!("resizing renderer, terminal size: {:?}", src); + bridge.thread.enable(); + } - renderer.set_size(Size::new(7, 14), src); + carbonyl_bridge_resize(bridge); } #[no_mangle] -pub extern "C" fn carbonyl_renderer_clear_text(renderer: *mut Renderer) { - let renderer = unsafe { &mut *renderer }; +pub extern "C" fn carbonyl_bridge_resize(bridge: BridgePointer) { + let bridge = unsafe { bridge.as_ref() }; + let mut bridge = bridge.unwrap().lock().unwrap(); + let window = bridge.window.update().unwrap(); + let cells = window.cells.clone(); + + log::debug!("resizing renderer, terminal window: {:?}", window); - renderer.clear_text() + bridge.thread.run(move |renderer| renderer.set_size(cells)); } #[no_mangle] -pub extern "C" fn carbonyl_renderer_push_nav( - renderer: *mut Renderer, +pub extern "C" fn carbonyl_bridge_push_nav( + bridge: BridgePointer, url: *const c_char, can_go_back: bool, can_go_forward: bool, ) { - let (renderer, url) = unsafe { (&mut *renderer, CStr::from_ptr(url)) }; + let (bridge, url) = unsafe { (bridge.as_ref(), CStr::from_ptr(url)) }; + let (mut bridge, url) = (bridge.unwrap().lock().unwrap(), url.to_owned()); - renderer.push_nav(url.to_str().unwrap(), can_go_back, can_go_forward) + bridge + .thread + .run(move |renderer| renderer.push_nav(url.to_str().unwrap(), can_go_back, can_go_forward)); } #[no_mangle] -pub extern "C" fn carbonyl_renderer_set_title(renderer: *mut Renderer, title: *const c_char) { - let (renderer, title) = unsafe { (&mut *renderer, CStr::from_ptr(title)) }; +pub extern "C" fn carbonyl_bridge_set_title(bridge: BridgePointer, title: *const c_char) { + let (bridge, title) = unsafe { (bridge.as_ref(), CStr::from_ptr(title)) }; + let (mut bridge, title) = (bridge.unwrap().lock().unwrap(), title.to_owned()); - renderer.set_title(title.to_str().unwrap()).unwrap() + bridge + .thread + .run(move |renderer| renderer.set_title(title.to_str().unwrap()).unwrap()); } #[no_mangle] -pub extern "C" fn carbonyl_renderer_draw_text( - renderer: *mut Renderer, - text: *const c_char, - rect: *const CRect, - color: *const CColor, +pub extern "C" fn carbonyl_bridge_draw_text( + bridge: BridgePointer, + text: *const CText, + text_size: size_t, ) { - let (renderer, text, rect, color) = - unsafe { (&mut *renderer, CStr::from_ptr(text), &*rect, &*color) }; - - renderer.draw_text( - text.to_str().unwrap(), - Point::from(&rect.origin), - Size::from(&rect.size), - Color::new(color.r, color.g, color.b), - ) + let (bridge, text) = unsafe { (bridge.as_ref(), std::slice::from_raw_parts(text, text_size)) }; + let mut bridge = bridge.unwrap().lock().unwrap(); + let mut vec = text + .iter() + .map(|text| { + let str = unsafe { CStr::from_ptr(text.text) }; + + ( + str.to_str().unwrap().to_owned(), + text.rect.origin.into(), + text.rect.size.into(), + text.color.into(), + ) + }) + .collect::>(); + + bridge.thread.run(move |renderer| { + renderer.clear_text(); + + for (text, origin, size, color) in std::mem::take(&mut vec) { + renderer.draw_text(&text, origin, size, color) + } + }); } +#[derive(Clone, Copy)] +struct CallbackData(*const c_void); + +impl CallbackData { + pub fn as_ptr(&self) -> *const c_void { + self.0 + } +} + +unsafe impl Send for CallbackData {} +unsafe impl Sync for CallbackData {} + #[no_mangle] -pub extern "C" fn carbonyl_renderer_draw_background( - renderer: *mut Renderer, - pixels: *mut c_uchar, - pixels_size: size_t, - rect: *const CRect, +pub extern "C" fn carbonyl_bridge_draw_bitmap( + bridge: BridgePointer, + pixels: *const c_uchar, + pixels_size: CSize, + rect: CRect, + callback: extern "C" fn(*const c_void), + callback_data: *const c_void, ) { - let (renderer, pixels, rect) = unsafe { - ( - &mut *renderer, - std::slice::from_raw_parts_mut(pixels, pixels_size), - &*rect, - ) - }; + let length = (pixels_size.width * pixels_size.height * 4) as usize; + let (bridge, pixels) = unsafe { (bridge.as_ref(), std::slice::from_raw_parts(pixels, length)) }; + let callback_data = CallbackData(callback_data); + let mut bridge = bridge.unwrap().lock().unwrap(); - renderer - .draw_background( + bridge.thread.run(move |renderer| { + renderer.draw_background( pixels, + pixels_size.into(), Rect { - origin: Point::from(&rect.origin), - size: Size::from(&rect.size), + size: rect.size.into(), + origin: rect.origin.into(), }, - ) - .unwrap() + ); + + callback(callback_data.as_ptr()); + }); } #[no_mangle] -pub extern "C" fn carbonyl_output_get_size(size: *mut CSize) { - let dst = unsafe { &mut *size }; - let src = output::size().unwrap().cast::(); +pub extern "C" fn carbonyl_bridge_get_dpi(bridge: BridgePointer) -> c_float { + let bridge = unsafe { bridge.as_ref() }; + let bridge = bridge.unwrap().lock().unwrap(); - log::debug!("terminal size: {:?}", src); + bridge.window.dpi +} - dst.width = src.width * 7; - dst.height = src.height * 14; +#[no_mangle] +pub extern "C" fn carbonyl_bridge_get_size(bridge: BridgePointer) -> CSize { + let bridge = unsafe { bridge.as_ref() }; + let bridge = bridge.unwrap().lock().unwrap(); + + log::debug!("terminal size: {:?}", bridge.window.browser); + + bridge.window.browser.into() } extern "C" fn post_task_handler(callback: *mut c_void) { - let mut closure = unsafe { Box::from_raw(callback as *mut Box io::Result<()>>) }; + let mut closure = unsafe { Box::from_raw(callback as *mut Box) }; - closure().unwrap(); + closure() } -fn post_task(handle: &extern "C" fn(extern "C" fn(*mut c_void), *mut c_void), run: F) +unsafe fn post_task(handle: extern "C" fn(extern "C" fn(*mut c_void), *mut c_void), run: F) where - F: FnMut() -> io::Result<()>, + F: FnMut() + Send + 'static, { - let closure: *mut Box io::Result<()>> = Box::into_raw(Box::new(Box::new(run))); + let closure: *mut Box = Box::into_raw(Box::new(Box::new(run))); handle(post_task_handler, closure as *mut c_void); } @@ -230,100 +316,111 @@ where /// This will block so the calling code should start and own a dedicated thread. /// It will panic if there is any error. #[no_mangle] -pub extern "C" fn carbonyl_input_listen(renderer: *mut Renderer, delegate: *mut BrowserDelegate) { - let char_width = 7; - let char_height = 14; - let BrowserDelegate { - shutdown, - refresh, - go_to, - go_back, - go_forward, - scroll, - key_press, - mouse_up, - mouse_down, - mouse_move, - post_task: handle, - } = unsafe { &*delegate }; - let dispatch = |action: NavigationAction| { - use NavigationAction::*; - - match action { - Ignore => (), - Forward => return true, - GoBack() => go_back(), - GoForward() => go_forward(), - Refresh() => refresh(), - GoTo(url) => { - let c_str = CString::new(url).unwrap(); - - go_to(c_str.as_ptr()) - } - } - - false - }; +pub extern "C" fn carbonyl_bridge_listen(bridge: BridgePointer, delegate: *mut BrowserDelegate) { + let bridge = unsafe { &*bridge }; + let delegate = unsafe { *delegate }; use input::*; - listen(|event| { - post_task(handle, move || { - let renderer = unsafe { &mut *renderer }; + macro_rules! emit { + ($event:ident($($args:expr),*) => $closure:expr) => {{ + let run = move || { + (delegate.$event)($($args),*); - use Event::*; + $closure + }; + + unsafe { post_task(delegate.post_task, run) } + }}; + ($event:ident($($args:expr),*)) => {{ + emit!($event($($args),*) => {}) + }}; + } - match event.clone() { - Exit => (), - Scroll { delta } => scroll(delta as c_int * char_height as c_int), - KeyPress { ref key } => { - if dispatch(renderer.keypress(key)?) { - key_press(key.char as c_char) + listen(|mut events| { + bridge.lock().unwrap().thread.run(move |renderer| { + let get_scale = || bridge.lock().unwrap().window.scale; + let scale = |col, row| { + let scale = get_scale(); + + scale + .mul(((col as f32 + 0.5), (row as f32 - 0.5))) + .floor() + .cast() + .into() + }; + let dispatch = |action| { + match action { + NavigationAction::Ignore => (), + NavigationAction::Forward => return true, + NavigationAction::GoBack() => emit!(go_back()), + NavigationAction::GoForward() => emit!(go_forward()), + NavigationAction::Refresh() => emit!(refresh()), + NavigationAction::GoTo(url) => { + let c_str = CString::new(url).unwrap(); + + emit!(go_to(c_str.as_ptr())) } - } - MouseUp { col, row } => { - if dispatch(renderer.mouse_up((col as _, row as _).into())?) { - mouse_up( - (col as c_uint) * char_width, - (row as c_uint - 1) * char_height, - ) + }; + + return false; + }; + + for event in std::mem::take(&mut events) { + use Event::*; + + match event { + Exit => (), + Scroll { delta } => { + let scale = get_scale(); + + emit!(scroll((delta as f32 * scale.height) as c_int)) } - } - MouseDown { col, row } => { - if dispatch(renderer.mouse_down((col as _, row as _).into())?) { - mouse_down( - (col as c_uint) * char_width, - (row as c_uint - 1) * char_height, - ) + KeyPress { key } => { + if dispatch(renderer.keypress(&key).unwrap()) { + emit!(key_press(key.char as c_char)) + } } - } - MouseMove { col, row } => { - if dispatch(renderer.mouse_move((col as _, row as _).into())?) { - mouse_move( - (col as c_uint) * char_width, - (row as c_uint - 1) * char_height, - ) + MouseUp { col, row } => { + if dispatch(renderer.mouse_up((col as _, row as _).into()).unwrap()) { + let (width, height) = scale(col, row); + + emit!(mouse_up(width, height)) + } } - } - Terminal(terminal) => match terminal { - TerminalEvent::Name(name) => log::debug!("terminal name: {name}"), - TerminalEvent::TrueColorSupported => renderer.enable_true_color(), - }, - }; + MouseDown { col, row } => { + if dispatch(renderer.mouse_down((col as _, row as _).into()).unwrap()) { + let (width, height) = scale(col, row); + + emit!(mouse_down(width, height)) + } + } + MouseMove { col, row } => { + if dispatch(renderer.mouse_move((col as _, row as _).into()).unwrap()) { + let (width, height) = scale(col, row); - Ok(()) + emit!(mouse_move(width, height)) + } + } + Terminal(terminal) => match terminal { + TerminalEvent::Name(name) => log::debug!("terminal name: {name}"), + TerminalEvent::TrueColorSupported => renderer.enable_true_color(), + }, + } + } }) }) .unwrap(); + // Setup single-use channel let (tx, rx) = mpsc::channel(); - post_task(handle, move || { - shutdown(); - tx.send(()).unwrap(); - - Ok(()) - }); - + // Signal the browser to shutdown and notify our thread + emit!(shutdown() => tx.send(()).unwrap()); rx.recv().unwrap(); + + // Shutdown rendering thread + if let Some(handle) = { bridge.lock().unwrap().thread.stop() } { + handle.join().unwrap() + } } diff --git a/src/browser/host_display_client.cc b/src/browser/host_display_client.cc index 1815053..60ca5b8 100644 --- a/src/browser/host_display_client.cc +++ b/src/browser/host_display_client.cc @@ -20,8 +20,12 @@ namespace carbonyl { LayeredWindowUpdater::LayeredWindowUpdater( - mojo::PendingReceiver receiver) - : receiver_(this, std::move(receiver)) {} + mojo::PendingReceiver receiver +) + : + receiver_(this, std::move(receiver)), + task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()) + {} LayeredWindowUpdater::~LayeredWindowUpdater() = default; @@ -30,17 +34,27 @@ void LayeredWindowUpdater::OnAllocatedSharedMemory( base::UnsafeSharedMemoryRegion region) { if (region.IsValid()) shm_mapping_ = region.Map(); + + pixel_size_ = pixel_size; } void LayeredWindowUpdater::Draw(const gfx::Rect& damage_rect, - DrawCallback draw_callback) { - Renderer::Main()->DrawBackground( + DrawCallback callback) { + Bridge::GetCurrent()->DrawBitmap( shm_mapping_.GetMemoryAs(), - shm_mapping_.size(), - damage_rect + pixel_size_, + damage_rect, + base::BindOnce( + []( + scoped_refptr task_runner, + DrawCallback callback + ) { + task_runner->PostTask(FROM_HERE, std::move(callback)); + }, + task_runner_, + std::move(callback) + ) ); - - std::move(draw_callback).Run(); } HostDisplayClient::HostDisplayClient() diff --git a/src/browser/host_display_client.h b/src/browser/host_display_client.h index 23fa6b3..9eeaaad 100644 --- a/src/browser/host_display_client.h +++ b/src/browser/host_display_client.h @@ -32,6 +32,10 @@ class LayeredWindowUpdater : public viz::mojom::LayeredWindowUpdater { private: mojo::Receiver receiver_; base::WritableSharedMemoryMapping shm_mapping_; + gfx::Size pixel_size_; + DrawCallback callback_; + scoped_refptr task_runner_; + base::WeakPtrFactory weak_ptr_factory_ { this }; }; class HostDisplayClient : public viz::HostDisplayClient { diff --git a/src/browser/render_service_impl.cc b/src/browser/render_service_impl.cc index b575e84..f67ff3a 100644 --- a/src/browser/render_service_impl.cc +++ b/src/browser/render_service_impl.cc @@ -14,13 +14,14 @@ CarbonylRenderServiceImpl::CarbonylRenderServiceImpl( CarbonylRenderServiceImpl::~CarbonylRenderServiceImpl() = default; void CarbonylRenderServiceImpl::DrawText(std::vector data) { - auto* renderer = Renderer::Main(); - - renderer->ClearText(); + auto* renderer = Bridge::GetCurrent(); + std::vector mapped; for (auto& text: data) { - renderer->DrawText(text->contents, text->bounds, text->color); + mapped.emplace_back(text->contents, text->bounds, text->color); } + + renderer->DrawText(mapped); } } diff --git a/src/cli.rs b/src/cli.rs index 0b10f5c..957776a 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,5 +1,5 @@ -mod main; -mod parser; +mod cli; +mod program; -pub use main::*; -pub use parser::*; +pub use cli::*; +pub use program::*; diff --git a/src/cli/cli.rs b/src/cli/cli.rs new file mode 100644 index 0000000..007ccb4 --- /dev/null +++ b/src/cli/cli.rs @@ -0,0 +1,109 @@ +use std::{env, ffi::OsStr}; + +use super::CommandLineProgram; + +#[derive(Clone, Debug)] +pub struct CommandLine { + pub args: Vec, + pub fps: f32, + pub zoom: f32, + pub debug: bool, + pub bitmap: bool, + pub program: CommandLineProgram, + pub shell_mode: bool, +} + +pub enum EnvVar { + Debug, + Bitmap, + ShellMode, +} + +impl EnvVar { + pub fn as_str(&self) -> &'static str { + match self { + EnvVar::Debug => "CARBONYL_ENV_DEBUG", + EnvVar::Bitmap => "CARBONYL_ENV_BITMAP", + EnvVar::ShellMode => "CARBONYL_ENV_SHELL_MODE", + } + } +} + +impl AsRef for EnvVar { + fn as_ref(&self) -> &OsStr { + self.as_str().as_ref() + } +} + +impl CommandLine { + pub fn parse() -> CommandLine { + let mut fps = 60.0; + let mut zoom = 1.0; + let mut debug = false; + let mut bitmap = false; + let mut shell_mode = false; + let mut program = CommandLineProgram::Main; + let args = env::args().skip(1).collect::>(); + + for arg in &args { + let split: Vec<&str> = arg.split("=").collect(); + let default = arg.as_str(); + let (key, value) = (split.get(0).unwrap_or(&default), split.get(1)); + + macro_rules! set { + ($var:ident, $enum:ident) => {{ + $var = true; + + env::set_var(EnvVar::$enum, "1"); + }}; + } + + macro_rules! set_f32 { + ($var:ident = $expr:expr) => {{ + if let Some(value) = value { + if let Some(value) = value.parse::().ok() { + $var = { + let $var = value; + + $expr + }; + } + } + }}; + } + + match *key { + "-f" | "--fps" => set_f32!(fps = fps), + "-z" | "--zoom" => set_f32!(zoom = zoom / 100.0), + "-b" | "--bitmap" => set!(bitmap, Bitmap), + "-d" | "--debug" => set!(debug, Debug), + + "-h" | "--help" => program = CommandLineProgram::Help, + "-v" | "--version" => program = CommandLineProgram::Version, + _ => (), + } + } + + if env::var(EnvVar::Debug).is_ok() { + debug = true; + } + + if env::var(EnvVar::Bitmap).is_ok() { + bitmap = true; + } + + if env::var(EnvVar::ShellMode).is_ok() { + shell_mode = true; + } + + CommandLine { + args, + fps, + zoom, + debug, + bitmap, + program, + shell_mode, + } + } +} diff --git a/src/cli/main.rs b/src/cli/main.rs deleted file mode 100644 index 10b14d1..0000000 --- a/src/cli/main.rs +++ /dev/null @@ -1,15 +0,0 @@ -use super::{CommandLine, CommandLineProgram}; - -pub fn main() -> Option { - match CommandLineProgram::parse() { - CommandLineProgram::Main(cmd) => return Some(cmd), - CommandLineProgram::Help => { - println!("{}", include_str!("usage.txt")) - } - CommandLineProgram::Version => { - println!("Carbonyl {}", env!("CARGO_PKG_VERSION")) - } - } - - None -} diff --git a/src/cli/parser.rs b/src/cli/parser.rs deleted file mode 100644 index e792c36..0000000 --- a/src/cli/parser.rs +++ /dev/null @@ -1,30 +0,0 @@ -use std::env; - -pub struct CommandLine { - pub args: Vec, - pub debug: bool, -} - -pub enum CommandLineProgram { - Help, - Version, - Main(CommandLine), -} - -impl CommandLineProgram { - pub fn parse() -> CommandLineProgram { - let mut debug = false; - let mut args = Vec::new(); - - for arg in env::args().skip(1) { - match arg.as_str() { - "-d" | "--debug" => debug = true, - "-h" | "--help" => return CommandLineProgram::Help, - "-v" | "--version" => return CommandLineProgram::Version, - _ => args.push(arg), - } - } - - CommandLineProgram::Main(CommandLine { args, debug }) - } -} diff --git a/src/cli/program.rs b/src/cli/program.rs new file mode 100644 index 0000000..2359bdb --- /dev/null +++ b/src/cli/program.rs @@ -0,0 +1,26 @@ +use super::CommandLine; + +#[derive(Clone, Debug)] +pub enum CommandLineProgram { + Main, + Help, + Version, +} + +impl CommandLineProgram { + pub fn parse_or_run() -> Option { + let cmd = CommandLine::parse(); + + match cmd.program { + CommandLineProgram::Main => return Some(cmd), + CommandLineProgram::Help => { + println!("{}", include_str!("usage.txt")) + } + CommandLineProgram::Version => { + println!("Carbonyl {}", env!("CARGO_PKG_VERSION")) + } + } + + None + } +} diff --git a/src/cli/usage.txt b/src/cli/usage.txt index 7bc6ecc..5a2083d 100644 --- a/src/cli/usage.txt +++ b/src/cli/usage.txt @@ -7,6 +7,9 @@ O —— Cr —— O In addition to the following options, Usage: carbonyl [options] [url] Options: - -h, --help display this help message + -f, --fps= set the maximum number of frames per second (default: 60) + -z, --zoom= set the zoom level in percent (default: 100) + -b, --bitmap render text as bitmaps -d, --debug enable debug logs + -h, --help display this help message -v, --version output the version number diff --git a/src/gfx/size.rs b/src/gfx/size.rs index f5392db..d9a1f08 100644 --- a/src/gfx/size.rs +++ b/src/gfx/size.rs @@ -1,7 +1,7 @@ use super::Vector2; use crate::impl_vector_overload; -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Default)] pub struct Size { pub width: T, pub height: T, diff --git a/src/gfx/vector.rs b/src/gfx/vector.rs index e16334f..1c4e00d 100644 --- a/src/gfx/vector.rs +++ b/src/gfx/vector.rs @@ -137,6 +137,12 @@ macro_rules! impl_vector_overload { } } + impl From<$struct> for (T, T) { + fn from(vector: $struct) -> Self { + (vector.$x, vector.$y) + } + } + impl From<(T, T)> for $struct { fn from((x, y): (T, T)) -> Self { Self::new(x, y) @@ -219,6 +225,12 @@ macro_rules! impl_vector_overload { } } + impl From<$struct> for (T, T, T) { + fn from(vector: $struct) -> Self { + (vector.$x, vector.$y, vector.$z) + } + } + impl From<[T; 3]> for $struct { fn from(array: [T; 3]) -> Self { Self::new(array[0], array[1], array[2]) @@ -338,6 +350,14 @@ macro_rules! impl_vector_traits { self.map(|v| v.round()) } + pub fn floor(&self) -> Self { + self.map(|v| v.floor()) + } + + pub fn ceil(&self) -> Self { + self.map(|v| v.ceil()) + } + pub fn min(&self, min: U) -> Self where U: Into diff --git a/src/input/listen.rs b/src/input/listen.rs index ddc7359..4343d6d 100644 --- a/src/input/listen.rs +++ b/src/input/listen.rs @@ -6,7 +6,7 @@ use crate::input::*; /// This will block, so it should run from a dedicated thread. pub fn listen(mut callback: F) -> io::Result<()> where - F: FnMut(Event), + F: FnMut(Vec), { let mut buf = [0u8; 1024]; let mut stdin = io::stdin(); @@ -15,20 +15,22 @@ where loop { // Wait for some input let size = stdin.read(&mut buf)?; + let read = parser.parse(&buf[0..size]); let mut scroll = 0; + let mut events = Vec::with_capacity(read.len()); - // Parse the input for xterm commands - for event in parser.parse(&buf[0..size]) { - // Allow the callback to return early (ie. handle ctrl+c) + for event in read { match event { Event::Exit => return Ok(()), Event::Scroll { delta } => scroll += delta, - event => callback(event), + event => events.push(event), } } if scroll != 0 { - callback(Event::Scroll { delta: scroll }) + events.push(Event::Scroll { delta: scroll }) } + + callback(events) } } diff --git a/src/output.rs b/src/output.rs index e7ed7a5..9d16b5f 100644 --- a/src/output.rs +++ b/src/output.rs @@ -1,12 +1,18 @@ // mod kd_tree; // mod quantizer; mod cell; +mod frame_sync; mod painter; +mod quad; +mod render_thread; mod renderer; -mod size; +mod window; mod xterm; pub use cell::*; +pub use frame_sync::*; pub use painter::*; +pub use quad::*; +pub use render_thread::*; pub use renderer::*; -pub use size::*; +pub use window::*; diff --git a/src/output/cell.rs b/src/output/cell.rs index 2b57bcc..6c2193d 100644 --- a/src/output/cell.rs +++ b/src/output/cell.rs @@ -14,22 +14,23 @@ pub struct Grapheme { /// Terminal cell with `height = width * 2` #[derive(PartialEq)] pub struct Cell { - /// Top pixel color value - pub top: Color, - /// Bottom pixel color value - pub bottom: Color, pub cursor: Point, /// Text grapheme if any pub grapheme: Option>, + pub quadrant: (Color, Color, Color, Color), } impl Cell { pub fn new(x: u32, y: u32) -> Cell { Cell { - top: Color::black(), - bottom: Color::black(), cursor: Point::new(x, y), grapheme: None, + quadrant: ( + Color::black(), + Color::black(), + Color::black(), + Color::black(), + ), } } } diff --git a/src/output/frame_sync.rs b/src/output/frame_sync.rs new file mode 100644 index 0000000..b5f3397 --- /dev/null +++ b/src/output/frame_sync.rs @@ -0,0 +1,31 @@ +use std::time::{Duration, Instant}; + +pub struct FrameSync { + last_render: Option, + frame_duration: Duration, +} + +impl FrameSync { + pub fn new(fps: f32) -> Self { + Self { + last_render: None, + frame_duration: Duration::from_micros((1_000_000.0 / fps) as u64), + } + } + + pub fn tick(&mut self) { + self.last_render = Some(Instant::now()); + } + + pub fn wait(&mut self) -> Duration { + if let Some(last_render) = self.last_render { + let elapsed = Instant::now() - last_render; + + if elapsed < self.frame_duration { + return self.frame_duration - elapsed; + } + } + + Duration::from_secs(0) + } +} diff --git a/src/output/painter.rs b/src/output/painter.rs index 961d9df..a28862d 100644 --- a/src/output/painter.rs +++ b/src/output/painter.rs @@ -2,16 +2,9 @@ use std::io::{self, Stdout, Write}; use crate::gfx::{Color, Point}; -use super::Cell; - -#[derive(PartialEq)] -enum PaintMode { - Text, - Bitmap, -} +use super::{binarize_quandrant, Cell}; pub struct Painter { - mode: PaintMode, output: Stdout, buffer: Vec, cursor: Option>, @@ -25,7 +18,6 @@ pub struct Painter { impl Painter { pub fn new() -> Painter { Painter { - mode: PaintMode::Text, buffer: Vec::new(), cursor: None, output: io::stdout(), @@ -73,42 +65,29 @@ impl Painter { pub fn paint(&mut self, cell: &Cell) -> io::Result<()> { let &Cell { cursor, + quadrant, ref grapheme, - top: mut background, - bottom: mut foreground, } = cell; - let (char, width, escape) = if let Some(grapheme) = grapheme { + let (char, background, foreground, width) = if let Some(grapheme) = grapheme { if grapheme.index > 0 { return Ok(()); } - background = background.avg_with(foreground); - foreground = grapheme.color; - ( grapheme.char.as_str(), + quadrant + .0 + .avg_with(quadrant.1) + .avg_with(quadrant.2) + .avg_with(quadrant.3), + grapheme.color, grapheme.width as u32, - if self.mode == PaintMode::Bitmap { - self.mode = PaintMode::Text; - - Some("\x1b[22m\x1b[24m") - } else { - None - }, ) } else { - ( - "▄", - 1, - if self.mode == PaintMode::Text { - self.mode = PaintMode::Bitmap; - - Some("\x1b[1m\x1b[4m") - } else { - None - }, - ) + let (char, background, foreground) = binarize_quandrant(quadrant); + + (char, background, foreground, 1) }; if self.cursor != Some(cursor) { @@ -157,10 +136,6 @@ impl Painter { } } - if let Some(escape) = escape { - self.buffer.write_all(escape.as_bytes())? - } - self.buffer.write_all(char.as_bytes())?; Ok(()) diff --git a/src/output/quad.rs b/src/output/quad.rs new file mode 100644 index 0000000..4e8fd04 --- /dev/null +++ b/src/output/quad.rs @@ -0,0 +1,40 @@ +use crate::gfx::Color; +use crate::utils::FourBits::{self, *}; + +/// Turn a quadrant of four colors into two colors and a quadrant unicode character. +pub fn binarize_quandrant( + (x, y, z, w): (Color, Color, Color, Color), +) -> (&'static str, Color, Color) { + // Step 1: grayscale + const LUMA: Color = Color::new(0.299, 0.587, 0.114); + let (a, b, c, d) = ( + LUMA.dot(x.cast()), + LUMA.dot(y.cast()), + LUMA.dot(z.cast()), + LUMA.dot(w.cast()), + ); + // Step 2: luminance middlepoint + let min = a.min(b).min(c).min(d); + let max = a.max(b).max(c).max(d); + let mid = min + (max - min) / 2.0; + + // Step 3: average colors based on binary mask + match FourBits::new(a > mid, b > mid, c > mid, d > mid) { + B0000 => ("▄", x.avg_with(y), z.avg_with(w)), + B0001 => ("▖", x.avg_with(y).avg_with(z), w), + B0010 => ("▗", x.avg_with(y).avg_with(w), z), + B0011 => ("▄", x.avg_with(y), z.avg_with(w)), + B0100 => ("▝", x.avg_with(z).avg_with(w), y), + B0101 => ("▞", x.avg_with(z), y.avg_with(w)), + B0110 => ("▐", x.avg_with(w), y.avg_with(z)), + B0111 => ("▘", y.avg_with(z).avg_with(w), x), + B1000 => ("▘", y.avg_with(z).avg_with(w), x), + B1001 => ("▌", y.avg_with(z), x.avg_with(w)), + B1010 => ("▚", y.avg_with(w), x.avg_with(z)), + B1011 => ("▝", x.avg_with(z).avg_with(w), y), + B1100 => ("▄", x.avg_with(y), z.avg_with(w)), + B1101 => ("▗", x.avg_with(y).avg_with(w), z), + B1110 => ("▖", x.avg_with(y).avg_with(z), w), + B1111 => ("▄", x.avg_with(y), z.avg_with(w)), + } +} diff --git a/src/output/render_thread.rs b/src/output/render_thread.rs new file mode 100644 index 0000000..9109903 --- /dev/null +++ b/src/output/render_thread.rs @@ -0,0 +1,102 @@ +use std::{ + sync::mpsc::{self, Sender}, + thread::{self, JoinHandle}, + time::Duration, +}; + +use crate::cli::CommandLine; + +use super::{FrameSync, Renderer}; + +pub struct RenderThread { + thread: Option<(Sender, JoinHandle<()>)>, + enabled: bool, +} + +type RenderClosure = Box; +enum Message { + Run(RenderClosure), + Shutdown, +} + +impl RenderThread { + pub fn new() -> Self { + Self { + thread: None, + enabled: false, + } + } + + pub fn enable(&mut self) { + self.enabled = true + } + + pub fn stop(&mut self) -> Option> { + self.enabled = false; + self.send(Message::Shutdown); + + let (_, handle) = self.thread.take()?; + + Some(handle) + } + + pub fn run(&mut self, run: F) + where + F: FnMut(&mut Renderer) + Send + 'static, + { + self.send(Message::Run(Box::new(run))) + } + + fn send(&mut self, message: Message) { + if let Some((tx, _)) = &self.thread { + tx.send(message).unwrap() + } else if self.enabled { + let (tx, rx) = mpsc::channel(); + + tx.send(message).unwrap(); + + self.thread = Some(( + tx.clone(), + thread::spawn(move || { + let cmd = CommandLine::parse(); + let mut sync = FrameSync::new(cmd.fps); + let mut renderer = Renderer::new(); + let mut needs_render = false; + + loop { + let mut wait = Duration::from_secs(0); + + loop { + match if wait.as_micros() == 0 { + rx.recv().ok() + } else { + rx.recv_timeout(wait).ok() + } { + None => (), + Some(Message::Shutdown) => return, + Some(Message::Run(mut closure)) => { + closure(&mut renderer); + + needs_render = true; + } + } + + wait = sync.wait(); + + if wait.as_micros() == 0 { + break; + } + } + + if needs_render { + needs_render = false; + + sync.tick(); + renderer.render().unwrap() + } + } + }), + )); + } + } +} diff --git a/src/output/renderer.rs b/src/output/renderer.rs index b1c1e16..def8f81 100644 --- a/src/output/renderer.rs +++ b/src/output/renderer.rs @@ -10,24 +10,16 @@ use crate::{ gfx::{Color, Point, Rect, Size}, input::Key, ui::navigation::{Navigation, NavigationAction}, + utils::log, }; use super::{Cell, Grapheme, Painter}; -struct Dimensions { - /// Size of a terminal cell in pixels - cell: Size, - /// Size of the browser window in pixels - browser: Size, - /// Size of the terminal window in cells - terminal: Size, -} - pub struct Renderer { nav: Navigation, cells: Vec<(Cell, Cell)>, - dimensions: Dimensions, painter: Painter, + dimensions: Size, } impl Renderer { @@ -36,11 +28,7 @@ impl Renderer { nav: Navigation::new(), cells: Vec::with_capacity(0), painter: Painter::new(), - dimensions: Dimensions { - cell: Size::new(7, 14), - browser: Size::new(0, 0), - terminal: Size::new(0, 0), - }, + dimensions: Size::new(0, 0), } } @@ -51,29 +39,21 @@ impl Renderer { pub fn keypress(&mut self, key: &Key) -> io::Result { let action = self.nav.keypress(key); - self.render()?; - Ok(action) } pub fn mouse_up(&mut self, origin: Point) -> io::Result { let action = self.nav.mouse_up(origin); - self.render()?; - Ok(action) } pub fn mouse_down(&mut self, origin: Point) -> io::Result { let action = self.nav.mouse_down(origin); - self.render()?; - Ok(action) } pub fn mouse_move(&mut self, origin: Point) -> io::Result { let action = self.nav.mouse_move(origin); - self.render()?; - Ok(action) } @@ -82,16 +62,14 @@ impl Renderer { } pub fn get_size(&self) -> Size { - self.dimensions.terminal + self.dimensions } - pub fn set_size(&mut self, cell: Size, terminal: Size) { + pub fn set_size(&mut self, terminal: Size) { let size = (terminal.width + terminal.width * terminal.height) as usize; self.nav.set_size(terminal); - self.dimensions.cell = cell; - self.dimensions.terminal = terminal; - self.dimensions.browser = cell * terminal; + self.dimensions = terminal; let mut x = 0; let mut y = 0; @@ -113,14 +91,19 @@ impl Renderer { } pub fn render(&mut self) -> io::Result<()> { - let size = self.dimensions.terminal; + let size = self.dimensions; for (origin, element) in self.nav.render(size) { self.fill_rect( Rect::new(origin.x, origin.y, element.text.width() as u32, 1), element.background, ); - self.draw_text(&element.text, origin, Size::splat(0), element.foreground); + self.draw_text( + &element.text, + origin * (2, 1), + Size::splat(0), + element.foreground, + ); } self.painter.begin()?; @@ -130,8 +113,7 @@ impl Renderer { continue; } - previous.top = current.top; - previous.bottom = current.bottom; + previous.quadrant = current.quadrant; previous.grapheme = current.grapheme.clone(); self.painter.paint(current)?; @@ -143,53 +125,52 @@ impl Renderer { } /// Draw the background from a pixel array encoded in RGBA8888 - pub fn draw_background(&mut self, pixels: &mut [u8], rect: Rect) -> io::Result<()> { - let viewport = self.dimensions.terminal.cast::(); - let pixels_row = viewport.width * 4; - - if pixels.len() != pixels_row * viewport.height * 2 { - return Ok(()); + pub fn draw_background(&mut self, pixels: &[u8], pixels_size: Size, rect: Rect) { + let viewport = self.dimensions.cast::(); + + if pixels.len() < viewport.width * viewport.height * 8 * 4 { + log::debug!( + "unexpected size, actual: {}, expected: {}", + pixels.len(), + viewport.width * viewport.height * 8 * 4 + ); + return; } - let pos = rect.origin.cast::() / (1, 2); - let size = rect.size.cast::() / (1, 2); - let pixels_left = pos.x * 4; - let pixels_width = size.width * 4; + let origin = rect.origin.cast::().max(0.0) / (2.0, 4.0); + let size = rect.size.cast::().max(0.0) / (2.0, 4.0); + let top = origin.y.floor() as usize; + let left = origin.x.floor() as usize; + let right = ((origin.x + size.width).ceil() as usize).min(viewport.width); + let bottom = ((origin.y + size.height).ceil() as usize).min(viewport.height); + let row_length = pixels_size.width as usize; + let pixel = |x, y| { + Color::new( + pixels[((x + y * row_length) * 4 + 2) as usize], + pixels[((x + y * row_length) * 4 + 1) as usize], + pixels[((x + y * row_length) * 4 + 0) as usize], + ) + }; + let pair = |x, y| pixel(x, y).avg_with(pixel(x, y + 1)); - // Iterate over each row - for y in pos.y..pos.y + size.height { - // Terminal chars have an aspect ratio of 2:1. - // In order to display perfectly squared pixels, we - // render a unicode glyph taking the bottom half of the cell - // using a foreground representing the bottom pixel, - // and a background representing the top pixel. - // This means that the pixel input buffer should be twice the size - // of the terminal cell buffer (two pixels take one terminal cell). - let left = pixels_left + y * 2 * pixels_row; - let right = left + pixels_width; - // Get a slice pointing to the top pixel row - let mut top_row = pixels[left..right].iter(); - // Get a slice pointing to the bottom pixel row - let mut bottom_row = pixels[left + pixels_row..right + pixels_row].iter(); - let cells_left = y * viewport.width + pos.x + viewport.width; - let cells = self.cells[cells_left..].iter_mut(); - - // Iterate over each column - for (_, cell) in cells { - match ( - Color::from_iter(&mut top_row), - Color::from_iter(&mut bottom_row), - ) { - (Some(top), Some(bottom)) => { - cell.top = top; - cell.bottom = bottom; - } - _ => break, - } + for y in top..bottom { + let index = (y + 1) * viewport.width; + let start = index + left; + let end = index + right; + let mut x = left * 2; + let y = y * 4; + + for (_, cell) in &mut self.cells[start..end] { + cell.quadrant = ( + pair(x + 0, y + 0), + pair(x + 1, y + 0), + pair(x + 1, y + 2), + pair(x + 0, y + 2), + ); + + x += 2; } } - - self.render() } pub fn clear_text(&mut self) { @@ -210,9 +191,8 @@ impl Renderer { pub fn fill_rect(&mut self, rect: Rect, color: Color) { self.draw(rect, |cell| { - cell.top = color; - cell.bottom = color; cell.grapheme = None; + cell.quadrant = (color, color, color, color); }) } @@ -222,7 +202,7 @@ impl Renderer { { let origin = bounds.origin.cast::(); let size = bounds.size.cast::(); - let viewport_width = self.dimensions.terminal.width as usize; + let viewport_width = self.dimensions.width as usize; let top = origin.y; let bottom = top + size.height; @@ -241,25 +221,28 @@ impl Renderer { pub fn draw_text(&mut self, string: &str, origin: Point, size: Size, color: Color) { // Get an iterator starting at the text origin let len = self.cells.len(); - let viewport = &self.dimensions.terminal; + let viewport = &self.dimensions.cast::(); if size.width > 2 && size.height > 2 { - let x = origin.x.max(0).min(viewport.width as i32); - let top = (origin.y + 1).max(0); - let bottom = top + size.height as i32; + let origin = (origin.cast::() / (2.0, 4.0) + (0.0, 1.0)).round(); + let size = (size.cast::() / (2.0, 4.0)).round(); + let left = (origin.x.max(0.0) as usize).min(viewport.width); + let right = ((origin.x + size.width).max(0.0) as usize).min(viewport.width); + let top = (origin.y.max(0.0) as usize).min(viewport.height); + let bottom = ((origin.y + size.height).max(0.0) as usize).min(viewport.height); for y in top..bottom { - let index = x + y / 2 * (viewport.width as i32); - let left = len.min(index as usize); - let right = len.min(left + size.width as usize); + let index = y * viewport.width; + let start = index + left; + let end = index + right; - for (_, cell) in self.cells[left..right].iter_mut() { + for (_, cell) in self.cells[start..end].iter_mut() { cell.grapheme = None } } } else { // Compute the buffer index based on the position - let index = origin.x + (origin.y + 1) / 2 * (viewport.width as i32); + let index = origin.x / 2 + (origin.y + 1) / 4 * (viewport.width as i32); let mut iter = self.cells[len.min(index as usize)..].iter_mut(); // Get every Unicode grapheme in the input string diff --git a/src/output/size.rs b/src/output/size.rs deleted file mode 100644 index d6697b6..0000000 --- a/src/output/size.rs +++ /dev/null @@ -1,43 +0,0 @@ -use core::mem::MaybeUninit; -use std::{io, str::FromStr}; - -use crate::{gfx::Size, utils::log}; - -pub fn size() -> io::Result { - let mut ptr = MaybeUninit::::uninit(); - let mut size = unsafe { - if libc::ioctl(libc::STDOUT_FILENO, libc::TIOCGWINSZ, ptr.as_mut_ptr()) == 0 { - Some(ptr.assume_init()) - } else { - None - } - } - .ok_or_else(io::Error::last_os_error)?; - - if size.ws_col == 0 || size.ws_row == 0 { - let cols = parse_var("COLUMNS").unwrap_or(80); - let rows = parse_var("LINES").unwrap_or(24); - - log::warning!( - "TIOCGWINSZ returned an empty size ({}x{}), defaulting to {}x{}", - size.ws_col, - size.ws_row, - cols, - rows - ); - - size.ws_col = cols; - size.ws_row = rows; - } - - Ok(Size::new( - if size.ws_col > 2 { size.ws_col } else { 1 }, - // Keep some space for the UI - if size.ws_row > 2 { size.ws_row - 1 } else { 1 }, - ) - .cast()) -} - -fn parse_var(var: &str) -> Option { - std::env::var(var).ok()?.parse().ok() -} diff --git a/src/output/window.rs b/src/output/window.rs new file mode 100644 index 0000000..8c1b9f4 --- /dev/null +++ b/src/output/window.rs @@ -0,0 +1,78 @@ +use core::mem::MaybeUninit; +use std::{io, str::FromStr}; + +use crate::{cli::CommandLine, gfx::Size, utils::log}; + +#[derive(Clone, Debug)] +pub struct Window { + pub dpi: f32, + /// Size of a terminal cell in pixels + pub scale: Size, + /// Size of the termina window in cells + pub cells: Size, + /// Size of the browser window in pixels + pub browser: Size, + pub cmd: CommandLine, +} + +impl Window { + pub fn read() -> io::Result { + let mut window = Self { + dpi: 1.0, + scale: (0.0, 0.0).into(), + cells: (0, 0).into(), + browser: (0, 0).into(), + cmd: CommandLine::parse(), + }; + + window.update()?; + + Ok(window) + } + + pub fn update(&mut self) -> io::Result<&Self> { + let mut ptr = MaybeUninit::::uninit(); + let mut size = unsafe { + if libc::ioctl(libc::STDOUT_FILENO, libc::TIOCGWINSZ, ptr.as_mut_ptr()) == 0 { + Some(ptr.assume_init()) + } else { + None + } + } + .ok_or_else(io::Error::last_os_error)?; + + if size.ws_col == 0 || size.ws_row == 0 { + let cols = parse_var("COLUMNS").unwrap_or(80); + let rows = parse_var("LINES").unwrap_or(24); + + log::warning!( + "TIOCGWINSZ returned an empty size ({}x{}), defaulting to {}x{}", + size.ws_col, + size.ws_row, + cols, + rows + ); + + size.ws_col = cols; + size.ws_row = rows; + } + + let zoom = 1.5 * self.cmd.zoom; + let cell_pixels = Size::::new(7.0, 14.0); + // Normalize the cells dimensions for an aspect ratio of 1:2 + let cell_width = (cell_pixels.width + cell_pixels.height / 2.0) / 2.0; + + self.dpi = 2.0 / cell_width * zoom; + // A virtual cell should contain a 2x4 pixel quadrant + self.scale = Size::new(2.0, 4.0) / self.dpi; + // Keep some space for the UI + self.cells = Size::new(size.ws_col.max(1), size.ws_row.max(2) - 1).cast(); + self.browser = self.cells.cast::().mul(self.scale).ceil().cast(); + + Ok(self) + } +} + +fn parse_var(var: &str) -> Option { + std::env::var(var).ok()?.parse().ok() +} diff --git a/src/utils.rs b/src/utils.rs index 6fc8535..ee68b73 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,4 +1,8 @@ -pub mod log; +mod four_bits; mod try_block; +pub mod log; + use try_block::*; + +pub use four_bits::*; diff --git a/src/utils/four_bits.rs b/src/utils/four_bits.rs new file mode 100644 index 0000000..0db5ab8 --- /dev/null +++ b/src/utils/four_bits.rs @@ -0,0 +1,44 @@ +pub enum FourBits { + B0000 = 0b0000, + B0001 = 0b0001, + B0010 = 0b0010, + B0011 = 0b0011, + B0100 = 0b0100, + B0101 = 0b0101, + B0110 = 0b0110, + B0111 = 0b0111, + B1000 = 0b1000, + B1001 = 0b1001, + B1010 = 0b1010, + B1011 = 0b1011, + B1100 = 0b1100, + B1101 = 0b1101, + B1110 = 0b1110, + B1111 = 0b1111, +} + +impl FourBits { + pub fn new(x: bool, y: bool, z: bool, w: bool) -> Self { + use FourBits::*; + + match (x as u8) << 3 | (y as u8) << 2 | (z as u8) << 1 | (w as u8) << 0 { + 0b0000 => B0000, + 0b0001 => B0001, + 0b0010 => B0010, + 0b0011 => B0011, + 0b0100 => B0100, + 0b0101 => B0101, + 0b0110 => B0110, + 0b0111 => B0111, + 0b1000 => B1000, + 0b1001 => B1001, + 0b1010 => B1010, + 0b1011 => B1011, + 0b1100 => B1100, + 0b1101 => B1101, + 0b1110 => B1110, + 0b1111 => B1111, + _ => panic!("Unexpected mask value"), + } + } +}