feat: better true color detection

This commit is contained in:
Fathy Boundjadj 2023-01-23 11:45:19 +01:00
parent b7d3b74ace
commit ef6429d881
29 changed files with 1268 additions and 1091 deletions

View File

@ -1,3 +0,0 @@
[build]
rustflags = ["-L", "/Users/fathy/Git/carbonyl/chromium/src/out/Default/obj/headless"]
target-dir = "build"

3
.dockerignore Normal file
View File

@ -0,0 +1,3 @@
/build
!/build/browser
/chromium

View File

@ -1,63 +1,22 @@
FROM debian:11 AS build-env
FROM debian:bullseye-slim
ARG WORKDIR
WORKDIR ${WORKDIR}
RUN groupadd -r carbonyl && useradd -r -g carbonyl carbonyl && \
apt-get update && \
apt-get install -y \
libasound2 libatk-bridge2.0-0 libatk1.0-0 libatomic1 libatspi2.0-0 \
libbrotli1 libc6 libcairo2 libcups2 libdbus-1-3 libdouble-conversion3 \
libdrm2 libevent-2.1-7 libexpat1 libflac8 libfontconfig1 libfreetype6 \
libgbm1 libgcc-s1 libglib2.0-0 libjpeg62-turbo libjsoncpp24 liblcms2-2 \
libminizip1 libnspr4 libnss3 libopenjp2-7 libopus0 libpango-1.0-0 \
libpng16-16 libpulse0 libre2-9 libsnappy1v5 libstdc++6 libwebp6 \
libwebpdemux2 libwebpmux3 libwoff1 libx11-6 libxcb1 libxcomposite1 \
libxdamage1 libxext6 libxfixes3 libxkbcommon0 libxml2 libxnvctrl0 \
libxrandr2 libxslt1.1 zlib1g libgtk-3-0 && \
rm -rf /var/lib/apt/lists/*
ENV PATH="${PATH}:/depot_tools"
ENV CCACHE_DIR="${WORKDIR}/.ccache"
ENV GIT_CACHE_PATH="${WORKDIR}/.git_cache"
ENV DEBIAN_FRONTEND=noninteractive
ENV CHROMIUM_BUILDTOOLS_PATH="${WORKDIR}/electron/src/buildtools"
ENV CCACHE_DIR="${WORKDIR}/.ccache"
ENV CCACHE_CPP2=yes
ENV CCACHE_SLOPPINESS=time_macros
RUN apt-get update && \
apt-get install -y git sudo curl ccache python3 bzip2 xz-utils \
binutils binutils-aarch64-linux-gnu binutils-arm-linux-gnueabihf binutils-mips64el-linux-gnuabi64 binutils-mipsel-linux-gnu bison bzip2 cdbs curl dbus-x11 devscripts dpkg-dev elfutils fakeroot flex git-core gperf libasound2 libasound2-dev libatk1.0-0 libatspi2.0-0 libatspi2.0-dev libbluetooth-dev libbrlapi-dev libbrlapi0.8 libbz2-1.0 libbz2-dev libc6 libc6-dev libcairo2 libcairo2-dev libcap-dev libcap2 libcups2 libcups2-dev libcurl4-gnutls-dev libdrm-dev libdrm2 libegl1 libelf-dev libevdev-dev libevdev2 libexpat1 libffi-dev libffi7 libfontconfig1 libfreetype6 libgbm-dev libgbm1 libgl1 libglib2.0-0 libglib2.0-dev libglu1-mesa-dev libgtk-3-0 libgtk-3-dev libinput-dev libinput10 libjpeg-dev libkrb5-dev libnspr4 libnspr4-dev libnss3 libnss3-dev libpam0g libpam0g-dev libpango-1.0-0 libpangocairo-1.0-0 libpci-dev libpci3 libpcre3 libpixman-1-0 libpng16-16 libpulse-dev libpulse0 libsctp-dev libspeechd-dev libspeechd2 libsqlite3-0 libsqlite3-dev libssl-dev libstdc++6 libudev-dev libudev1 libuuid1 libva-dev libvulkan-dev libvulkan1 libwayland-egl1 libwayland-egl1-mesa libwww-perl libx11-6 libx11-xcb1 libxau6 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxdmcp6 libxext6 libxfixes3 libxi6 libxinerama1 libxkbcommon-dev libxrandr2 libxrender1 libxshmfence-dev libxslt1-dev libxss-dev libxt-dev libxtst-dev libxtst6 locales mesa-common-dev openbox p7zip patch perl pkg-config rpm ruby subversion uuid-dev wdiff x11-utils xcompmgr xz-utils zip zlib1g zstd && \
curl -fsSL https://deb.nodesource.com/setup_16.x | bash - && \
apt-get install -y nodejs && \
git clone --depth 1 --single-branch https://chromium.googlesource.com/chromium/tools/depot_tools.git /depot_tools && \
ccache --max-size=256G
# Release binaries
# ================
FROM --platform=$BUILDPLATFORM debian:11 AS carbonyl-binaries
RUN apt-get update && apt-get install -y unzip
USER carbonyl
ARG TARGETARCH
COPY electron/src/out/release-$TARGETARCH/dist.zip /runtime.zip
RUN unzip /runtime.zip -d /runtime
# TypeScript build
# ================
FROM --platform=$BUILDPLATFORM node:18 AS carbonyl-js
WORKDIR /app
COPY package.json yarn.lock /app/
RUN yarn
COPY tsconfig.json /app/
COPY src /app/src
RUN yarn tsc -b
# Main image
# ==========
FROM node:18
RUN apt-get update && \
apt-get install --yes \
libglib2.0-0 libnss3 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libgtk-3-0 libgbm1 libasound2 \
xvfb x11-xkb-utils xfonts-100dpi xfonts-75dpi xfonts-scalable xfonts-cyrillic x11-apps \
fonts-arphic-ukai fonts-arphic-uming fonts-ipafont-mincho fonts-ipafont-gothic fonts-unfonts-core fonts-noto-core
WORKDIR /app
COPY package.json yarn.lock /app/
RUN yarn --production
COPY --from=carbonyl-js /app/build /app/build
COPY --from=carbonyl-binaries /runtime /app/build/runtime
COPY /scripts/docker-entrypoint.sh /app/scripts/docker-entrypoint.sh
ENTRYPOINT ["/app/scripts/docker-entrypoint.sh"]
COPY build/browser/${TARGETARCH:-amd64} /carbonyl
ENTRYPOINT ["/carbonyl/carbonyl", "--no-sandbox", "--disable-dev-shm-usage"]

View File

@ -1,11 +1,12 @@
solutions = [
{
"name": "src",
"url": "https://chromium.googlesource.com/chromium/src.git@111.0.5539.1",
"url": "https://chromium.googlesource.com/chromium/src.git@111.0.5511.1",
"managed": False,
"custom_deps": {},
"custom_vars": {
"use_rust": True,
"checkout_pgo_profiles": True,
}
},
]

81
src/browser/bridge.cc Normal file
View File

@ -0,0 +1,81 @@
#include "carbonyl/src/browser/bridge.h"
#include <memory>
#include <iostream>
#include <stdio.h>
#include "third_party/skia/include/core/SkColor.h"
extern "C" {
void* carbonyl_renderer_create();
void carbonyl_renderer_clear_text(void* renderer);
void carbonyl_input_listen(void* renderer, void* delegate);
void carbonyl_renderer_draw_text(
void* renderer,
const char* utf8,
const struct carbonyl_bridge_rect* rect,
const struct carbonyl_bridge_color* color
);
void carbonyl_renderer_draw_background(
void* renderer,
const unsigned char* pixels,
size_t pixels_size,
const struct carbonyl_bridge_rect* rect
);
}
namespace carbonyl {
namespace {
static std::unique_ptr<Renderer> globalInstance;
}
Renderer::Renderer(void* ptr): ptr_(ptr) {}
Renderer* Renderer::Main() {
if (!globalInstance) {
globalInstance = std::make_unique<Renderer>(
carbonyl_renderer_create()
);
}
return globalInstance.get();
}
void Renderer::Listen(void* delegate) {
carbonyl_input_listen(ptr_, delegate);
}
void Renderer::ClearText() {
carbonyl_renderer_clear_text(ptr_);
}
void Renderer::DrawText(const std::string& text, const gfx::RectF& bounds, uint32_t sk_color) {
struct carbonyl_bridge_rect rect;
struct carbonyl_bridge_color color;
rect.origin.x = bounds.x();
rect.origin.y = bounds.y();
rect.size.width = bounds.width();
rect.size.height = bounds.height();
color.r = SkColorGetR(sk_color);
color.g = SkColorGetG(sk_color);
color.b = SkColorGetB(sk_color);
carbonyl_renderer_draw_text(ptr_, text.c_str(), &rect, &color);
}
void Renderer::DrawBackgrond(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);
}
}

60
src/browser/bridge.h Normal file
View File

@ -0,0 +1,60 @@
#ifndef CARBONYL_SRC_BROWSER_BRIDGE_H_
#define CARBONYL_SRC_BROWSER_BRIDGE_H_
#include <cstdint>
#include "ui/gfx/geometry/rect_f.h"
extern "C" {
struct carbonyl_bridge_size {
unsigned int width;
unsigned int height;
};
struct carbonyl_bridge_point {
unsigned int x;
unsigned int y;
};
struct carbonyl_bridge_rect {
struct carbonyl_bridge_point origin;
struct carbonyl_bridge_size size;
};
struct carbonyl_bridge_color {
uint8_t r;
uint8_t g;
uint8_t b;
};
struct carbonyl_bridge_browser_delegate {
void (*shutdown) ();
void (*scroll) (int);
void (*key_press) (char);
void (*mouse_up) (unsigned int, unsigned int);
void (*mouse_down) (unsigned int, unsigned int);
void (*mouse_move) (unsigned int, unsigned int);
};
void carbonyl_shell_main();
void carbonyl_output_get_size(struct carbonyl_bridge_size* size);
} /* end extern "C" */
namespace carbonyl {
class Renderer {
public:
Renderer(void* ptr);
static Renderer* Main();
void Listen(void* delegate);
void ClearText();
void DrawText(const std::string& text, const gfx::RectF& bounds, uint32_t color);
void DrawBackgrond(const unsigned char* pixels, size_t pixels_size, const gfx::Rect& bounds);
private:
void* ptr_;
};
}
#endif // CARBONYL_SRC_BROWSER_BRIDGE_H_

View File

@ -1,11 +1,11 @@
use std::ffi::CStr;
use std::io::{stderr, Write};
use std::process::{self, Command, Stdio};
use std::process::{Command, Stdio};
use std::{env, io};
use libc::{c_char, c_int, c_uchar, c_uint, size_t};
use crate::gfx::{Color, Point, Rect, Size};
use crate::gfx::{Cast, Color, Point, Rect, Size};
use crate::terminal::output::Renderer;
use crate::terminal::{input, output};
@ -35,6 +35,23 @@ pub struct CColor {
b: u8,
}
impl<T: Copy> From<&CPoint> for Point<T>
where
c_uint: Cast<T>,
{
fn from(value: &CPoint) -> Self {
Point::new(value.x, value.y).cast()
}
}
impl<T: Copy> From<&CSize> for Size<T>
where
c_uint: Cast<T>,
{
fn from(value: &CSize) -> Self {
Size::new(value.width, value.height).cast()
}
}
#[repr(C)]
pub struct BrowserDelegate {
shutdown: extern "C" fn(),
@ -45,16 +62,14 @@ pub struct BrowserDelegate {
mouse_move: extern "C" fn(c_uint, c_uint),
}
fn main() -> io::Result<()> {
fn main() -> io::Result<Option<i32>> {
const CARBONYL_INSIDE_SHELL: &str = "CARBONYL_INSIDE_SHELL";
if env::vars().find(|(key, value)| key == CARBONYL_INSIDE_SHELL && value == "1") != None {
return Ok(());
return Ok(None);
}
input::setup()?;
Renderer::setup()?;
let mut terminal = input::Terminal::setup();
let output = Command::new(env::current_exe()?)
.args(env::args().skip(1))
.arg("--disable-threaded-scrolling")
@ -65,19 +80,19 @@ fn main() -> io::Result<()> {
.stderr(Stdio::piped())
.output()?;
Renderer::teardown()?;
terminal.teardown();
stderr().write_all(&output.stderr)?;
if let Some(code) = output.status.code() {
process::exit(code);
} else {
process::exit(127);
}
let code = output.status.code();
Ok(if code == None { Some(127) } else { code })
}
#[no_mangle]
pub extern "C-unwind" fn carbonyl_shell_main() {
main().unwrap()
if let Some(code) = main().unwrap() {
std::process::exit(code)
}
}
#[no_mangle]
@ -109,8 +124,8 @@ pub extern "C-unwind" fn carbonyl_renderer_draw_text(
renderer.draw_text(
string.to_str().unwrap(),
Point::new(rect.origin.x as i32, rect.origin.y as i32),
Size::new(rect.size.width as u32, rect.size.height as u32),
Point::from(&rect.origin),
Size::from(&rect.size),
Color::new(color.r, color.g, color.b),
)
}
@ -134,8 +149,8 @@ pub extern "C-unwind" fn carbonyl_renderer_draw_background(
.draw_background(
pixels,
Rect {
origin: Point::new(rect.origin.x as i32, rect.origin.y as i32),
size: Size::new(rect.size.width, rect.size.height),
origin: Point::from(&rect.origin),
size: Size::from(&rect.size),
},
)
.unwrap()
@ -155,32 +170,46 @@ pub extern "C-unwind" fn carbonyl_output_get_size(size: *mut CSize) {
/// 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-unwind" fn carbonyl_input_listen(delegate: *mut BrowserDelegate) {
pub extern "C-unwind" fn carbonyl_input_listen(
renderer: *mut Renderer,
delegate: *mut BrowserDelegate,
) {
let char_width = 7;
let char_height = 14;
let BrowserDelegate {
shutdown,
scroll,
key_press,
mouse_up,
mouse_down,
mouse_move,
} = unsafe { &*delegate };
let (
renderer,
BrowserDelegate {
shutdown,
scroll,
key_press,
mouse_up,
mouse_down,
mouse_move,
},
) = unsafe { (&mut *renderer, &*delegate) };
use input::*;
listen(|event| {
use Event::*;
input::listen(|event| {
match event {
input::Event::Exit => return Some(shutdown()),
input::Event::KeyPress { key } => key_press(key as c_char),
input::Event::Scroll { delta } => scroll(delta as c_int * char_height as c_int),
input::Event::MouseUp { col, row } => {
Exit => return Some(shutdown()),
KeyPress { key } => key_press(key as c_char),
Scroll { delta } => scroll(delta as c_int * char_height as c_int),
MouseUp { col, row } => {
mouse_up(col as c_uint * char_width, row as c_uint * char_height)
}
input::Event::MouseDown { col, row } => {
MouseDown { col, row } => {
mouse_down(col as c_uint * char_width, row as c_uint * char_height)
}
input::Event::MouseMove { col, row } => {
MouseMove { col, row } => {
mouse_move(col as c_uint * char_width, row as c_uint * char_height)
}
Terminal(terminal) => match terminal {
TerminalEvent::Name(name) => eprintln!("Terminal name: {name}"),
TerminalEvent::TrueColorSupported => renderer.enable_true_color(),
},
}
None

View File

@ -1,8 +1,4 @@
// Copyright (c) 2019 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "headless/lib/browser/headless_host_display_client.h"
#include "carbonyl/src/browser/host_display_client.h"
#include <utility>
@ -19,7 +15,7 @@
#include "skia/ext/skia_utils_win.h"
#endif
#include "headless/app/carbonyl_rust_bridge.h"
#include "carbonyl/src/browser/bridge.h"
namespace carbonyl {
@ -40,7 +36,8 @@ void LayeredWindowUpdater::Draw(const gfx::Rect& damage_rect,
DrawCallback draw_callback) {
Renderer::Main()->DrawBackgrond(
shm_mapping_.GetMemoryAs<uint8_t>(),
shm_mapping_.size()
shm_mapping_.size(),
damage_rect
);
std::move(draw_callback).Run();
@ -56,6 +53,11 @@ void HostDisplayClient::CreateLayeredWindowUpdater(
std::make_unique<LayeredWindowUpdater>(std::move(receiver));
}
#if BUILDFLAG(IS_MAC)
void HostDisplayClient::OnDisplayReceivedCALayerParams(
const gfx::CALayerParams& ca_layer_params) {}
#endif
#if BUILDFLAG(IS_LINUX) && !BUILDFLAG(IS_CHROMEOS)
void HostDisplayClient::DidCompleteSwapWithNewSize(
const gfx::Size& size) {}

View File

@ -1,9 +1,5 @@
// Copyright (c) 2019 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef HEADLESS_LIB_BROWSER_HEADLESS_FOCUS_HOST_DISPLAY_CLIENT_H_
#define HEADLESS_LIB_BROWSER_HEADLESS_FOCUS_HOST_DISPLAY_CLIENT_H_
#ifndef CARBONYL_SRC_BROWSER_HOST_DISPLAY_CLIENT_H_
#define CARBONYL_SRC_BROWSER_HOST_DISPLAY_CLIENT_H_
#include <memory>
@ -64,9 +60,8 @@ class HostDisplayClient : public viz::HostDisplayClient {
std::unique_ptr<LayeredWindowUpdater> layered_window_updater_;
OnPaintCallback callback_;
bool active_ = false;
};
} // namespace carbonyl
#endif // HEADLESS_LIB_BROWSER_HEADLESS_FOCUS_HOST_DISPLAY_CLIENT_H_
#endif // CARBONYL_SRC_BROWSER_HOST_DISPLAY_CLIENT_H_

View File

@ -0,0 +1,26 @@
#include "carbonyl/src/browser/render_service_impl.h"
#include <iostream>
#include "carbonyl/src/browser/bridge.h"
namespace carbonyl {
CarbonylRenderServiceImpl::CarbonylRenderServiceImpl(
mojo::PendingReceiver<mojom::CarbonylRenderService> receiver):
receiver_(this, std::move(receiver))
{}
CarbonylRenderServiceImpl::~CarbonylRenderServiceImpl() = default;
void CarbonylRenderServiceImpl::DrawText(std::vector<mojom::TextDataPtr> data) {
auto* renderer = Renderer::Main();
renderer->ClearText();
for (auto& text: data) {
renderer->DrawText(text->contents, text->bounds, text->color);
}
}
}

View File

@ -0,0 +1,27 @@
#ifndef CARBONYL_SRC_BROWSER_RENDER_SERVICE_IMPL_H_
#define CARBONYL_SRC_BROWSER_RENDER_SERVICE_IMPL_H_
#include "carbonyl/src/browser/carbonyl.mojom.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver.h"
namespace carbonyl {
class CarbonylRenderServiceImpl: public mojom::CarbonylRenderService {
public:
explicit CarbonylRenderServiceImpl(mojo::PendingReceiver<mojom::CarbonylRenderService> receiver);
CarbonylRenderServiceImpl(const CarbonylRenderServiceImpl&) = delete;
CarbonylRenderServiceImpl& operator=(const CarbonylRenderServiceImpl&) = delete;
~CarbonylRenderServiceImpl() override;
// carbonyl::mojom::CarbonylRenderService:
void DrawText(std::vector<mojom::TextDataPtr> data) override;
private:
mojo::Receiver<mojom::CarbonylRenderService> receiver_;
};
} // namespace carbonyl
#endif // CARBONYL_SRC_BROWSER_RENDER_SERVICE_IMPL_H_

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
use super::{Point, Size};
// #[derive(Clone, Copy, Debug, PartialEq)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Rect<P: Copy = i32, S: Copy = u32> {
pub origin: Point<P>,
pub size: Size<S>,

View File

@ -247,6 +247,28 @@ macro_rules! impl_vector_traits {
{
self.iter().map(f).collect()
}
pub fn reduce<F>(&self, f: F) -> T
where
T: Default,
F: FnMut(T, T) -> T
{
self.iter().fold(<T as Default>::default(), f)
}
pub fn min_val(&self) -> T
where
T: Default + Ord
{
self.reduce(|a, b| a.min(b))
}
pub fn max_val(&self) -> T
where
T: Default + Ord
{
self.reduce(|a, b| a.max(b))
}
}
crate::impl_vector_traits!($struct $vector i8);
@ -274,7 +296,7 @@ macro_rules! impl_vector_traits {
impl $struct<$type> {
pub fn avg_with<T>(&self, rhs: T) -> Self
where
T: Into<$struct<$type>>
T: Into<Self>
{
let rhs = rhs.into();
@ -294,8 +316,8 @@ macro_rules! impl_vector_traits {
pub fn mul_add<M, A>(&self, mul: M, add: A) -> Self
where
M: Into<$struct<$type>>,
A: Into<$struct<$type>>,
M: Into<Self>,
A: Into<Self>,
{
self.iter()
.zip(mul.into().iter())
@ -310,7 +332,7 @@ macro_rules! impl_vector_traits {
pub fn min<U>(&self, min: U) -> Self
where
U: Into<$struct<$type>>
U: Into<Self>
{
self.iter()
.zip(min.into().iter())
@ -320,7 +342,7 @@ macro_rules! impl_vector_traits {
pub fn max<U>(&self, max: U) -> Self
where
U: Into<$struct<$type>>
U: Into<Self>
{
self.iter()
.zip(max.into().iter())
@ -330,7 +352,7 @@ macro_rules! impl_vector_traits {
pub fn clamp<U>(&self, min: U, max: U) -> Self
where
U: Into<$struct<$type>>
U: Into<Self>
{
self.iter()
.zip(min.into().iter())
@ -345,7 +367,7 @@ macro_rules! impl_vector_traits {
pub fn $name<U>(&self, rhs: U) -> Self
where
T: std::ops::$trait<T, Output = T>,
U: Copy + Into<$struct<T>>
U: Copy + Into<Self>
{
self.iter()
.zip(rhs.into().iter())

View File

@ -1,10 +1,13 @@
mod dcs;
mod event;
mod listen;
mod mouse;
mod parser;
mod raw_tty;
mod tty;
pub use dcs::*;
pub use event::*;
pub use listen::*;
pub use mouse::*;
pub use parser::*;
pub use tty::*;

208
src/terminal/input/dcs.rs Normal file
View File

@ -0,0 +1,208 @@
#[derive(Clone)]
enum State {
Code,
Type(u8),
Status(DeviceControlStatus),
Resource(DeviceControlResource),
}
#[derive(Clone)]
pub struct DeviceControl {
state: State,
}
pub enum DeviceControlEvent {
Break,
Continue,
TerminalName(String),
TrueColorSupported,
}
impl DeviceControl {
pub fn new() -> Self {
DeviceControl { state: State::Code }
}
pub fn parse(&mut self, key: u8) -> DeviceControlEvent {
use DeviceControlEvent::*;
use State::*;
match self.state {
Code => match key {
b'0' | b'1' => self.state = Type(key),
_ => return Break,
},
Type(code) => match key {
b'$' => self.state = Status(DeviceControlStatus::new(code)),
b'+' => self.state = Resource(DeviceControlResource::new(code)),
_ => return Break,
},
Status(ref mut status) => return status.parse(key),
Resource(ref mut resource) => return resource.parse(key),
}
Continue
}
}
#[derive(Clone)]
enum DeviceControlResourceState {
Start,
Name,
Value,
Terminator,
}
#[derive(Clone)]
struct DeviceControlResource {
code: u8,
state: DeviceControlResourceState,
name: Vec<u8>,
value: Vec<u8>,
}
impl DeviceControlResource {
fn new(code: u8) -> Self {
Self {
code,
state: DeviceControlResourceState::Start,
name: Vec::new(),
value: Vec::new(),
}
}
fn parse(&mut self, key: u8) -> DeviceControlEvent {
use DeviceControlEvent::*;
use DeviceControlResourceState::*;
match self.state {
Start => match key {
b'r' => self.state = Name,
_ => return Break,
},
Name => match key {
0x1b => self.state = Terminator,
b'=' => self.state = Value,
key => self.name.push(key),
},
Value => match key {
0x1b => self.state = Terminator,
key => self.value.push(key),
},
Terminator => {
if key == b'\\' && self.code == b'1' {
let name = read_hex_string(self.name.as_slice());
let value = read_hex_string(self.value.as_slice());
if let (Some(name), Some(value)) = (name, value) {
if name == "TN" {
return TerminalName(value);
}
}
}
return Break;
}
}
Continue
}
}
#[derive(Clone)]
enum DeviceControlStatusState {
Start,
Value,
Terminator,
}
#[derive(Clone)]
struct DeviceControlStatus {
code: u8,
op: Option<u8>,
state: DeviceControlStatusState,
buffer: Vec<u8>,
values: Vec<String>,
}
impl DeviceControlStatus {
fn new(code: u8) -> Self {
Self {
code,
op: None,
state: DeviceControlStatusState::Start,
buffer: Vec::new(),
values: Vec::new(),
}
}
fn parse(&mut self, key: u8) -> DeviceControlEvent {
use DeviceControlEvent::*;
use DeviceControlStatusState::*;
match self.state {
Start => match key {
b'r' => self.state = Value,
_ => return Break,
},
Value => match key {
b';' | 0x1b => {
if key == 0x1b {
self.op = self.buffer.pop();
self.state = Terminator;
}
if let Ok(str) = String::from_utf8(std::mem::take(&mut self.buffer)) {
self.values.push(str);
}
}
key => self.buffer.push(key),
},
Terminator => {
if key == b'\\' && self.code == b'1' && self.op == Some(b'm') {
for value in &self.values {
let mut val = 0;
let mut set = Vec::new();
for &char in value.as_bytes() {
match char {
b'0'..=b'9' => val = val * 10 + char - b'0',
b':' => set.push(std::mem::take(&mut val)),
_ => break,
}
}
set.push(val);
if set.len() > 4 && set[1] == 2 && (set[0] == 38 || set[0] == 48) {
return TrueColorSupported;
}
}
}
return Break;
}
}
Continue
}
}
fn read_hex_string(str: &[u8]) -> Option<String> {
let mut iter = str.into_iter();
let mut vec = Vec::with_capacity(str.len() / 2);
loop {
match (iter.next(), iter.next()) {
(Some(left), Some(right)) => {
let chunk = [*left, *right];
let hex = std::str::from_utf8(&chunk).ok()?;
vec.push(u8::from_str_radix(hex, 16).ok()?)
}
_ => break,
}
}
Some(std::str::from_utf8(&vec).ok()?.to_owned())
}

View File

@ -2,6 +2,12 @@ use std::ops::BitAnd;
use super::Mouse;
#[derive(Debug)]
pub enum TerminalEvent {
Name(String),
TrueColorSupported,
}
#[derive(Debug)]
pub enum Event {
KeyPress { key: u8 },
@ -9,36 +15,34 @@ pub enum Event {
MouseDown { row: usize, col: usize },
MouseMove { row: usize, col: usize },
Scroll { delta: isize },
Terminal(TerminalEvent),
Exit,
}
impl Event {
pub fn from(mouse: &mut Mouse, release: bool) -> Option<Event> {
if !mouse.read() {
return None;
}
pub fn parse(mouse: &mut Mouse, release: bool) -> Option<Event> {
mouse.parse()?;
match (mouse.btn, mouse.col, mouse.row) {
(Some(btn), Some(col), Some(row)) => Some({
if Mask::ScrollDown & btn {
Event::Scroll { delta: -1 }
} else if Mask::ScrollUp & btn {
Event::Scroll { delta: 1 }
let btn = mouse.btn?;
Some({
if Mask::ScrollDown & btn {
Event::Scroll { delta: -1 }
} else if Mask::ScrollUp & btn {
Event::Scroll { delta: 1 }
} else {
let col = mouse.col? as usize - 1;
let row = mouse.row? as usize - 1;
if release {
Event::MouseUp { row, col }
} else if Mask::MouseMove & btn {
Event::MouseMove { row, col }
} else {
let col = col as usize - 1;
let row = row as usize - 1;
if release {
Event::MouseUp { row, col }
} else if Mask::MouseMove & btn {
Event::MouseMove { row, col }
} else {
Event::MouseDown { row, col }
}
Event::MouseDown { row, col }
}
}),
_ => None,
}
}
})
}
}

View File

@ -2,10 +2,6 @@ use std::io::{self, Read};
use crate::terminal::input::*;
pub fn setup() -> io::Result<()> {
raw_tty::setup()
}
/// Listen for input events in stdin.
/// This will block, so it should run from a dedicated thread.
pub fn listen<T, F>(mut callback: F) -> io::Result<T>

View File

@ -1,64 +1,37 @@
use super::Event;
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct Mouse {
pub buf: Option<Vec<u8>>,
pub btn: Option<u32>,
pub col: Option<u32>,
pub row: Option<u32>,
buf: Vec<u8>,
}
impl Mouse {
pub fn start(&mut self) {
self.buf = Some(Vec::new())
}
pub fn end(&mut self, key: u8, events: &mut Vec<Event>) {
if let Some(event) = Event::from(self, key == 0x6d) {
events.push(event)
pub fn new() -> Self {
Mouse {
btn: None,
col: None,
row: None,
buf: Vec::new(),
}
}
pub fn reset(&mut self) {
self.buf = None;
self.btn = None;
self.col = None;
self.row = None;
pub fn push(&mut self, char: u8) {
self.buf.push(char)
}
pub fn read(&mut self) -> bool {
if self.parse() == None {
self.reset();
pub fn parse(&mut self) -> Option<()> {
let buf = std::mem::take(&mut self.buf);
let str = std::str::from_utf8(&buf).ok()?;
let num = Some(str.parse().ok()?);
false
} else {
true
match (self.btn, self.col, self.row) {
(None, _, _) => self.btn = num,
(_, None, _) => self.col = num,
(_, _, None) => self.row = num,
_ => return None,
}
}
fn parse(&mut self) -> Option<()> {
if let Some(ref buf) = self.buf {
let string = std::str::from_utf8(buf).ok()?;
let data = string.parse().ok()?;
self.update(data)
} else {
None
}
}
fn update(&mut self, data: u32) -> Option<()> {
if self.btn == None {
self.btn = Some(data)
} else if self.col == None {
self.col = Some(data)
} else if self.row == None {
self.row = Some(data)
} else {
return None;
}
self.buf = Some(Vec::new());
return Some(());
}

View File

@ -1,92 +1,116 @@
use crate::terminal::input::*;
#[derive(Clone)]
enum State {
CharSequence,
EscapeSequence,
ControlSequence,
MouseSequence(Mouse),
DeviceControlSequence(DeviceControl),
}
pub struct Parser {
esc: bool,
csi: bool,
mouse: Mouse,
state: State,
}
impl Parser {
pub fn new() -> Parser {
Parser {
esc: false,
csi: false,
mouse: Mouse {
buf: None,
btn: None,
col: None,
row: None,
},
state: State::CharSequence,
}
}
pub fn parse(&mut self, input: &[u8]) -> Vec<Event> {
let mut events = Vec::new();
let Parser {
mut esc,
mut csi,
ref mut mouse,
} = self;
let mut emit = |e| events.push(e);
let mut state = self.state.clone();
use Event::*;
use State::*;
for &key in input {
if esc {
// Inside an escape sequence
if let Some(ref mut buf) = mouse.buf {
// Process mouse input sequence
match key {
// Delimiter: concat characters, parse number, and clear buffer
0x3b => esc = mouse.read(),
// Terminator: emit mouse move, movement, or release based on terminator
0x4d | 0x6d => {
mouse.end(key, &mut events);
match state {
CharSequence => match key {
// ESC character, start an escape sequence
0x1b => state = EscapeSequence,
// CTRL-C pressed
0x03 => emit(Exit),
// Any other character should be parsed as text input
key => emit(KeyPress { key }),
},
EscapeSequence => match key {
// CSI
b'[' => state = ControlSequence,
// DCS
b'P' => state = DeviceControlSequence(DeviceControl::new()),
key => {
// Unrecognized sequence, emit an escape keypress
emit(KeyPress { key: 0x1b });
esc = false
// If this isn't an escape character, emit a
// keypress for this key and close the sequence
if key != 0x1b {
state = CharSequence;
emit(KeyPress { key });
}
// Consider anything else part of the value
_ => buf.push(key),
}
} else if csi {
// Inside a control sequence
match key {
// Mouse input
0x3c => mouse.start(),
// Map arrow keys events to key codes
0x41..=0x44 => events.push(Event::KeyPress {
key: [0x26, 0x28, 0x27, 0x25][(key - 0x41) as usize],
}),
// Ignore anything else
_ => esc = false,
}
} else if key == 0x5b {
// [ character, start a CSI sequence
csi = true
} else {
// Unrecognized sequence, emit an ESC keypress
events.push(Event::KeyPress { key: 0x1b });
},
ControlSequence => match key {
// Mouse input
b'<' => state = MouseSequence(Mouse::new()),
_ => {
state = CharSequence;
if key != 0x1b {
// Cancel the sequence only if this isn't an ESC character
esc = false;
events.push(Event::KeyPress { key });
match key {
// Map arrow keys events to key codes
b'A' => emit(KeyPress { key: 0x26 }),
b'B' => emit(KeyPress { key: 0x28 }),
b'C' => emit(KeyPress { key: 0x27 }),
b'D' => emit(KeyPress { key: 0x25 }),
// Ignore anything else
_ => continue,
}
}
}
} else if key == 0x1b {
// ESC character, start an escape sequence
esc = true;
csi = false;
mouse.reset();
} else if key == 0x03 {
// CTRL-C pressed
events.push(Event::Exit)
} else {
// Any other character should be parser as text input
events.push(Event::KeyPress { key })
},
MouseSequence(ref mut mouse) => match key {
// Delimiter
b';' => {
if mouse.parse() == None {
state = CharSequence
}
}
// Terminator
b'm' | b'M' => {
if let Some(event) = Event::parse(mouse, key == b'm') {
emit(event)
}
state = CharSequence
}
// Consider anything else part of the value
key => mouse.push(key),
},
DeviceControlSequence(ref mut dcs) => match dcs.parse(key) {
DeviceControlEvent::Continue => continue,
event => {
state = CharSequence;
match event {
DeviceControlEvent::TerminalName(name) => {
emit(Terminal(TerminalEvent::Name(name)))
}
DeviceControlEvent::TrueColorSupported => {
emit(Terminal(TerminalEvent::TrueColorSupported))
}
DeviceControlEvent::Break | DeviceControlEvent::Continue => continue,
}
}
},
}
}
self.esc = esc;
self.csi = csi;
self.state = state;
events
}

View File

@ -1,40 +0,0 @@
use std::os::unix::prelude::AsRawFd;
use std::{fs, io};
/// Setup the input stream to operate in raw mode.
/// Allows for reading characters without waiting for the return key to be pressed.
pub fn setup() -> io::Result<()> {
unsafe {
let tty;
let fd = if libc::isatty(libc::STDIN_FILENO) == 1 {
libc::STDIN_FILENO
} else {
// Use /dev/tty in the input stream is not a terminal.
// Happens if something is piped to stdin.
tty = fs::File::open("/dev/tty")?;
tty.as_raw_fd()
};
let mut ptr = core::mem::MaybeUninit::uninit();
// Load the terminal parameters
if libc::tcgetattr(fd, ptr.as_mut_ptr()) == 0 {
let mut termios = ptr.assume_init();
let c_oflag = termios.c_oflag;
// Set the terminal to raw mode
libc::cfmakeraw(&mut termios);
// Restore output flags, ensures carriage returns are consistent
termios.c_oflag = c_oflag;
// Save the terminal parameters
if libc::tcsetattr(fd, libc::TCSANOW, &termios) == 0 {
return Ok(());
}
}
}
Err(io::Error::last_os_error())
}

171
src/terminal/input/tty.rs Normal file
View File

@ -0,0 +1,171 @@
use std::fs::File;
use std::io;
use std::io::Write;
use std::mem::MaybeUninit;
use std::os::fd::RawFd;
use std::os::unix::prelude::AsRawFd;
pub struct Terminal {
settings: Option<TerminalSettings>,
alt_screen: bool,
}
impl Drop for Terminal {
fn drop(&mut self) {
self.teardown()
}
}
impl Terminal {
/// Setup the input stream to operate in raw mode.
/// Returns an object that'll revert terminal settings.
pub fn setup() -> Self {
Self {
settings: match TerminalSettings::open_raw() {
Ok(settings) => Some(settings),
Err(error) => {
eprintln!("Failed to setup terminal: {error}");
None
}
},
alt_screen: if let Err(error) = TTY::enter_alt_screen() {
eprintln!("Failed to enter alternative screen: {error}");
false
} else {
true
},
}
}
pub fn teardown(&mut self) {
if let Some(ref settings) = self.settings {
if let Err(error) = settings.apply() {
eprintln!("Failed to revert terminal settings: {error}");
}
self.settings = None;
}
if self.alt_screen {
if let Err(error) = TTY::quit_alt_screen() {
eprintln!("Failed to quit alternative screen: {error}");
}
self.alt_screen = false;
}
}
}
enum TTY {
Raw(RawFd),
File(File),
}
const SEQUENCES: [(u32, bool); 4] = [(1049, true), (1003, true), (1006, true), (25, false)];
impl TTY {
fn stdin() -> TTY {
let isatty = unsafe { libc::isatty(libc::STDIN_FILENO) };
if isatty != 1 {
if let Ok(file) = File::open("/dev/tty") {
return TTY::File(file);
}
}
TTY::Raw(libc::STDIN_FILENO)
}
fn enter_alt_screen() -> io::Result<()> {
let mut out = io::stdout();
for (sequence, enable) in SEQUENCES {
write!(out, "\x1b[?{}{}", sequence, if enable { "h" } else { "l" })?;
}
write!(out, "\x1b[48;2;1;2;3m\x1bP$qm\x1b\\\x1bP+q544e\x1b\\")?;
out.flush()
}
fn quit_alt_screen() -> io::Result<()> {
let mut out = io::stdout();
for (sequence, enable) in SEQUENCES {
write!(out, "\x1b[?{}{}", sequence, if enable { "l" } else { "h" })?;
}
out.flush()
}
fn as_raw_fd(self) -> RawFd {
match self {
TTY::Raw(fd) => fd,
TTY::File(file) => file.as_raw_fd(),
}
}
}
trait ToErr {
fn to_err(self) -> io::Result<()>;
}
impl ToErr for libc::c_int {
fn to_err(self) -> io::Result<()> {
if self == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
}
/// Safe wrapper around libc::termios
#[derive(Clone)]
struct TerminalSettings {
data: libc::termios,
}
impl TerminalSettings {
/// Fetch settings from the current TTY
fn open() -> io::Result<Self> {
let tty = TTY::stdin();
let mut term = MaybeUninit::uninit();
let data = unsafe {
libc::tcgetattr(tty.as_raw_fd(), term.as_mut_ptr()).to_err()?;
term.assume_init()
};
Ok(Self { data })
}
fn open_raw() -> io::Result<TerminalSettings> {
let mut raw = Self::open()?;
let settings = raw.clone();
raw.make_raw();
raw.apply()?;
Ok(settings)
}
/// Enable raw input
fn make_raw(&mut self) {
let c_oflag = self.data.c_oflag;
// Set the terminal to raw mode
unsafe { libc::cfmakeraw(&mut self.data) }
// Restore output flags, ensures carriage returns are consistent
self.data.c_oflag = c_oflag;
}
/// Apply the settings to the current TTY
fn apply(&self) -> io::Result<()> {
let tty = TTY::stdin();
unsafe { libc::tcsetattr(tty.as_raw_fd(), libc::TCSANOW, &self.data).to_err() }
}
}

View File

@ -29,18 +29,14 @@ impl Painter {
buffer: Vec::new(),
cursor: None,
output: io::stdout(),
true_color: if let Ok(value) = std::env::var("COLORTERM") {
match value.as_str() {
"truecolor" | "24bit" => true,
_ => false,
}
} else {
false
},
background: None,
foreground: None,
background_code: None,
foreground_code: None,
true_color: match std::env::var("COLORTERM").unwrap_or_default().as_str() {
"truecolor" | "24bit" => true,
_ => false,
},
}
}
@ -48,6 +44,10 @@ impl Painter {
self.true_color
}
pub fn set_true_color(&mut self, true_color: bool) {
self.true_color = true_color
}
pub fn flush(&mut self) -> io::Result<()> {
self.output.write(self.buffer.as_slice())?;
self.output.flush()?;

View File

@ -1,15 +1,9 @@
use std::{
io::{self, Write as _},
rc::Rc,
};
use std::{io, rc::Rc};
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
use crate::{
gfx::{Color, Point, Rect, Size},
terminal,
};
use crate::gfx::{Color, Point, Rect, Size};
use super::{Cell, Grapheme, Painter};
@ -28,8 +22,6 @@ pub struct Renderer {
painter: Painter,
}
const SEQUENCES: [(u32, bool); 4] = [(1049, true), (1003, true), (1006, true), (25, false)];
impl Renderer {
pub fn new() -> Renderer {
Renderer {
@ -43,30 +35,8 @@ impl Renderer {
}
}
pub fn setup() -> io::Result<()> {
terminal::input::setup()?;
let mut out = io::stdout();
for (sequence, enable) in SEQUENCES {
write!(out, "\x1b[?{}{}", sequence, if enable { "h" } else { "l" })?;
}
out.flush()?;
Ok(())
}
pub fn teardown() -> io::Result<()> {
let mut out = io::stdout();
for (sequence, enable) in SEQUENCES {
write!(out, "\x1b[?{}{}", sequence, if enable { "l" } else { "h" })?;
}
out.flush()?;
Ok(())
pub fn enable_true_color(&mut self) {
self.painter.set_true_color(true)
}
pub fn set_size(&mut self, cell: Size, terminal: Size) {

View File

@ -2,13 +2,21 @@ use crate::gfx::Color;
impl Color {
pub fn to_xterm(&self) -> u8 {
if self.r == self.g && self.g == self.b && self.r > 4 && self.r < 239 {
232 + (self.r - 8) / 10
if self.max_val() - self.min_val() < 5 {
match self.r {
r if r < 4 => 16,
r if r < 8 => 232,
r if r > 246 => 231,
r if r > 238 => 255,
r => 232 + (r - 8) / 10,
}
} else {
let scale = 5.0 / 200.0;
(16.0
+ self
.cast::<f32>()
.mul_add(5.0 / 200.0, -(55.0 * (5.0 / 200.0)))
.mul_add(scale, -55.0 * scale)
.max(0.0)
.round()
.dot((36.0, 6.0, 1.0))) as u8