mirror of
https://github.com/fathyb/carbonyl.git
synced 2025-01-07 03:07:10 +08:00
feat: better true color detection
This commit is contained in:
parent
b7d3b74ace
commit
ef6429d881
@ -1,3 +0,0 @@
|
||||
[build]
|
||||
rustflags = ["-L", "/Users/fathy/Git/carbonyl/chromium/src/out/Default/obj/headless"]
|
||||
target-dir = "build"
|
3
.dockerignore
Normal file
3
.dockerignore
Normal file
@ -0,0 +1,3 @@
|
||||
/build
|
||||
!/build/browser
|
||||
/chromium
|
75
Dockerfile
75
Dockerfile
@ -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"]
|
||||
|
@ -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
81
src/browser/bridge.cc
Normal 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
60
src/browser/bridge.h
Normal 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_
|
@ -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
|
||||
|
@ -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) {}
|
||||
|
@ -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_
|
||||
|
26
src/browser/render_service_impl.cc
Normal file
26
src/browser/render_service_impl.cc
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
27
src/browser/render_service_impl.h
Normal file
27
src/browser/render_service_impl.h
Normal 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_
|
155
src/browser/software_output_device_proxy.cc
Normal file
155
src/browser/software_output_device_proxy.cc
Normal 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
|
91
src/browser/software_output_device_proxy.h
Normal file
91
src/browser/software_output_device_proxy.h
Normal 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
@ -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>,
|
||||
|
@ -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())
|
||||
|
@ -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
208
src/terminal/input/dcs.rs
Normal 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())
|
||||
}
|
@ -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,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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(());
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
171
src/terminal/input/tty.rs
Normal 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() }
|
||||
}
|
||||
}
|
@ -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()?;
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user