Compare commits

..

8 Commits

Author SHA1 Message Date
Bananymous e4982a1a5c LibC: Fix printf with alternate format and zero values 2024-09-26 15:25:16 +03:00
Bananymous cea6dedccc Userspace: Compile programs and libraries with -Wall -Wextra -Werror 2024-09-26 15:20:07 +03:00
Bananymous e6ed5a388d BuildSystem: Export compile commands for clangd 2024-09-26 15:14:54 +03:00
Bananymous b89fc3fe87 Kernel: Implement ANSI SGR 7 to invert colors
This allows vim's visual selection to show up
2024-09-26 15:08:11 +03:00
Bananymous 57ae74f908 Terminal: Implement more ANSI escape handling
This patch adds L and M codes for inserting and deleting lines and SGR 7
for inverting colors
2024-09-26 15:07:08 +03:00
Bananymous 1a6804b4b4 Terminal: Make Terminal::handle_csi() return invalidated rectangle
When I updated Terminal to only do a single invalidation after all of
input text was printed, I forgot to change handle_csi
2024-09-26 15:05:11 +03:00
Bananymous 82e6a3582d LibGUI: cleanup Window::shift_vertical and add copy_horizontal_slice 2024-09-26 15:00:14 +03:00
Bananymous 11a4e4faa2 LibImage: Add Adam7 support for PNG decoder 2024-09-26 11:47:34 +03:00
14 changed files with 379 additions and 209 deletions

13
.clangd Normal file
View File

@ -0,0 +1,13 @@
Diagnostics:
Suppress: target_unsupported_type
CompileFlags:
Remove: [
-fstrict-volatile-bitfields,
-fno-tree-loop-distribute-patterns
]
Add: [
-D__banan_os__,
-D__arch__=x86_64,
-D__x86_64__
]

View File

@ -2,5 +2,9 @@
"cmake.configureOnOpen": false, "cmake.configureOnOpen": false,
"editor.tabSize": 4, "editor.tabSize": 4,
"editor.insertSpaces": false, "editor.insertSpaces": false,
"editor.detectIndentation": false "editor.detectIndentation": false,
} "clangd.arguments": [
"--compile-commands-dir=${workspaceFolder}/build",
"-header-insertion=never"
]
}

View File

@ -29,6 +29,8 @@ set(CMAKE_STATIC_LIBRARY_PREFIX "")
set(CMAKE_SHARED_LIBRARY_PREFIX "") set(CMAKE_SHARED_LIBRARY_PREFIX "")
set(BUILD_SHARED_LIBS True) set(BUILD_SHARED_LIBS True)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}") set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}") set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}")

View File

@ -121,6 +121,10 @@ namespace Kernel
m_background = TerminalColor::BLACK; m_background = TerminalColor::BLACK;
break; break;
case 7:
BAN::swap(m_foreground, m_background);
break;
case 30: m_foreground = TerminalColor::BLACK; break; case 30: m_foreground = TerminalColor::BLACK; break;
case 31: m_foreground = TerminalColor::RED; break; case 31: m_foreground = TerminalColor::RED; break;
case 32: m_foreground = TerminalColor::GREEN; break; case 32: m_foreground = TerminalColor::GREEN; break;

View File

@ -20,7 +20,7 @@ foreach(library ${USERSPACE_LIBRARIES})
# This is to allow cmake to link when libc updates # This is to allow cmake to link when libc updates
target_link_options(${library_lower} PRIVATE -nolibc) target_link_options(${library_lower} PRIVATE -nolibc)
# Default compile options # Default compile options
target_compile_options(${library_lower} PRIVATE -g -O2) target_compile_options(${library_lower} PRIVATE -g -O2 -Wall -Wextra -Werror)
target_compile_definitions(${library_lower} PRIVATE __enable_sse=${BANAN_ENABLE_SSE}) target_compile_definitions(${library_lower} PRIVATE __enable_sse=${BANAN_ENABLE_SSE})
if (NOT BANAN_ENABLE_SSE) if (NOT BANAN_ENABLE_SSE)

View File

@ -50,6 +50,16 @@ static void integer_to_string(char* buffer, T value, int base, bool upper, forma
options.zero_padded = false; options.zero_padded = false;
} }
if (value == 0 && options.alternate_form)
{
if (digits == 0 && base == 8)
digits = 1;
for (int i = 0; i < digits; i++)
buffer[i] = '0';
buffer[digits] = '\0';
return;
}
auto digit_char = [](int digit, bool upper) auto digit_char = [](int digit, bool upper)
{ {
if (digit < 10) if (digit < 10)
@ -83,6 +93,7 @@ static void integer_to_string(char* buffer, T value, int base, bool upper, forma
{ {
prefix_length = 1; prefix_length = 1;
prefix[0] = '0'; prefix[0] = '0';
digits--;
} }
else if (options.alternate_form && base == 16) else if (options.alternate_form && base == 16)
{ {
@ -361,7 +372,9 @@ extern "C" int printf_impl(const char* format, va_list arguments, int (*putc_fun
format--; format--;
format++; format++;
char conversion[128]; // FIXME: this should be thread-local to keep
// satisfy multithreaded requirement
static char conversion[4096];
const char* string = nullptr; const char* string = nullptr;
int length = -1; int length = -1;

View File

@ -135,14 +135,53 @@ namespace LibGUI
draw_character(text[i], font, tl_x + (int32_t)(i * font.width()), tl_y, color); draw_character(text[i], font, tl_x + (int32_t)(i * font.width()), tl_y, color);
} }
void Window::shift_vertical(int32_t amount) void Window::shift_vertical(int32_t amount, uint32_t fill_color)
{ {
uint32_t amount_abs = BAN::Math::abs(amount); const uint32_t amount_abs = BAN::Math::abs(amount);
if (amount_abs == 0 || amount_abs >= height()) if (amount_abs == 0 || amount_abs >= height())
return; return;
uint32_t* dst = (amount > 0) ? m_framebuffer.data() + width() * amount_abs : m_framebuffer.data(); uint32_t* dst = (amount > 0) ? m_framebuffer.data() + width() * amount_abs : m_framebuffer.data();
uint32_t* src = (amount < 0) ? m_framebuffer.data() + width() * amount_abs : m_framebuffer.data(); uint32_t* src = (amount < 0) ? m_framebuffer.data() + width() * amount_abs : m_framebuffer.data();
memmove(dst, src, width() * (height() - amount_abs) * 4); memmove(dst, src, width() * (height() - amount_abs) * 4);
const uint32_t y_lo = (amount < 0) ? height() - amount_abs : 0;
const uint32_t y_hi = (amount < 0) ? height() : amount_abs;
for (uint32_t y = y_lo; y < y_hi; y++)
for (uint32_t x = 0; x < width(); x++)
set_pixel(x, y, fill_color);
}
void Window::copy_horizontal_slice(int32_t dst_y, int32_t src_y, uint32_t uamount, uint32_t fill_color)
{
int32_t amount = uamount;
if (dst_y < 0)
{
amount -= -dst_y;
src_y += -dst_y;
dst_y = 0;
}
amount = BAN::Math::min<int32_t>(amount, height() - dst_y);
if (amount <= 0)
return;
const int32_t copy_src_y = BAN::Math::clamp<int32_t>(src_y, 0, height());
const int32_t copy_amount = BAN::Math::clamp<int32_t>(src_y + amount, 0, height()) - copy_src_y;
if (copy_amount > 0)
{
memmove(
&m_framebuffer[width() * (dst_y + (copy_src_y - src_y))],
&m_framebuffer[width() * copy_src_y],
copy_amount * width() * 4
);
}
const uint32_t fill_y_off = (src_y < copy_src_y) ? 0 : copy_amount;
const uint32_t fill_amount = amount - copy_amount;
for (uint32_t i = 0; i < fill_amount; i++)
for (uint32_t x = 0; x < width(); x++)
set_pixel(x, dst_y + fill_y_off + i, fill_color);
} }
bool Window::clamp_to_framebuffer(int32_t& signed_x, int32_t& signed_y, uint32_t& width, uint32_t& height) const bool Window::clamp_to_framebuffer(int32_t& signed_x, int32_t& signed_y, uint32_t& width, uint32_t& height) const

View File

@ -125,7 +125,13 @@ namespace LibGUI
void draw_character(uint32_t codepoint, const LibFont::Font& font, int32_t x, int32_t y, uint32_t color); void draw_character(uint32_t codepoint, const LibFont::Font& font, int32_t x, int32_t y, uint32_t color);
void draw_text(BAN::StringView text, const LibFont::Font& font, int32_t x, int32_t y, uint32_t color); void draw_text(BAN::StringView text, const LibFont::Font& font, int32_t x, int32_t y, uint32_t color);
void shift_vertical(int32_t amount); // shift whole vertically by amount pixels, sign determines the direction
// fill_color is used to fill "new" data
void shift_vertical(int32_t amount, uint32_t fill_color);
// copy horizontal slice [src_y, src_y + amount[ to [dst_y, dst_y + amount[
// fill_color is used when copying data outside of window bounds
void copy_horizontal_slice(int32_t dst_y, int32_t src_y, uint32_t amount, uint32_t fill_color);
bool invalidate(int32_t x, int32_t y, uint32_t width, uint32_t height); bool invalidate(int32_t x, int32_t y, uint32_t width, uint32_t height);
bool invalidate() { return invalidate(0, 0, width(), height()); } bool invalidate() { return invalidate(0, 0, width(), height()); }

View File

@ -522,6 +522,159 @@ namespace LibImage
} }
} }
static BAN::ErrorOr<uint64_t> parse_pixel_data(BAN::Vector<Image::Color>& color_bitmap, uint64_t image_width, uint64_t image_height, const IHDR& ihdr, const BAN::Vector<Image::Color>& palette, BAN::ByteSpan encoded_data)
{
ASSERT(color_bitmap.size() >= image_height * image_width);
const uint8_t bits_per_channel = ihdr.bit_depth;
const uint8_t channels =
[&]() -> uint8_t
{
switch (ihdr.colour_type)
{
case ColourType::Greyscale: return 1;
case ColourType::Truecolour: return 3;
case ColourType::IndexedColour: return 1;
case ColourType::GreyscaleAlpha: return 2;
case ColourType::TruecolourAlpha: return 4;
default:
ASSERT_NOT_REACHED();
}
}();
const auto extract_channel =
[&](auto& bit_buffer) -> uint8_t
{
uint16_t tmp = MUST(bit_buffer.get_bits(bits_per_channel));
switch (bits_per_channel)
{
case 1: return tmp * 0xFF;
case 2: return tmp * 0xFF / 3;
case 4: return tmp * 0xFF / 15;
case 8: return tmp;
case 16: return tmp & 0xFF; // NOTE: stored in big endian
}
ASSERT_NOT_REACHED();
};
const auto extract_color =
[&](auto& bit_buffer) -> Image::Color
{
uint8_t tmp;
switch (ihdr.colour_type)
{
case ColourType::Greyscale:
tmp = extract_channel(bit_buffer);
return Image::Color {
.r = tmp,
.g = tmp,
.b = tmp,
.a = 0xFF
};
case ColourType::Truecolour:
return Image::Color {
.r = extract_channel(bit_buffer),
.g = extract_channel(bit_buffer),
.b = extract_channel(bit_buffer),
.a = 0xFF
};
case ColourType::IndexedColour:
return palette[MUST(bit_buffer.get_bits(bits_per_channel))];
case ColourType::GreyscaleAlpha:
tmp = extract_channel(bit_buffer);
return Image::Color {
.r = tmp,
.g = tmp,
.b = tmp,
.a = extract_channel(bit_buffer)
};
case ColourType::TruecolourAlpha:
return Image::Color {
.r = extract_channel(bit_buffer),
.g = extract_channel(bit_buffer),
.b = extract_channel(bit_buffer),
.a = extract_channel(bit_buffer)
};
}
ASSERT_NOT_REACHED();
};
constexpr auto paeth_predictor =
[](int16_t a, int16_t b, int16_t c) -> uint8_t
{
int16_t p = a + b - c;
int16_t pa = BAN::Math::abs(p - a);
int16_t pb = BAN::Math::abs(p - b);
int16_t pc = BAN::Math::abs(p - c);
if (pa <= pb && pa <= pc)
return a;
if (pb <= pc)
return b;
return c;
};
const uint64_t bytes_per_scanline = BAN::Math::div_round_up<uint64_t>(image_width * channels * bits_per_channel, 8);
const uint64_t pitch = bytes_per_scanline + 1;
if (encoded_data.size() < pitch * image_height)
{
dwarnln_if(DEBUG_PNG, "PNG does not contain enough image data");
return BAN::Error::from_errno(ENODATA);
}
BAN::Vector<uint8_t> zero_scanline;
TRY(zero_scanline.resize(bytes_per_scanline, 0));
BAN::Vector<BAN::ConstByteSpan> encoded_data_wrapper;
TRY(encoded_data_wrapper.push_back({}));
const uint8_t filter_offset = (bits_per_channel < 8) ? 1 : channels * (bits_per_channel / 8);
for (uint64_t y = 0; y < image_height; y++)
{
auto scanline = encoded_data.slice((y - 0) * pitch + 1, bytes_per_scanline);
auto scanline_above = (y > 0) ? encoded_data.slice((y - 1) * pitch + 1, bytes_per_scanline) : BAN::ConstByteSpan(zero_scanline.span());
auto filter_type = static_cast<FilterType>(encoded_data[y * pitch]);
switch (filter_type)
{
case FilterType::None:
break;
case FilterType::Sub:
for (uint64_t x = filter_offset; x < bytes_per_scanline; x++)
scanline[x] += scanline[x - filter_offset];
break;
case FilterType::Up:
for (uint64_t x = 0; x < bytes_per_scanline; x++)
scanline[x] += scanline_above[x];
break;
case FilterType::Average:
for (uint8_t i = 0; i < filter_offset; i++)
scanline[i] += scanline_above[i] / 2;
for (uint64_t x = filter_offset; x < bytes_per_scanline; x++)
scanline[x] += ((uint16_t)scanline[x - filter_offset] + (uint16_t)scanline_above[x]) / 2;
break;
case FilterType::Paeth:
for (uint8_t i = 0; i < filter_offset; i++)
scanline[i] += paeth_predictor(0, scanline_above[i], 0);
for (uint64_t x = filter_offset; x < bytes_per_scanline; x++)
scanline[x] += paeth_predictor(scanline[x - filter_offset], scanline_above[x], scanline_above[x - filter_offset]);
break;
default:
dwarnln_if(DEBUG_PNG, "invalid filter type {}", static_cast<uint8_t>(filter_type));
return BAN::Error::from_errno(EINVAL);
}
encoded_data_wrapper[0] = scanline;
BitBuffer bit_buffer(encoded_data_wrapper);
for (uint64_t x = 0; x < image_width; x++)
color_bitmap[y * image_width + x] = extract_color(bit_buffer);
}
return pitch * image_height;
}
bool probe_png(BAN::ConstByteSpan image_data) bool probe_png(BAN::ConstByteSpan image_data)
{ {
if (image_data.size() < 8) if (image_data.size() < 8)
@ -578,12 +731,6 @@ namespace LibImage
return BAN::Error::from_errno(EINVAL); return BAN::Error::from_errno(EINVAL);
} }
if (ihdr.interlace_method == InterlaceMethod::Adam7)
{
dwarnln_if(DEBUG_PNG, "PNG with interlacing is not supported");
return BAN::Error::from_errno(ENOTSUP);
}
const uint64_t image_width = ihdr.width; const uint64_t image_width = ihdr.width;
const uint64_t image_height = ihdr.height; const uint64_t image_height = ihdr.height;
@ -708,153 +855,52 @@ namespace LibImage
dprintln_if(DEBUG_PNG, " uncompressed size {}", inflated_data.size()); dprintln_if(DEBUG_PNG, " uncompressed size {}", inflated_data.size());
dprintln_if(DEBUG_PNG, " compression ratio {}", (double)inflated_data.size() / total_size); dprintln_if(DEBUG_PNG, " compression ratio {}", (double)inflated_data.size() / total_size);
uint8_t bits_per_channel = ihdr.bit_depth; BAN::Vector<Image::Color> pixel_data;
uint8_t channels = 0; TRY(pixel_data.resize(image_width * image_height));
switch (ihdr.colour_type)
switch (ihdr.interlace_method)
{ {
case ColourType::Greyscale: channels = 1; break; case InterlaceMethod::NoInterlace:
case ColourType::Truecolour: channels = 3; break; TRY(parse_pixel_data(pixel_data, image_width, image_height, ihdr, palette, inflated_data));
case ColourType::IndexedColour: channels = 1; break; break;
case ColourType::GreyscaleAlpha: channels = 2; break; case InterlaceMethod::Adam7:
case ColourType::TruecolourAlpha: channels = 4; break; {
constexpr uint8_t x_start[] { 0, 4, 0, 2, 0, 1, 0 };
constexpr uint8_t x_increment[] { 8, 8, 4, 4, 2, 2, 1 };
constexpr uint8_t y_start[] { 0, 0, 4, 0, 2, 0, 1 };
constexpr uint8_t y_increment[] { 8, 8, 8, 4, 4, 2, 2 };
BAN::Vector<Image::Color> pass_pixel_data;
TRY(pass_pixel_data.resize(((image_height + 1) / 2) * image_width));
for (int pass = 0; pass < 7; pass++)
{
const uint64_t pass_width = BAN::Math::div_round_up<uint64_t>(image_width - x_start[pass], x_increment[pass]);
const uint64_t pass_height = BAN::Math::div_round_up<uint64_t>(image_height - y_start[pass], y_increment[pass]);
const uint64_t nparsed = TRY(parse_pixel_data(pass_pixel_data, pass_width, pass_height, ihdr, palette, inflated_data));
for (uint64_t y = 0; y < pass_height; y++)
{
for (uint64_t x = 0; x < pass_width; x++)
{
const uint64_t abs_x = x * x_increment[pass] + x_start[pass];
const uint64_t abs_y = y * y_increment[pass] + y_start[pass];
pixel_data[abs_y * image_width + abs_x] = pass_pixel_data[y * pass_width + x];
}
}
dprintln_if(DEBUG_PNG, "Adam7 pass {} done ({}x{})", pass + 1, pass_width, pass_height);
inflated_data = inflated_data.slice(nparsed);
}
break;
}
default: default:
ASSERT_NOT_REACHED(); ASSERT_NOT_REACHED();
} }
const auto extract_channel = return TRY(BAN::UniqPtr<Image>::create(image_width, image_height, BAN::move(pixel_data)));
[&](auto& bit_buffer) -> uint8_t
{
uint16_t tmp = MUST(bit_buffer.get_bits(bits_per_channel));
switch (bits_per_channel)
{
case 1: return tmp * 0xFF;
case 2: return tmp * 0xFF / 3;
case 4: return tmp * 0xFF / 15;
case 8: return tmp;
case 16: return tmp & 0xFF; // NOTE: stored in big endian
}
ASSERT_NOT_REACHED();
};
const auto extract_color =
[&](auto& bit_buffer) -> Image::Color
{
uint8_t tmp;
switch (ihdr.colour_type)
{
case ColourType::Greyscale:
tmp = extract_channel(bit_buffer);
return Image::Color {
.r = tmp,
.g = tmp,
.b = tmp,
.a = 0xFF
};
case ColourType::Truecolour:
return Image::Color {
.r = extract_channel(bit_buffer),
.g = extract_channel(bit_buffer),
.b = extract_channel(bit_buffer),
.a = 0xFF
};
case ColourType::IndexedColour:
return palette[MUST(bit_buffer.get_bits(bits_per_channel))];
case ColourType::GreyscaleAlpha:
tmp = extract_channel(bit_buffer);
return Image::Color {
.r = tmp,
.g = tmp,
.b = tmp,
.a = extract_channel(bit_buffer)
};
case ColourType::TruecolourAlpha:
return Image::Color {
.r = extract_channel(bit_buffer),
.g = extract_channel(bit_buffer),
.b = extract_channel(bit_buffer),
.a = extract_channel(bit_buffer)
};
}
ASSERT_NOT_REACHED();
};
constexpr auto paeth_predictor =
[](int16_t a, int16_t b, int16_t c) -> uint8_t
{
int16_t p = a + b - c;
int16_t pa = BAN::Math::abs(p - a);
int16_t pb = BAN::Math::abs(p - b);
int16_t pc = BAN::Math::abs(p - c);
if (pa <= pb && pa <= pc)
return a;
if (pb <= pc)
return b;
return c;
};
const uint64_t bytes_per_scanline = BAN::Math::div_round_up<uint64_t>(image_width * channels * bits_per_channel, 8);
const uint64_t pitch = bytes_per_scanline + 1;
if (inflated_data.size() < pitch * image_height)
{
dwarnln_if(DEBUG_PNG, "PNG does not contain enough image data");
return BAN::Error::from_errno(ENODATA);
}
BAN::Vector<uint8_t> zero_scanline;
TRY(zero_scanline.resize(bytes_per_scanline, 0));
BAN::Vector<Image::Color> color_bitmap;
TRY(color_bitmap.resize(image_width * image_height));
BAN::Vector<BAN::ConstByteSpan> inflated_data_wrapper;
TRY(inflated_data_wrapper.push_back({}));
const uint8_t filter_offset = (bits_per_channel < 8) ? 1 : channels * (bits_per_channel / 8);
for (uint64_t y = 0; y < image_height; y++)
{
auto scanline = inflated_data.slice((y - 0) * pitch + 1, bytes_per_scanline);
auto scanline_above = (y > 0) ? inflated_data.slice((y - 1) * pitch + 1, bytes_per_scanline) : BAN::ConstByteSpan(zero_scanline.span());
auto filter_type = static_cast<FilterType>(inflated_data[y * pitch]);
switch (filter_type)
{
case FilterType::None:
break;
case FilterType::Sub:
for (uint64_t x = filter_offset; x < bytes_per_scanline; x++)
scanline[x] += scanline[x - filter_offset];
break;
case FilterType::Up:
for (uint64_t x = 0; x < bytes_per_scanline; x++)
scanline[x] += scanline_above[x];
break;
case FilterType::Average:
for (uint8_t i = 0; i < filter_offset; i++)
scanline[i] += scanline_above[i] / 2;
for (uint64_t x = filter_offset; x < bytes_per_scanline; x++)
scanline[x] += ((uint16_t)scanline[x - filter_offset] + (uint16_t)scanline_above[x]) / 2;
break;
case FilterType::Paeth:
for (uint8_t i = 0; i < filter_offset; i++)
scanline[i] += paeth_predictor(0, scanline_above[i], 0);
for (uint64_t x = filter_offset; x < bytes_per_scanline; x++)
scanline[x] += paeth_predictor(scanline[x - filter_offset], scanline_above[x], scanline_above[x - filter_offset]);
break;
default:
dwarnln_if(DEBUG_PNG, "invalid filter type {}", static_cast<uint8_t>(filter_type));
return BAN::Error::from_errno(EINVAL);
}
inflated_data_wrapper[0] = scanline;
BitBuffer bit_buffer(inflated_data_wrapper);
for (uint64_t x = 0; x < image_width; x++)
color_bitmap[y * image_width + x] = extract_color(bit_buffer);
}
return TRY(BAN::UniqPtr<Image>::create(image_width, image_height, BAN::move(color_bitmap)));
} }
} }

View File

@ -43,7 +43,7 @@ foreach(project ${USERSPACE_PROGRAMS})
# This is to allow cmake to link when libc updates # This is to allow cmake to link when libc updates
target_link_options(${project} PRIVATE -nolibc) target_link_options(${project} PRIVATE -nolibc)
# Default compile options # Default compile options
target_compile_options(${project} PRIVATE -g -O2) target_compile_options(${project} PRIVATE -g -O2 -Wall -Wextra -Werror)
target_compile_definitions(${project} PRIVATE __enable_sse=${BANAN_ENABLE_SSE}) target_compile_definitions(${project} PRIVATE __enable_sse=${BANAN_ENABLE_SSE})
if (NOT BANAN_ENABLE_SSE) if (NOT BANAN_ENABLE_SSE)

View File

@ -222,13 +222,13 @@ bool Terminal::read_shell()
} }
// find the next ansi escape code or end of buffer // find the next ansi escape code or end of buffer
size_t non_ansi_end = i; ssize_t non_ansi_end = i;
while (non_ansi_end < nread && buffer[non_ansi_end] != '\e') while (non_ansi_end < nread && buffer[non_ansi_end] != '\e')
non_ansi_end++; non_ansi_end++;
// we only need to process maximum of `rows()` newlines. // we only need to process maximum of `rows()` newlines.
// anything before that would get overwritten anyway // anything before that would get overwritten anyway
size_t start = non_ansi_end; ssize_t start = non_ansi_end;
size_t newline_count = 0; size_t newline_count = 0;
while (start > i && newline_count < rows()) while (start > i && newline_count < rows())
newline_count += (buffer[--start] == '\n'); newline_count += (buffer[--start] == '\n');
@ -239,8 +239,7 @@ bool Terminal::read_shell()
{ {
const uint32_t scroll = m_cursor.y + newline_count - rows() + 1; const uint32_t scroll = m_cursor.y + newline_count - rows() + 1;
m_cursor.y -= scroll; m_cursor.y -= scroll;
m_window->shift_vertical(-scroll * (int32_t)m_font.height()); m_window->shift_vertical(-scroll * (int32_t)m_font.height(), m_bg_color);
m_window->fill_rect(0, m_window->height() - scroll * m_font.height(), m_window->width(), scroll * m_font.height(), m_bg_color);
should_invalidate = { 0, 0, m_window->width(), m_window->height() }; should_invalidate = { 0, 0, m_window->width(), m_window->height() };
} }
@ -270,6 +269,12 @@ void Terminal::handle_sgr()
m_bg_color = s_colors_dark[0]; m_bg_color = s_colors_dark[0];
m_fg_color = s_colors_bright[7]; m_fg_color = s_colors_bright[7];
break; break;
case 1:
// FIXME: bold
break;
case 7:
BAN::swap(m_fg_color, m_bg_color);
break;
case 30: case 31: case 32: case 33: case 34: case 35: case 36: case 37: case 30: case 31: case 32: case 33: case 34: case 35: case 36: case 37:
m_fg_color = s_colors_dark[m_csi_info.fields[0] - 30]; m_fg_color = s_colors_dark[m_csi_info.fields[0] - 30];
break; break;
@ -288,18 +293,18 @@ void Terminal::handle_sgr()
} }
} }
void Terminal::handle_csi(char ch) Rectangle Terminal::handle_csi(char ch)
{ {
if (ch == ';') if (ch == ';')
{ {
m_csi_info.index++; m_csi_info.index++;
return; return {};
} }
if (ch == '?') if (ch == '?')
{ {
m_csi_info.question = true; m_csi_info.question = true;
return; return {};
} }
if (isdigit(ch)) if (isdigit(ch))
@ -309,9 +314,10 @@ void Terminal::handle_csi(char ch)
auto& field = m_csi_info.fields[m_csi_info.index]; auto& field = m_csi_info.fields[m_csi_info.index];
field = (BAN::Math::max(field, 0) * 10) + (ch - '0'); field = (BAN::Math::max(field, 0) * 10) + (ch - '0');
} }
return; return {};
} }
Rectangle should_invalidate;
switch (ch) switch (ch)
{ {
case 'C': case 'C':
@ -328,66 +334,106 @@ void Terminal::handle_csi(char ch)
m_cursor.x = BAN::Math::clamp<int32_t>(m_csi_info.fields[0], 1, cols()) - 1; m_cursor.x = BAN::Math::clamp<int32_t>(m_csi_info.fields[0], 1, cols()) - 1;
break; break;
case 'H': case 'H':
m_cursor.y = BAN::Math::clamp<int32_t>(m_csi_info.fields[0], 1, rows()) - 1;
m_cursor.x = BAN::Math::clamp<int32_t>(m_csi_info.fields[1], 1, cols()) - 1; m_cursor.x = BAN::Math::clamp<int32_t>(m_csi_info.fields[1], 1, cols()) - 1;
m_cursor.y = BAN::Math::clamp<int32_t>(m_csi_info.fields[0], 1, rows()) - 1;
break; break;
case 'J': case 'J':
{ {
uint32_t rects[2][4] { { (uint32_t)-1 }, { (uint32_t)-1 } }; Rectangle rects[2];
size_t rect_count = 0;
if (m_csi_info.fields[0] == -1 || m_csi_info.fields[0] == 0) if (m_csi_info.fields[0] == -1 || m_csi_info.fields[0] == 0)
{ {
rects[0][0] = m_cursor.x * m_font.width(); rects[0].x = m_cursor.x * m_font.width();
rects[0][1] = m_cursor.y * m_font.height(); rects[0].y = m_cursor.y * m_font.height();
rects[0][2] = m_window->width() - rects[0][0]; rects[0].width = m_window->width() - rects[0].x;
rects[0][3] = m_font.height(); rects[0].height = m_font.height();
rects[1][0] = 0; rects[1].x = 0;
rects[1][1] = (m_cursor.y + 1) * m_font.height(); rects[1].y = (m_cursor.y + 1) * m_font.height();
rects[1][2] = m_window->width(); rects[1].width = m_window->width();
rects[1][3] = m_window->height() - rects[1][1]; rects[1].height = m_window->height() - rects[1].y;
rect_count = 2;
} }
else if (m_csi_info.fields[0] == 1) else if (m_csi_info.fields[0] == 1)
{ {
rects[0][0] = 0; rects[0].x = 0;
rects[0][1] = m_cursor.y * m_font.height(); rects[0].y = m_cursor.y * m_font.height();
rects[0][2] = m_cursor.x * m_font.width(); rects[0].width = m_cursor.x * m_font.width();
rects[0][3] = m_font.height(); rects[0].height = m_font.height();
rects[1][0] = 0; rects[1].x = 0;
rects[1][1] = 0; rects[1].y = 0;
rects[1][2] = m_window->width(); rects[1].width = m_window->width();
rects[1][3] = m_cursor.y * m_font.height(); rects[1].height = m_cursor.y * m_font.height();
rect_count = 2;
} }
else else
{ {
rects[0][0] = 0; rects[0].x = 0;
rects[0][1] = 0; rects[0].y = 0;
rects[0][2] = m_window->width(); rects[0].width = m_window->width();
rects[0][3] = m_window->height(); rects[0].height = m_window->height();
rect_count = 1;
} }
for (int i = 0; i < 2; i++) for (size_t i = 0; i < rect_count; i++)
{ {
if (rects[i][0] == (uint32_t)-1) m_window->fill_rect(rects[i].x, rects[i].y, rects[i].width, rects[i].height, m_bg_color);
continue; should_invalidate = should_invalidate.get_bounding_box(rects[i]);
m_window->fill_rect(rects[i][0], rects[i][1], rects[i][2], rects[i][3], m_bg_color);
m_window->invalidate(rects[i][0], rects[i][1], rects[i][2], rects[i][3]);
} }
break; break;
} }
case 'K': case 'K':
{ {
m_csi_info.fields[0] = BAN::Math::max(m_csi_info.fields[0], 0); m_csi_info.fields[0] = BAN::Math::max(m_csi_info.fields[0], 0);
uint32_t rect[4]; Rectangle rect;
rect[0] = (m_csi_info.fields[0] == 0) ? m_cursor.x * m_font.width() : 0; rect.x = (m_csi_info.fields[0] == 0) ? m_cursor.x * m_font.width() : 0;
rect[1] = m_cursor.y * m_font.height(); rect.y = m_cursor.y * m_font.height();
rect[2] = (m_csi_info.fields[0] == 1) ? m_cursor.x * m_font.width() : m_window->width() - rect[0]; rect.width = (m_csi_info.fields[0] == 1) ? m_cursor.x * m_font.width() : m_window->width() - rect.x;
rect[3] = m_font.height(); rect.height = m_font.height();
m_window->fill_rect(rect[0], rect[1], rect[2], rect[3], m_bg_color); m_window->fill_rect(rect.x, rect.y, rect.width, rect.height, m_bg_color);
m_window->invalidate(rect[0], rect[1], rect[2], rect[3]); should_invalidate = rect;
break;
}
case 'L':
{
const uint32_t count = (m_csi_info.fields[0] == -1) ? 1 : m_csi_info.fields[0];
const uint32_t src_y = m_cursor.y * m_font.height();
const uint32_t dst_y = src_y + count * m_font.height();
m_window->copy_horizontal_slice(dst_y, src_y, m_window->height() - dst_y, m_bg_color);
m_window->fill_rect(0, src_y, m_window->width(), count * m_font.height(), m_bg_color);
should_invalidate = {
0,
src_y,
m_window->width(),
m_window->height() - src_y
};
break;
}
case 'M':
{
const uint32_t count = (m_csi_info.fields[0] == -1) ? 1 : m_csi_info.fields[0];
const uint32_t dst_y = m_cursor.y * m_font.height();
const uint32_t src_y = dst_y + count * m_font.height();
m_window->copy_horizontal_slice(dst_y, src_y, m_window->height() - dst_y, m_bg_color);
m_window->fill_rect(0, m_window->height() - count * m_font.height(), m_window->width(), count * m_font.height(), m_bg_color);
should_invalidate = {
0,
src_y,
m_window->width(),
m_window->height() - src_y
};
break; break;
} }
@ -413,7 +459,9 @@ void Terminal::handle_csi(char ch)
dprintln("TODO: CSI {}", ch); dprintln("TODO: CSI {}", ch);
break; break;
} }
m_state = State::Normal; m_state = State::Normal;
return should_invalidate;
} }
Rectangle Terminal::putchar(uint8_t ch) Rectangle Terminal::putchar(uint8_t ch)
@ -442,8 +490,7 @@ Rectangle Terminal::putchar(uint8_t ch)
m_state = State::Normal; m_state = State::Normal;
return {}; return {};
} }
handle_csi(ch); return handle_csi(ch);
return {};
} }
m_utf8_bytes[m_utf8_index++] = ch; m_utf8_bytes[m_utf8_index++] = ch;
@ -522,8 +569,7 @@ Rectangle Terminal::putchar(uint8_t ch)
{ {
const uint32_t scroll = m_cursor.y - rows() + 1; const uint32_t scroll = m_cursor.y - rows() + 1;
m_cursor.y -= scroll; m_cursor.y -= scroll;
m_window->shift_vertical(-scroll * (int32_t)m_font.height()); m_window->shift_vertical(-scroll * (int32_t)m_font.height(), m_bg_color);
m_window->fill_rect(0, m_window->height() - scroll * m_font.height(), m_window->width(), scroll * m_font.height(), m_bg_color);
should_invalidate = { 0, 0, m_window->width(), m_window->height() }; should_invalidate = { 0, 0, m_window->width(), m_window->height() };
} }

View File

@ -35,8 +35,8 @@ public:
uint32_t rows() const { return m_window->height() / m_font.height(); } uint32_t rows() const { return m_window->height() / m_font.height(); }
private: private:
void handle_csi(char ch);
void handle_sgr(); void handle_sgr();
Rectangle handle_csi(char ch);
Rectangle putchar(uint8_t ch); Rectangle putchar(uint8_t ch);
bool read_shell(); bool read_shell();

View File

@ -413,7 +413,7 @@ void WindowServer::invalidate(Rectangle area)
const auto is_rounded_off = const auto is_rounded_off =
[&](Position pos) -> bool [&](Position pos) -> bool
{ {
for (size_t i = 0; i < 4; i++) for (int32_t i = 0; i < 4; i++)
{ {
if (!corner_areas[i].contains(pos)) if (!corner_areas[i].contains(pos))
continue; continue;
@ -558,8 +558,6 @@ void WindowServer::sync()
dir_y = -dir_y; dir_y = -dir_y;
} }
size_t synced_pages = 0;
for (size_t i = 0; i < m_pages_to_sync_bitmap.size() * 8; i++) for (size_t i = 0; i < m_pages_to_sync_bitmap.size() * 8; i++)
{ {
size_t byte = i / 8; size_t byte = i / 8;
@ -582,7 +580,6 @@ void WindowServer::sync()
len * 4096, len * 4096,
MS_SYNC MS_SYNC
); );
synced_pages += len;
i += len; i += len;
} }

View File

@ -135,7 +135,7 @@ int list_directory(const BAN::String& path, config_t config)
if (!S_ISDIR(st.st_mode)) if (!S_ISDIR(st.st_mode))
{ {
MUST(entries.emplace_back(path, st)); MUST(entries.emplace_back(path, st, BAN::String()));
if (S_ISLNK(st.st_mode)) if (S_ISLNK(st.st_mode))
{ {
if (readlink(path.data(), link_buffer, sizeof(link_buffer)) == -1) if (readlink(path.data(), link_buffer, sizeof(link_buffer)) == -1)
@ -166,7 +166,7 @@ int list_directory(const BAN::String& path, config_t config)
continue; continue;
} }
MUST(entries.emplace_back(BAN::StringView(dirent->d_name), st)); MUST(entries.emplace_back(BAN::StringView(dirent->d_name), st, BAN::String()));
if (S_ISLNK(st.st_mode)) if (S_ISLNK(st.st_mode))
{ {
if (readlinkat(dirfd(dirp), dirent->d_name, link_buffer, sizeof(link_buffer)) == -1) if (readlinkat(dirfd(dirp), dirent->d_name, link_buffer, sizeof(link_buffer)) == -1)