mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-11-02 03:44:07 +08:00
126 lines
4.6 KiB
C++
126 lines
4.6 KiB
C++
/*
|
|
* @Author: DI JUNKUN
|
|
* @Date: 2026-10-30
|
|
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
|
*/
|
|
|
|
#ifndef _TERMINAL_RENDERER_H_
|
|
#define _TERMINAL_RENDERER_H_
|
|
|
|
#include <controller_session.h>
|
|
namespace crossdesk::terminal {
|
|
enum class AnsiDetail { Half, Quadrant };
|
|
struct Viewport {
|
|
int columns = 80, rows = 23;
|
|
double cell_aspect = .5;
|
|
bool utf8 = true;
|
|
int colors = 24;
|
|
AnsiDetail detail = AnsiDetail::Half;
|
|
double zoom = 1, center_x = .5, center_y = .5;
|
|
int pixel_width = 0, pixel_height = 0;
|
|
bool native = false, pixel_mode = false;
|
|
int row_offset = 0; // Terminal rows reserved above the video area.
|
|
};
|
|
// Keep reported cell geometry across queries; never combine partial replies
|
|
// from different query cycles. Pixel/cell window sizes use the full text area.
|
|
class TerminalMetrics {
|
|
public:
|
|
void BeginQuery();
|
|
void Report(const std::string &sequence);
|
|
void Apply(Viewport &) const;
|
|
bool Known() const { return cell_width_ > 0; }
|
|
|
|
private:
|
|
bool SetCellSize(double width, double height);
|
|
double cell_width_ = 0, cell_height_ = 0;
|
|
int pixel_width_ = 0, pixel_height_ = 0, columns_ = 0, rows_ = 0;
|
|
bool queried_ = false, direct_reply_ = false;
|
|
};
|
|
inline constexpr char kTerminalSizeQuery[] = "\033[16t\033[14t\033[18t";
|
|
struct Rect {
|
|
int x, y, width, height;
|
|
};
|
|
struct Cell {
|
|
uint32_t top = 0, bottom = 0;
|
|
uint32_t glyph = 0x2580;
|
|
bool operator==(const Cell &other) const {
|
|
return top == other.top && bottom == other.bottom && glyph == other.glyph;
|
|
}
|
|
};
|
|
struct Grid {
|
|
Viewport viewport;
|
|
Rect content{};
|
|
std::vector<Cell> cells;
|
|
Rect source{};
|
|
unsigned source_width = 0, source_height = 0;
|
|
int samples_x = 1, samples_y = 2;
|
|
};
|
|
Rect Fit(int width, int height, int columns, int pixel_rows,
|
|
double pixel_aspect = 1);
|
|
Grid Layout(const DecodedFrame &, Viewport);
|
|
Grid Convert(const DecodedFrame &, Viewport);
|
|
std::optional<std::pair<float, float>> MapPointer(const Grid &, int column,
|
|
int row);
|
|
std::vector<std::string> Lines(const Grid &);
|
|
std::string Difference(const Grid &, const std::optional<Grid> &previous);
|
|
struct PixelFrame {
|
|
int width = 0, height = 0;
|
|
std::vector<uint8_t> rgb;
|
|
};
|
|
PixelFrame KittyPixels(const DecodedFrame &, const Viewport &);
|
|
std::string KittySharedUpload(const std::string &name, int width, int height,
|
|
bool alternate_buffer, bool query = false);
|
|
std::string KittyPresent(const Viewport &, bool alternate_buffer);
|
|
struct KittyAck {
|
|
uint32_t image_id = 0;
|
|
bool ok = false;
|
|
};
|
|
std::optional<KittyAck> ParseKittyAck(const std::string &);
|
|
// Toggle alternate_buffer only after the previous output has been written in
|
|
// full. Upload to the other image ID before replacing the visible placement.
|
|
std::string Kitty(const DecodedFrame &, const Viewport &,
|
|
bool alternate_buffer = false);
|
|
inline constexpr char kKittyQuery[] =
|
|
"\033_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\033\\";
|
|
inline constexpr char kKittyDelete[] =
|
|
"\033_Ga=d,d=I,i=31,q=2\033\\\033_Ga=d,d=I,i=32,q=2\033\\";
|
|
bool KittyReply(const std::string &response);
|
|
struct StatusBar {
|
|
std::string progress, compact_progress, error, view, network, fps;
|
|
bool control = false, command_mode = false;
|
|
};
|
|
// Three padded ASCII rows. Shortcuts are compacted as whole groups to fit.
|
|
std::vector<std::string> StatusLines(const StatusBar &, int columns);
|
|
// Incremental parser: protocol replies and unknown escapes are consumed
|
|
// locally. Key taps always release immediately; paste uses the UTF-8 text
|
|
// protocol. In command_mode, control records the mode to restore while the
|
|
// session's remote input is suspended until the user explicitly leaves the mode.
|
|
class Input {
|
|
public:
|
|
void Feed(const std::string &bytes, ControllerSession &, const Grid *grid);
|
|
bool exit = false, control = false, native = false, command_mode = false;
|
|
std::string error;
|
|
double zoom = 1, center_x = .5, center_y = .5;
|
|
uint64_t view_revision = 0;
|
|
uint64_t mouse_reports = 0;
|
|
uint64_t mouse_legacy_reports = 0, mouse_forwarded = 0;
|
|
uint64_t mouse_send_failures = 0;
|
|
TerminalMetrics terminal_metrics;
|
|
std::optional<KittyAck> kitty_ack;
|
|
|
|
private:
|
|
std::string pending_;
|
|
std::chrono::steady_clock::time_point last_input_{};
|
|
bool pasting_ = false;
|
|
unsigned mouse_buttons_ = 0;
|
|
float pointer_x_ = .5f, pointer_y_ = .5f;
|
|
void Tap(ControllerSession &, int key, int modifier = 0);
|
|
bool LocalKey(int key, ControllerSession &, const Grid *);
|
|
void Navigate(char key, const Grid *);
|
|
void Mouse(int button, int x, int y, bool release, ControllerSession &,
|
|
const Grid *);
|
|
};
|
|
} // namespace crossdesk::terminal
|
|
|
|
#endif
|