--- src/terminal.h 2026-04-26 21:16:21.226140146 +0000 +++ src/terminal.h 2026-04-26 21:16:37.729574798 +0000 @@ -6,8 +6,10 @@ #include "attrs.h" #include +#include #include +#include namespace KeyboardModifier { constexpr int kShift = TSM_SHIFT_MASK, @@ -33,12 +35,14 @@ using CopyCb = std::function; using PasteCb = std::function; using TitleCb = std::function; + using ImageCb = std::function, int, int, int, int, bool)>; void set_theme(const Theme& theme); void set_draw_cb(DrawCb draw_cb); void set_copy_cb(CopyCb copy_cb); void set_paste_cb(PasteCb paste_cb); void set_title_cb(TitleCb title_cb); + void set_image_cb(ImageCb image_cb); void set_pty(Pty* pty); Pos cursor(); @@ -62,12 +66,16 @@ static void StaticWrite(tsm_vte *vte, const char *u8, size_t len, void *data); static void StaticOsc(tsm_vte *vte, const char *u8, size_t len, void *data); + void HandleITerm2Image(const char *params, size_t params_len, + const char *data, size_t data_len); + const Theme *m_theme{nullptr}; DrawCb m_draw_cb; CopyCb m_copy_cb; PasteCb m_paste_cb; TitleCb m_title_cb; + ImageCb m_image_cb; tsm_screen *m_screen; tsm_vte *m_vte; --- src/terminal.cc 2026-04-26 21:16:21.231140278 +0000 +++ src/terminal.cc 2026-04-26 21:17:28.190903832 +0000 @@ -2,8 +2,48 @@ #include #include +#include #include +#include + +// Base64 decoding table +static const int8_t b64_table[256] = { + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63, + 52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14, + 15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1, + -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40, + 41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 +}; + +static std::vector base64_decode(const char *input, size_t len) { + std::vector out; + out.reserve(len * 3 / 4); + + int val = 0, bits = -8; + for (size_t i = 0; i < len; i++) { + int c = b64_table[(unsigned char)input[i]]; + if (c < 0) continue; + val = (val << 6) | c; + bits += 6; + if (bits >= 0) { + out.push_back((val >> bits) & 0xFF); + bits -= 8; + } + } + return out; +} Terminal::Terminal() { tsm_screen_new(&m_screen, nullptr, nullptr); @@ -29,6 +69,7 @@ void Terminal::set_copy_cb(CopyCb copy_cb) { m_copy_cb = copy_cb; } void Terminal::set_paste_cb(PasteCb paste_cb) { m_paste_cb = paste_cb; } void Terminal::set_title_cb(TitleCb title_cb) { m_title_cb = title_cb; } +void Terminal::set_image_cb(ImageCb image_cb) { m_image_cb = image_cb; } void Terminal::set_pty(Pty *pty) { m_pty = pty; } Pos Terminal::cursor() { @@ -224,8 +265,80 @@ void Terminal::StaticOsc(tsm_vte *vte, const char *u8, size_t len, void *data) { Terminal *term = static_cast(data); - if (u8[0] == '2' && u8[1] == ';') { + // OSC 2; - Set window title + if (len > 2 && u8[0] == '2' && u8[1] == ';') { string text(u8 + 2, len - 2); term->m_title_cb(text); + return; + } + + // OSC 1337; - iTerm2 proprietary sequences + if (len > 5 && strncmp(u8, "1337;", 5) == 0) { + const char *payload = u8 + 5; + size_t payload_len = len - 5; + + // Look for "File=" prefix for inline images + if (payload_len > 5 && strncmp(payload, "File=", 5) == 0) { + // Find the colon separating params from base64 data + const char *colon = static_cast(memchr(payload, ':', payload_len)); + if (colon) { + size_t params_len = colon - payload - 5; + const char *params = payload + 5; + const char *b64data = colon + 1; + size_t b64len = payload_len - (b64data - payload); + + term->HandleITerm2Image(params, params_len, b64data, b64len); + } + } } } + +void Terminal::HandleITerm2Image(const char *params, size_t params_len, + const char *data, size_t data_len) { + if (!m_image_cb) return; + + // Parse parameters: name=xxx;size=xxx;width=xxx;height=xxx;preserveAspectRatio=xxx;inline=1 + int width = 0, height = 0; + bool preserveAspect = true; + bool is_inline = false; + + string params_str(params, params_len); + size_t pos = 0; + while (pos < params_str.size()) { + size_t eq = params_str.find('=', pos); + size_t semi = params_str.find(';', pos); + if (semi == string::npos) semi = params_str.size(); + + if (eq != string::npos && eq < semi) { + string key = params_str.substr(pos, eq - pos); + string val = params_str.substr(eq + 1, semi - eq - 1); + + if (key == "width") { + try { width = std::stoi(val); } catch (...) {} + } else if (key == "height") { + try { height = std::stoi(val); } catch (...) {} + } else if (key == "preserveAspectRatio") { + preserveAspect = (val != "0"); + } else if (key == "inline") { + is_inline = (val == "1"); + } + } + pos = semi + 1; + } + + // Only handle inline images + if (!is_inline) return; + + // Decode base64 data + std::vector decoded = base64_decode(data, data_len); + if (decoded.empty()) return; + + // Create SkData and invoke callback + sk_sp skdata = SkData::MakeWithCopy(decoded.data(), decoded.size()); + + // Get current cursor position for image placement + int x = tsm_screen_get_cursor_x(m_screen); + int y = tsm_screen_get_cursor_y(m_screen); + + m_image_cb(skdata, x, y, width, height, preserveAspect); +} --- src/display.h 2026-04-26 21:16:21.236140409 +0000 +++ src/display.h 2026-04-26 21:17:41.648258273 +0000 @@ -1,14 +1,18 @@ #pragma once -#include -#include -#include +#include +#include +#include +#include +#include #include "base.h" #include "terminal.h" #include "text.h" #include "marker_set.h" +#include + class Display { public: Display(Terminal *term); @@ -20,6 +24,10 @@ Error Resize(int width, int height); bool Draw(SkCanvas *canvas, bool lazy_updating); + + // Add an inline image at the given cell position + void AddImage(sk_sp data, int x, int y, int width, int height, bool preserveAspect); + private: void TermDraw(const u32string& str, Pos pos, Attr attr, int width); void UpdateWidth(); @@ -38,4 +46,16 @@ AttrSet m_attrs; bool m_has_updated{false}; + + // Stored images for rendering + struct DisplayImage { + sk_sp image; + int x, y; + int width, height; + bool preserveAspect; + + DisplayImage(sk_sp img, int px, int py, int w, int h, bool aspect) + : image(img), x(px), y(py), width(w), height(h), preserveAspect(aspect) {} + }; + std::vector m_images; }; --- src/display.cc 2026-04-26 21:16:21.239140488 +0000 +++ src/display.cc 2026-04-26 21:24:58.544750624 +0000 @@ -1,6 +1,8 @@ #include "display.h" #include +#include +#include // Clamps v to the range low (inclusive) to high (exclusive). template @@ -103,6 +105,24 @@ }); } + // Draw inline images + for (const auto &img : m_images) { + if (!img.image) continue; + + SkScalar cellW = m_char_width; + SkScalar cellH = m_renderers[0].FindHeight(); + + // Calculate destination rect + SkScalar destX = cellW * img.x; + SkScalar destY = cellH * img.y + m_renderers[0].FindBaselineOffset(); + SkScalar destW = img.width > 0 ? cellW * img.width : img.image->width(); + SkScalar destH = img.height > 0 ? cellH * img.height : img.image->height(); + + SkRect destRect = SkRect::MakeXYWH(destX, destY, destW, destH); + + canvas->drawImageRect(img.image, destRect, SkSamplingOptions(SkFilterMode::kLinear)); + } + m_has_updated = false; return significant_redraw; } @@ -179,3 +199,28 @@ canvas->restore(); } } + +void Display::AddImage(sk_sp data, int x, int y, int width, int height, bool preserveAspect) { + // Decode image using SkCodec + std::unique_ptr codec = SkCodec::MakeFromData(data); + if (!codec) { + return; + } + + SkImageInfo info = codec->getInfo().makeColorType(kN32_SkColorType); + + SkBitmap bitmap; + if (!bitmap.tryAllocPixels(info)) { + return; + } + + SkCodec::Result result = codec->getPixels(info, bitmap.getPixels(), bitmap.rowBytes()); + if (result != SkCodec::kSuccess) { + return; + } + + sk_sp image = bitmap.asImage(); + + m_images.emplace_back(image, x, y, width, height, preserveAspect); + m_has_updated = true; +} --- src/uterm.h 2026-04-26 21:16:21.244140620 +0000 +++ src/uterm.h 2026-04-26 21:18:28.652496295 +0000 @@ -9,6 +9,8 @@ #include #include +#include + class AtomicFlag { public: bool get() { return m_flag.load(); } @@ -57,6 +59,7 @@ void HandleSelection(Selection state, double mx, double my); void HandleScroll(ScrollDirection direction, uint distance); void HandleTitle(const string &title); + void HandleImage(sk_sp data, int x, int y, int width, int height, bool preserveAspect); std::mutex m_current_reader_lock; ReaderThread *m_current_reader{nullptr}; --- src/uterm.cc 2026-04-26 21:16:21.260141042 +0000 +++ src/uterm.cc 2026-04-26 21:18:57.322250064 +0000 @@ -88,6 +88,7 @@ m_term.set_copy_cb(std::bind(&Uterm::HandleCopy, this, _1)); m_term.set_paste_cb(std::bind(&Uterm::HandlePaste, this)); m_term.set_title_cb(std::bind(&Uterm::HandleTitle, this, _1)); + m_term.set_image_cb(std::bind(&Uterm::HandleImage, this, _1, _2, _3, _4, _5, _6)); for (auto &font : m_config.fonts()) { m_display.AddFont(font.name, font.size); @@ -190,3 +191,8 @@ void Uterm::HandleTitle(const string &title) { m_window.SetTitle(title); } + +void Uterm::HandleImage(sk_sp data, int x, int y, int width, int height, + bool preserveAspect) { + m_display.AddImage(data, x, y, width, height, preserveAspect); +}