[feat] integrate CrossDesk CLI with terminal rendering and npm packaging

This commit is contained in:
dijunkun
2026-10-30 10:42:23 +08:00
parent 2f57d0aba3
commit 699d867956
34 changed files with 3811 additions and 18 deletions
+255
View File
@@ -0,0 +1,255 @@
#include "terminal_renderer.h"
#include <algorithm>
#include <charconv>
namespace crossdesk::terminal {
void Input::Tap(ControllerSession &s, int key, int modifier) {
if (modifier)
s.Key(modifier, true);
s.Key(key, true);
s.Key(key, false);
if (modifier)
s.Key(modifier, false);
}
void Input::Navigate(char key, const Grid *grid) {
if (key == '+' || key == '=')
zoom = std::min(32.0, zoom * 2);
if (key == '-')
zoom = std::max(1.0, zoom / 2);
if (key == '0') {
native = false;
zoom = 1;
center_x = center_y = .5;
}
if (key == '1') {
native = true;
zoom = 1;
}
// Use the displayed crop as the pan step, including native pixel mode.
const double dx = grid && grid->source_width
? double(grid->source.width) / grid->source_width
: 1 / zoom;
const double dy = grid && grid->source_height
? double(grid->source.height) / grid->source_height
: 1 / zoom;
if (key == 'h' || key == 'j' || key == 'k' || key == 'l') {
center_x = std::clamp(center_x, dx / 2, 1 - dx / 2);
center_y = std::clamp(center_y, dy / 2, 1 - dy / 2);
}
if (key == 'h')
center_x -= .2 * dx;
if (key == 'l')
center_x += .2 * dx;
if (key == 'k')
center_y -= .2 * dy;
if (key == 'j')
center_y += .2 * dy;
center_x = std::clamp(center_x, 0.0, 1.0);
center_y = std::clamp(center_y, 0.0, 1.0);
++view_revision;
}
void Input::Feed(const std::string &bytes, ControllerSession &s,
const Grid *grid) {
if (pending_.size() + bytes.size() > 32768) {
pending_.clear();
pasting_ = false;
s.SetControl(false);
control = false;
return;
}
if (!bytes.empty()) {
last_input_ = std::chrono::steady_clock::now();
error.clear();
}
if (bytes.empty() && pending_ == "\033" &&
std::chrono::steady_clock::now() - last_input_ >
std::chrono::milliseconds(80)) {
Tap(s, 27);
pending_.clear();
return;
}
pending_ += bytes;
while (!pending_.empty()) {
if (pasting_) {
auto end = pending_.find("\033[201~");
if (end == std::string::npos)
return; // bounded by 32 KiB; one UTF-8 transaction
if (control && !s.Text(pending_.substr(0, end)))
error = "Remote UTF-8 text input unavailable";
pending_.erase(0, end + 6);
pasting_ = false;
continue;
}
unsigned char c = pending_[0];
if (c == 29) {
exit = true;
pending_.erase(0, 1);
s.SetControl(false);
return;
}
if (c == 20) {
control = !control;
s.SetControl(control);
pending_.erase(0, 1);
continue;
}
if (!control && (c == '+' || c == '=' || c == '-' || c == '0' || c == '1' ||
c == 'h' || c == 'j' || c == 'k' || c == 'l')) {
Navigate(c, grid);
pending_.erase(0, 1);
continue;
}
if (c == 27) {
if (pending_.size() < 2)
return;
if (pending_.compare(0, 6, "\033[200~") == 0) {
pasting_ = true;
pending_.erase(0, 6);
continue;
}
if (pending_[1] == '_') {
auto end = pending_.find("\033\\", 2);
if (end == std::string::npos)
return;
pending_.erase(0, end + 2);
continue;
}
if (pending_[1] == '[' || pending_[1] == 'O') {
size_t end = 2;
while (end < pending_.size() &&
!(pending_[end] >= '@' && pending_[end] <= '~'))
++end;
if (end == pending_.size())
return;
std::string seq = pending_.substr(0, end + 1);
pending_.erase(0, end + 1);
if (seq == "\033[O") {
control = false;
s.SetControl(false);
continue;
}
int key = 0;
if (seq == "\033[A")
key = 38;
else if (seq == "\033[B")
key = 40;
else if (seq == "\033[C")
key = 39;
else if (seq == "\033[D")
key = 37;
else if (seq == "\033[H")
key = 36;
else if (seq == "\033[F")
key = 35;
else if (seq == "\033[3~")
key = 46;
else if (seq == "\033[5~")
key = 33;
else if (seq == "\033[6~")
key = 34;
else if (seq == "\033OP")
key = 112;
else if (seq == "\033OQ")
key = 113;
else if (seq == "\033OR")
key = 114;
else if (seq == "\033OS")
key = 115;
if (key) {
if (!control && key >= 37 && key <= 40) {
Navigate(key == 37 ? 'h'
: key == 38 ? 'k'
: key == 39 ? 'l'
: 'j',
grid);
continue;
}
Tap(s, key);
continue;
}
int b = 0, x = 0, y = 0;
char action = seq.back();
bool mouse =
seq.rfind("\033[<", 0) == 0 && (action == 'M' || action == 'm');
const char *cur = seq.data() + std::min(size_t(3), seq.size());
const char *limit = seq.data() + seq.size() - 1;
for (int *value : {&b, &x, &y}) {
if (!mouse)
break;
auto result = std::from_chars(cur, limit, *value);
mouse = result.ec == std::errc{} && *value >= 0 && *value <= 10000;
cur = result.ptr;
if (value != &y) {
mouse = mouse && cur < limit && *cur == ';';
if (mouse)
++cur;
} else
mouse = mouse && cur == limit;
}
if (grid && mouse) {
auto pos = MapPointer(*grid, x - 1, y - 1);
if (!control) {
if (pos && action == 'M' && ((b & 64) || b == 0)) {
center_x = pos->first;
center_y = pos->second;
if (b & 64)
Navigate((b & 1) ? '-' : '+', grid);
else
Navigate('1', grid);
}
continue;
}
if (!pos && action != 'm')
continue;
if (pos) {
pointer_x_ = pos->first;
pointer_y_ = pos->second;
}
int flag = 0, wheel = 0;
if (b & 64) {
flag = (b & 2) ? 8 : 7;
wheel = (b & 1) ? -120 : 120;
} else if (!(b & 32)) {
int base = b & 3;
if (base == 3)
continue;
flag = (base == 0 ? 1
: base == 1 ? 5
: 3) +
(action == 'm' ? 1 : 0);
}
s.Pointer(pointer_x_, pointer_y_, flag, wheel);
}
continue;
}
// Alt+ASCII letter: a tap with explicit modifier release.
if (pending_[1] >= 'a' && pending_[1] <= 'z')
Tap(s, pending_[1] - 'a' + 'A', 18);
pending_.erase(0, 2);
continue;
}
if (c < 32 || c == 127) {
pending_.erase(0, 1);
if (c == 13 || c == 10)
Tap(s, 13);
else if (c == 9)
Tap(s, 9);
else if (c == 127 || c == 8)
Tap(s, 8);
else if (c >= 1 && c <= 26)
Tap(s, 'A' + c - 1, 17);
continue;
}
size_t n = c < 128 ? 1
: (c & 0xe0) == 0xc0 ? 2
: (c & 0xf0) == 0xe0 ? 3
: (c & 0xf8) == 0xf0 ? 4
: 1;
if (pending_.size() < n)
return;
auto text = pending_.substr(0, n);
pending_.erase(0, n);
if (control && !s.Text(text))
error = "Remote UTF-8 text input unavailable";
}
}
} // namespace crossdesk::terminal