reflex-terminal 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.doc/ext/reflex-terminal/renderer.cpp +33 -1
- data/.doc/ext/reflex-terminal/terminal.cpp +26 -10
- data/.github/workflows/release-gem.yml +4 -49
- data/.github/workflows/tag.yml +5 -28
- data/.github/workflows/test.yml +3 -37
- data/ChangeLog.md +23 -0
- data/Gemfile.lock +26 -0
- data/README.md +2 -2
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/examples/terminal.rb +24 -13
- data/ext/reflex-terminal/renderer.cpp +37 -1
- data/ext/reflex-terminal/terminal.cpp +27 -10
- data/include/reflex/terminal.h +30 -5
- data/include/reflex-terminal/renderer.h +8 -0
- data/lib/reflex/terminal.rb +3 -2
- data/lib/reflex/terminal_view.rb +76 -10
- data/reflex-terminal.gemspec +5 -5
- data/src/pty.cpp +33 -4
- data/src/renderer.cpp +350 -80
- data/src/terminal.cpp +237 -105
- data/src/win32/pty.cpp +2 -2
- data/test/test_renderer.rb +122 -4
- data/test/test_terminal.rb +163 -8
- data/test/test_terminal_view.rb +1 -1
- metadata +12 -12
- data/.github/workflows/utils.rb +0 -127
data/src/terminal.cpp
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
|
|
4
4
|
#include <stdlib.h>
|
|
5
5
|
#include <string.h>
|
|
6
|
+
#include <math.h>
|
|
6
7
|
#include <string>
|
|
8
|
+
#include <algorithm>
|
|
7
9
|
#include <ghostty/vt.h>
|
|
8
10
|
#include <rays/color.h>
|
|
9
11
|
#include <reflex/exception.h>
|
|
@@ -47,25 +49,31 @@ namespace Reflex
|
|
|
47
49
|
|
|
48
50
|
GhosttyOptionAsAlt option_as_alt = GHOSTTY_OPTION_AS_ALT_TRUE;
|
|
49
51
|
|
|
50
|
-
|
|
52
|
+
PTY pty;
|
|
53
|
+
|
|
54
|
+
RowList spans;
|
|
55
|
+
|
|
56
|
+
std::vector<uint> cell_offsets;
|
|
51
57
|
|
|
52
58
|
int columns = 0, rows = 0;
|
|
53
59
|
|
|
54
|
-
|
|
60
|
+
coord cell_width = 8, cell_height = 16;
|
|
55
61
|
|
|
56
62
|
int screen_width = 0, screen_height = 0;
|
|
57
63
|
|
|
58
|
-
|
|
64
|
+
uint pressed_buttons = 0;
|
|
59
65
|
|
|
60
|
-
|
|
66
|
+
float wheel_rows = 0;
|
|
61
67
|
|
|
62
|
-
|
|
68
|
+
longlong bells = 0;
|
|
63
69
|
|
|
64
|
-
|
|
70
|
+
bool spans_blink = false;
|
|
65
71
|
|
|
66
|
-
|
|
72
|
+
Cursor last_cursor;
|
|
67
73
|
|
|
68
|
-
|
|
74
|
+
String pending_input;
|
|
75
|
+
|
|
76
|
+
String title;
|
|
69
77
|
|
|
70
78
|
~Data ()
|
|
71
79
|
{
|
|
@@ -110,6 +118,13 @@ namespace Reflex
|
|
|
110
118
|
write_input(self, (const char*) data, len);
|
|
111
119
|
}
|
|
112
120
|
|
|
121
|
+
static void
|
|
122
|
+
set_option (GhosttyTerminal terminal, GhosttyTerminalOption option, const void* value)
|
|
123
|
+
{
|
|
124
|
+
if (ghostty_terminal_set(terminal, option, value) != GHOSTTY_SUCCESS)
|
|
125
|
+
system_error(__FILE__, __LINE__, "failed to set the terminal option %d", (int) option);
|
|
126
|
+
}
|
|
127
|
+
|
|
113
128
|
static void
|
|
114
129
|
set_default_modes (GhosttyTerminal terminal)
|
|
115
130
|
{
|
|
@@ -120,7 +135,8 @@ namespace Reflex
|
|
|
120
135
|
// and the child that was told they are four columns wide disagree
|
|
121
136
|
// about where the next character goes.
|
|
122
137
|
// a reset clears the modes, so this has to be said again after one
|
|
123
|
-
|
|
138
|
+
GhosttyTerminalModeConfig mode = {GHOSTTY_MODE_GRAPHEME_CLUSTER, true};
|
|
139
|
+
set_option(terminal, GHOSTTY_TERMINAL_OPT_MODE, &mode);
|
|
124
140
|
}
|
|
125
141
|
|
|
126
142
|
static void
|
|
@@ -150,9 +166,15 @@ namespace Reflex
|
|
|
150
166
|
to_attribs (const GhosttyStyle& style)
|
|
151
167
|
{
|
|
152
168
|
// INVERSE is reported rather than applied: swapping fg/bg is left to
|
|
153
|
-
// the renderer, which is the one that knows the theme's default colors
|
|
154
|
-
|
|
155
|
-
//
|
|
169
|
+
// the renderer, which is the one that knows the theme's default colors
|
|
170
|
+
|
|
171
|
+
// the mask carries ghostty's style number as is
|
|
172
|
+
static_assert(Terminal::Span::UNDERLINE_NONE == (int) GHOSTTY_SGR_UNDERLINE_NONE);
|
|
173
|
+
static_assert(Terminal::Span::UNDERLINE_SINGLE == (int) GHOSTTY_SGR_UNDERLINE_SINGLE);
|
|
174
|
+
static_assert(Terminal::Span::UNDERLINE_DOUBLE == (int) GHOSTTY_SGR_UNDERLINE_DOUBLE);
|
|
175
|
+
static_assert(Terminal::Span::UNDERLINE_CURLY == (int) GHOSTTY_SGR_UNDERLINE_CURLY);
|
|
176
|
+
static_assert(Terminal::Span::UNDERLINE_DOTTED == (int) GHOSTTY_SGR_UNDERLINE_DOTTED);
|
|
177
|
+
static_assert(Terminal::Span::UNDERLINE_DASHED == (int) GHOSTTY_SGR_UNDERLINE_DASHED);
|
|
156
178
|
|
|
157
179
|
uint attribs = 0;
|
|
158
180
|
if (style.bold) attribs |= Terminal::Span::BOLD;
|
|
@@ -174,6 +196,47 @@ namespace Reflex
|
|
|
174
196
|
return (color.r << 16) | (color.g << 8) | color.b;
|
|
175
197
|
}
|
|
176
198
|
|
|
199
|
+
enum {PALETTE_SIZE = Terminal::COLOR_PALETTE_LAST - Terminal::COLOR_PALETTE_FIRST + 1};
|
|
200
|
+
|
|
201
|
+
static bool
|
|
202
|
+
is_palette (Terminal::ColorIndex index)
|
|
203
|
+
{
|
|
204
|
+
return Terminal::COLOR_PALETTE_FIRST <= index && index <= Terminal::COLOR_PALETTE_LAST;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
static void
|
|
208
|
+
get_palette (GhosttyTerminal terminal, GhosttyColorRgb* palette, bool default_)
|
|
209
|
+
{
|
|
210
|
+
GhosttyTerminalData data = default_
|
|
211
|
+
? GHOSTTY_TERMINAL_DATA_COLOR_PALETTE_DEFAULT
|
|
212
|
+
: GHOSTTY_TERMINAL_DATA_COLOR_PALETTE;
|
|
213
|
+
|
|
214
|
+
if (ghostty_terminal_get(terminal, data, palette) != GHOSTTY_SUCCESS)
|
|
215
|
+
system_error(__FILE__, __LINE__, "failed to get a terminal palette");
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
static void
|
|
219
|
+
set_palette (GhosttyTerminal terminal, const GhosttyColorRgb* palette)
|
|
220
|
+
{
|
|
221
|
+
set_option(terminal, GHOSTTY_TERMINAL_OPT_COLOR_PALETTE, palette);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
static int
|
|
225
|
+
to_underline_color (const GhosttyStyle& style, const GhosttyColorRgb* palette)
|
|
226
|
+
{
|
|
227
|
+
switch (style.underline_color.tag)
|
|
228
|
+
{
|
|
229
|
+
case GHOSTTY_STYLE_COLOR_PALETTE:
|
|
230
|
+
return to_rgb(palette[style.underline_color.value.palette]);
|
|
231
|
+
|
|
232
|
+
case GHOSTTY_STYLE_COLOR_RGB:
|
|
233
|
+
return to_rgb(style.underline_color.value.rgb);
|
|
234
|
+
|
|
235
|
+
default:
|
|
236
|
+
return Terminal::Span::COLOR_NONE;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
177
240
|
static GhosttyKey
|
|
178
241
|
to_ghostty_key (int code)
|
|
179
242
|
{
|
|
@@ -304,6 +367,15 @@ namespace Reflex
|
|
|
304
367
|
return mods;
|
|
305
368
|
}
|
|
306
369
|
|
|
370
|
+
static int
|
|
371
|
+
to_ghostty_button (uint types)
|
|
372
|
+
{
|
|
373
|
+
if (types & Pointer::MOUSE_LEFT) return GHOSTTY_MOUSE_BUTTON_LEFT;
|
|
374
|
+
if (types & Pointer::MOUSE_RIGHT) return GHOSTTY_MOUSE_BUTTON_RIGHT;
|
|
375
|
+
if (types & Pointer::MOUSE_MIDDLE) return GHOSTTY_MOUSE_BUTTON_MIDDLE;
|
|
376
|
+
return 0;
|
|
377
|
+
}
|
|
378
|
+
|
|
307
379
|
static uint32_t
|
|
308
380
|
to_unshifted_codepoint (GhosttyKey key)
|
|
309
381
|
{
|
|
@@ -344,6 +416,17 @@ namespace Reflex
|
|
|
344
416
|
{
|
|
345
417
|
unsigned char c = (unsigned char) *p;
|
|
346
418
|
if (c < 0x20 || c == 0x7f) return false;
|
|
419
|
+
|
|
420
|
+
// U+F700-U+F8FF (utf-8 "\xEF\x9C\x80"-"\xEF\xA3\xBF"): the private
|
|
421
|
+
// use range where macos hands over its function keys, arrows
|
|
422
|
+
// included -- a key rather than text, and the kitty protocol
|
|
423
|
+
// would otherwise send it out as the key's text
|
|
424
|
+
if (
|
|
425
|
+
c == 0xef &&
|
|
426
|
+
0x9c <= (unsigned char) p[1] && (unsigned char) p[1] <= 0xa3)
|
|
427
|
+
{
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
347
430
|
}
|
|
348
431
|
return true;
|
|
349
432
|
}
|
|
@@ -387,10 +470,13 @@ namespace Reflex
|
|
|
387
470
|
GhosttyKey key = to_ghostty_key(event.code());
|
|
388
471
|
GhosttyMods mods = to_ghostty_mods(event.modifiers());
|
|
389
472
|
|
|
390
|
-
// send the composed text only
|
|
391
|
-
//
|
|
473
|
+
// send the composed text only on a press -- text goes with presses
|
|
474
|
+
// alone, and the encoder writes a release's text out again as if it
|
|
475
|
+
// were typed -- and only when it is printable and no command/meta
|
|
476
|
+
// modifier is in effect
|
|
392
477
|
const char* chars = event.chars();
|
|
393
478
|
bool use_utf8 =
|
|
479
|
+
action != GHOSTTY_KEY_ACTION_RELEASE &&
|
|
394
480
|
is_printable(chars) &&
|
|
395
481
|
!(mods & GHOSTTY_MODS_SUPER) &&
|
|
396
482
|
!((mods & GHOSTTY_MODS_ALT) && self->option_as_alt != GHOSTTY_OPTION_AS_ALT_FALSE);
|
|
@@ -435,16 +521,31 @@ namespace Reflex
|
|
|
435
521
|
{
|
|
436
522
|
ghostty_mouse_encoder_setopt_from_terminal(self->mouse_encoder, self->terminal);
|
|
437
523
|
|
|
524
|
+
// the encoder's cell size is integral, so handing it the position and
|
|
525
|
+
// a rounded cell width would drift by a cell or more toward the right
|
|
526
|
+
// edge. the cell is settled here with the fractional size instead, and
|
|
527
|
+
// the position is projected onto an integer grid of the encoder's own,
|
|
528
|
+
// where its division cannot miss: the offset inside the cell is scaled
|
|
529
|
+
// to the integral cell and clamped so that rounding cannot carry it
|
|
530
|
+
// into the next cell, keeping the sub-cell precision that SGR-Pixels
|
|
531
|
+
// mode reports
|
|
532
|
+
uint32_t cell_width = (uint32_t) std::max(1L, lround(self->cell_width));
|
|
533
|
+
uint32_t cell_height = (uint32_t) std::max(1L, lround(self->cell_height));
|
|
534
|
+
int cell_x = (int) (x / self->cell_width);
|
|
535
|
+
int cell_y = (int) (y / self->cell_height);
|
|
536
|
+
float offset_x = (x - cell_x * self->cell_width) * (cell_width / self->cell_width);
|
|
537
|
+
float offset_y = (y - cell_y * self->cell_height) * (cell_height / self->cell_height);
|
|
538
|
+
|
|
438
539
|
GhosttyMouseEncoderSize size = init_sized<GhosttyMouseEncoderSize>();
|
|
439
|
-
size.screen_width = self->
|
|
440
|
-
|
|
441
|
-
size.
|
|
442
|
-
|
|
443
|
-
size.cell_width = self->cell_width;
|
|
444
|
-
size.cell_height = self->cell_height;
|
|
540
|
+
size.screen_width = cell_width * self->columns;
|
|
541
|
+
size.screen_height = cell_height * self->rows;
|
|
542
|
+
size.cell_width = cell_width;
|
|
543
|
+
size.cell_height = cell_height;
|
|
445
544
|
ghostty_mouse_encoder_setopt(self->mouse_encoder, GHOSTTY_MOUSE_ENCODER_OPT_SIZE, &size);
|
|
545
|
+
|
|
546
|
+
bool any_button_pressed = self->pressed_buttons != 0;
|
|
446
547
|
ghostty_mouse_encoder_setopt(
|
|
447
|
-
self->mouse_encoder, GHOSTTY_MOUSE_ENCODER_OPT_ANY_BUTTON_PRESSED, &
|
|
548
|
+
self->mouse_encoder, GHOSTTY_MOUSE_ENCODER_OPT_ANY_BUTTON_PRESSED, &any_button_pressed);
|
|
448
549
|
|
|
449
550
|
GhosttyMouseEvent e = self->mouse_event;
|
|
450
551
|
ghostty_mouse_event_set_action(e, action);
|
|
@@ -454,7 +555,10 @@ namespace Reflex
|
|
|
454
555
|
ghostty_mouse_event_clear_button(e);
|
|
455
556
|
ghostty_mouse_event_set_mods(e, mods);
|
|
456
557
|
|
|
457
|
-
GhosttyMousePosition position = {
|
|
558
|
+
GhosttyMousePosition position = {
|
|
559
|
+
cell_x * cell_width + std::clamp(offset_x, 0.f, cell_width - 1.f),
|
|
560
|
+
cell_y * cell_height + std::clamp(offset_y, 0.f, cell_height - 1.f)
|
|
561
|
+
};
|
|
458
562
|
ghostty_mouse_event_set_position(e, position);
|
|
459
563
|
|
|
460
564
|
char buffer[64];
|
|
@@ -471,11 +575,14 @@ namespace Reflex
|
|
|
471
575
|
{
|
|
472
576
|
self->spans.clear();
|
|
473
577
|
self->cell_offsets.clear();
|
|
578
|
+
self->spans_blink = false;
|
|
474
579
|
|
|
475
580
|
GhosttyResult result = ghostty_render_state_get(
|
|
476
581
|
self->render_state, GHOSTTY_RENDER_STATE_DATA_ROW_ITERATOR, &self->row_iterator);
|
|
477
582
|
if (result != GHOSTTY_SUCCESS) return;
|
|
478
583
|
|
|
584
|
+
GhosttyColorRgb *palette = NULL, palette_data[PALETTE_SIZE];
|
|
585
|
+
|
|
479
586
|
String utf8;
|
|
480
587
|
while (ghostty_render_state_row_iterator_next(self->row_iterator))
|
|
481
588
|
{
|
|
@@ -524,6 +631,7 @@ namespace Reflex
|
|
|
524
631
|
if (result == GHOSTTY_SUCCESS) bg = to_rgb(color);
|
|
525
632
|
|
|
526
633
|
uint attribs = 0;
|
|
634
|
+
int ul = Terminal::Span::COLOR_NONE;
|
|
527
635
|
bool has_styling = false;
|
|
528
636
|
ghostty_render_state_row_cells_get(
|
|
529
637
|
self->row_cells, GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_HAS_STYLING, &has_styling);
|
|
@@ -533,7 +641,18 @@ namespace Reflex
|
|
|
533
641
|
result = ghostty_render_state_row_cells_get(
|
|
534
642
|
self->row_cells, GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_STYLE, &style);
|
|
535
643
|
if (result == GHOSTTY_SUCCESS)
|
|
644
|
+
{
|
|
536
645
|
attribs = to_attribs(style);
|
|
646
|
+
if (attribs & Terminal::Span::BLINK)
|
|
647
|
+
self->spans_blink = true;
|
|
648
|
+
|
|
649
|
+
if (style.underline_color.tag == GHOSTTY_STYLE_COLOR_PALETTE && !palette)
|
|
650
|
+
{
|
|
651
|
+
get_palette(self->terminal, palette_data, false);
|
|
652
|
+
palette = palette_data;
|
|
653
|
+
}
|
|
654
|
+
ul = to_underline_color(style, palette);
|
|
655
|
+
}
|
|
537
656
|
}
|
|
538
657
|
|
|
539
658
|
// a wide cell answers for the spacer that follows it, which
|
|
@@ -576,8 +695,8 @@ namespace Reflex
|
|
|
576
695
|
|
|
577
696
|
if (
|
|
578
697
|
!span ||
|
|
579
|
-
span->fg != fg || span->bg != bg || span->
|
|
580
|
-
span_is_wide != is_wide)
|
|
698
|
+
span->fg != fg || span->bg != bg || span->ul != ul ||
|
|
699
|
+
span->attribs != attribs || span_is_wide != is_wide)
|
|
581
700
|
{
|
|
582
701
|
row.emplace_back();
|
|
583
702
|
span = &row.back();
|
|
@@ -587,6 +706,7 @@ namespace Reflex
|
|
|
587
706
|
span->cell_size = 0;
|
|
588
707
|
span->fg = fg;
|
|
589
708
|
span->bg = bg;
|
|
709
|
+
span->ul = ul;
|
|
590
710
|
span->attribs = attribs;
|
|
591
711
|
span_is_wide = is_wide;
|
|
592
712
|
}
|
|
@@ -652,24 +772,28 @@ namespace Reflex
|
|
|
652
772
|
__FILE__, __LINE__, "invalid terminal size: %dx%d", columns, rows);
|
|
653
773
|
}
|
|
654
774
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
options.max_scrollback = scrollback_bytes;
|
|
659
|
-
if (ghostty_terminal_new(NULL, &self->terminal, options) != GHOSTTY_SUCCESS)
|
|
775
|
+
if (ghostty_terminal_new(
|
|
776
|
+
NULL, &self->terminal, (uint16_t) columns, (uint16_t) rows) != GHOSTTY_SUCCESS)
|
|
777
|
+
{
|
|
660
778
|
system_error(__FILE__, __LINE__, "failed to create a terminal");
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
set_option(self->terminal, GHOSTTY_TERMINAL_OPT_SCROLLBACK_MAX_BYTES, &scrollback_bytes);
|
|
661
782
|
|
|
662
783
|
// register the cell pixel size (required right after creation)
|
|
663
|
-
ghostty_terminal_resize(
|
|
664
|
-
self->terminal,
|
|
784
|
+
if (ghostty_terminal_resize(
|
|
785
|
+
self->terminal, (uint16_t) columns, (uint16_t) rows,
|
|
786
|
+
self->cell_width, self->cell_height) != GHOSTTY_SUCCESS)
|
|
787
|
+
{
|
|
788
|
+
system_error(__FILE__, __LINE__, "failed to resize a terminal");
|
|
789
|
+
}
|
|
665
790
|
|
|
666
791
|
set_default_modes(self->terminal);
|
|
667
792
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
self->terminal, GHOSTTY_TERMINAL_OPT_TITLE_CHANGED, (const void*) title_changed);
|
|
793
|
+
set_option(self->terminal, GHOSTTY_TERMINAL_OPT_USERDATA, self.get());
|
|
794
|
+
set_option(self->terminal, GHOSTTY_TERMINAL_OPT_WRITE_PTY, (const void*) write_pty);
|
|
795
|
+
set_option(self->terminal, GHOSTTY_TERMINAL_OPT_BELL, (const void*) bell_rang);
|
|
796
|
+
set_option(self->terminal, GHOSTTY_TERMINAL_OPT_TITLE_CHANGED, (const void*) title_changed);
|
|
673
797
|
if (
|
|
674
798
|
ghostty_render_state_new(NULL, &self->render_state) != GHOSTTY_SUCCESS ||
|
|
675
799
|
ghostty_render_state_row_iterator_new(NULL, &self->row_iterator) != GHOSTTY_SUCCESS ||
|
|
@@ -735,20 +859,37 @@ namespace Reflex
|
|
|
735
859
|
system_error(__FILE__, __LINE__, "failed to update a render state");
|
|
736
860
|
|
|
737
861
|
uint16_t columns = 0, rows = 0;
|
|
738
|
-
ghostty_render_state_get(
|
|
739
|
-
|
|
862
|
+
result = ghostty_render_state_get(
|
|
863
|
+
self->render_state, GHOSTTY_RENDER_STATE_DATA_COLS, &columns);
|
|
864
|
+
if (result != GHOSTTY_SUCCESS)
|
|
865
|
+
system_error(__FILE__, __LINE__, "failed to get the columns of a render state");
|
|
866
|
+
result = ghostty_render_state_get(
|
|
867
|
+
self->render_state, GHOSTTY_RENDER_STATE_DATA_ROWS, &rows);
|
|
868
|
+
if (result != GHOSTTY_SUCCESS)
|
|
869
|
+
system_error(__FILE__, __LINE__, "failed to get the rows of a render state");
|
|
740
870
|
if (columns > 0) self->columns = columns;
|
|
741
871
|
if (rows > 0) self->rows = rows;
|
|
742
872
|
|
|
743
873
|
GhosttyRenderStateDirty dirty = GHOSTTY_RENDER_STATE_DIRTY_FALSE;
|
|
744
|
-
ghostty_render_state_get(
|
|
874
|
+
result = ghostty_render_state_get(
|
|
875
|
+
self->render_state, GHOSTTY_RENDER_STATE_DATA_DIRTY, &dirty);
|
|
876
|
+
if (result != GHOSTTY_SUCCESS)
|
|
877
|
+
system_error(__FILE__, __LINE__, "failed to get the dirty state of a render state");
|
|
878
|
+
|
|
879
|
+
Cursor cursor = this->cursor();
|
|
880
|
+
bool cursor_moved = cursor != self->last_cursor;
|
|
881
|
+
self->last_cursor = cursor;
|
|
882
|
+
|
|
745
883
|
if (dirty == GHOSTTY_RENDER_STATE_DIRTY_FALSE && !self->spans.empty())
|
|
746
|
-
return
|
|
884
|
+
return cursor_moved;// moving the cursor dirties no cell, but a renderer draws it
|
|
747
885
|
|
|
748
886
|
rebuild_spans(self.get());
|
|
749
887
|
|
|
750
888
|
GhosttyRenderStateDirty clean = GHOSTTY_RENDER_STATE_DIRTY_FALSE;
|
|
751
|
-
ghostty_render_state_set(
|
|
889
|
+
result = ghostty_render_state_set(
|
|
890
|
+
self->render_state, GHOSTTY_RENDER_STATE_OPTION_DIRTY, &clean);
|
|
891
|
+
if (result != GHOSTTY_SUCCESS)
|
|
892
|
+
system_error(__FILE__, __LINE__, "failed to clear the dirty state of a render state");
|
|
752
893
|
|
|
753
894
|
return true;
|
|
754
895
|
}
|
|
@@ -766,7 +907,7 @@ namespace Reflex
|
|
|
766
907
|
void
|
|
767
908
|
Terminal::resize (
|
|
768
909
|
int columns, int rows,
|
|
769
|
-
|
|
910
|
+
coord cell_width, coord cell_height,
|
|
770
911
|
int screen_width, int screen_height)
|
|
771
912
|
{
|
|
772
913
|
if (
|
|
@@ -776,7 +917,7 @@ namespace Reflex
|
|
|
776
917
|
argument_error(__FILE__, __LINE__, "invalid terminal size: %dx%d", columns, rows);
|
|
777
918
|
}
|
|
778
919
|
if (cell_width <= 0 || cell_height <= 0)
|
|
779
|
-
argument_error(__FILE__, __LINE__, "invalid cell size: %
|
|
920
|
+
argument_error(__FILE__, __LINE__, "invalid cell size: %gx%g", cell_width, cell_height);
|
|
780
921
|
if (!*this)
|
|
781
922
|
invalid_state_error(__FILE__, __LINE__);
|
|
782
923
|
|
|
@@ -785,10 +926,13 @@ namespace Reflex
|
|
|
785
926
|
self->screen_width = screen_width;
|
|
786
927
|
self->screen_height = screen_height;
|
|
787
928
|
|
|
929
|
+
// rounded the same way as encode_mouse rounds, so that the terminal
|
|
930
|
+
// and the mouse encoder agree on the integral cell size
|
|
788
931
|
GhosttyResult result = ghostty_terminal_resize(
|
|
789
932
|
self->terminal,
|
|
790
933
|
(uint16_t) columns, (uint16_t) rows,
|
|
791
|
-
(uint32_t)
|
|
934
|
+
(uint32_t) std::max(1L, lround(cell_width)),
|
|
935
|
+
(uint32_t) std::max(1L, lround(cell_height)));
|
|
792
936
|
if (result != GHOSTTY_SUCCESS)
|
|
793
937
|
system_error(__FILE__, __LINE__, "failed to resize a terminal");
|
|
794
938
|
|
|
@@ -796,7 +940,7 @@ namespace Reflex
|
|
|
796
940
|
self->rows = rows;
|
|
797
941
|
|
|
798
942
|
// sends SIGWINCH to the child process
|
|
799
|
-
self->pty.set_size(columns, rows, cell_width, cell_height);
|
|
943
|
+
self->pty.set_size(columns, rows, (int) cell_width, (int) cell_height);
|
|
800
944
|
}
|
|
801
945
|
|
|
802
946
|
void
|
|
@@ -903,37 +1047,40 @@ namespace Reflex
|
|
|
903
1047
|
uint types = pointer.types();
|
|
904
1048
|
if (!(types & Pointer::MOUSE)) return;// touch/pen: not supported yet
|
|
905
1049
|
|
|
906
|
-
int button = 0;
|
|
907
|
-
if (types & Pointer::MOUSE_LEFT) button = GHOSTTY_MOUSE_BUTTON_LEFT;
|
|
908
|
-
else if (types & Pointer::MOUSE_RIGHT) button = GHOSTTY_MOUSE_BUTTON_RIGHT;
|
|
909
|
-
else if (types & Pointer::MOUSE_MIDDLE) button = GHOSTTY_MOUSE_BUTTON_MIDDLE;
|
|
910
|
-
|
|
911
1050
|
GhosttyMouseAction action;
|
|
912
1051
|
switch (pointer.action())
|
|
913
1052
|
{
|
|
914
|
-
case Pointer::DOWN:
|
|
915
|
-
action = GHOSTTY_MOUSE_ACTION_PRESS;
|
|
916
|
-
self->any_button_pressed = true;
|
|
917
|
-
break;
|
|
918
|
-
|
|
1053
|
+
case Pointer::DOWN: action = GHOSTTY_MOUSE_ACTION_PRESS; break;
|
|
919
1054
|
case Pointer::UP:
|
|
920
|
-
|
|
921
|
-
|
|
1055
|
+
case Pointer::CANCEL: action = GHOSTTY_MOUSE_ACTION_RELEASE; break;
|
|
1056
|
+
case Pointer::MOVE: action = GHOSTTY_MOUSE_ACTION_MOTION; break;
|
|
1057
|
+
default: return;
|
|
1058
|
+
}
|
|
922
1059
|
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
1060
|
+
GhosttyMods mods = to_ghostty_mods(pointer.modifiers());
|
|
1061
|
+
coord x = pointer.position().x;
|
|
1062
|
+
coord y = pointer.position().y;
|
|
926
1063
|
|
|
927
|
-
|
|
1064
|
+
if (action == GHOSTTY_MOUSE_ACTION_MOTION)
|
|
1065
|
+
{
|
|
1066
|
+
encode_mouse(self.get(), action, to_ghostty_button(types), mods, x, y);
|
|
1067
|
+
return;
|
|
928
1068
|
}
|
|
929
1069
|
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
1070
|
+
// a press or a release names the one button that changed, but a cancel
|
|
1071
|
+
// names every button being pressed, and each of them ends here
|
|
1072
|
+
|
|
1073
|
+
bool press = action == GHOSTTY_MOUSE_ACTION_PRESS;
|
|
1074
|
+
for (uint button : {Pointer::MOUSE_LEFT, Pointer::MOUSE_RIGHT, Pointer::MOUSE_MIDDLE})
|
|
1075
|
+
{
|
|
1076
|
+
if (!(types & button)) continue;
|
|
1077
|
+
|
|
1078
|
+
if (press) self->pressed_buttons |= button;
|
|
934
1079
|
|
|
935
|
-
|
|
936
|
-
|
|
1080
|
+
encode_mouse(self.get(), action, to_ghostty_button(button), mods, x, y);
|
|
1081
|
+
|
|
1082
|
+
if (!press) self->pressed_buttons &= ~button;
|
|
1083
|
+
}
|
|
937
1084
|
}
|
|
938
1085
|
|
|
939
1086
|
void
|
|
@@ -942,16 +1089,18 @@ namespace Reflex
|
|
|
942
1089
|
if (!*this)
|
|
943
1090
|
invalid_state_error(__FILE__, __LINE__);
|
|
944
1091
|
|
|
945
|
-
|
|
946
|
-
|
|
1092
|
+
self->wheel_rows += event.dposition().y / self->cell_height;
|
|
1093
|
+
int rows = (int) self->wheel_rows;
|
|
1094
|
+
if (rows == 0) return;
|
|
1095
|
+
self->wheel_rows -= rows;
|
|
947
1096
|
|
|
948
|
-
int button =
|
|
1097
|
+
int button = rows > 0 ? GHOSTTY_MOUSE_BUTTON_FIVE : GHOSTTY_MOUSE_BUTTON_FOUR;
|
|
949
1098
|
GhosttyMods mods = to_ghostty_mods(event.modifiers());
|
|
950
1099
|
float x = event.position().x;
|
|
951
1100
|
float y = event.position().y;
|
|
952
1101
|
|
|
953
1102
|
enum {MAX_STEPS = 8};
|
|
954
|
-
int steps =
|
|
1103
|
+
int steps = rows > 0 ? rows : -rows;
|
|
955
1104
|
if (steps > MAX_STEPS) steps = MAX_STEPS;
|
|
956
1105
|
|
|
957
1106
|
for (int i = 0; i < steps; ++i)
|
|
@@ -969,8 +1118,9 @@ namespace Reflex
|
|
|
969
1118
|
if (!*this)
|
|
970
1119
|
invalid_state_error(__FILE__, __LINE__);
|
|
971
1120
|
|
|
972
|
-
|
|
973
|
-
|
|
1121
|
+
GhosttyTerminalModeConfig mode = {GHOSTTY_MODE_BRACKETED_PASTE, false};
|
|
1122
|
+
ghostty_terminal_get(self->terminal, GHOSTTY_TERMINAL_DATA_MODE, &mode);
|
|
1123
|
+
bool bracketed = mode.value;
|
|
974
1124
|
|
|
975
1125
|
// ghostty sanitizes the text in place
|
|
976
1126
|
String input(text, size);
|
|
@@ -996,14 +1146,21 @@ namespace Reflex
|
|
|
996
1146
|
return self && self->pty.is_child_alive();
|
|
997
1147
|
}
|
|
998
1148
|
|
|
1149
|
+
bool
|
|
1150
|
+
Terminal::is_blinking () const
|
|
1151
|
+
{
|
|
1152
|
+
return self->spans_blink;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
999
1155
|
bool
|
|
1000
1156
|
Terminal::is_mouse_tracking () const
|
|
1001
1157
|
{
|
|
1002
1158
|
if (!*this) return false;
|
|
1003
1159
|
|
|
1004
|
-
bool tracking
|
|
1005
|
-
|
|
1006
|
-
|
|
1160
|
+
bool tracking = false;
|
|
1161
|
+
GhosttyResult result =
|
|
1162
|
+
ghostty_terminal_get(self->terminal, GHOSTTY_TERMINAL_DATA_MOUSE_TRACKING, &tracking);
|
|
1163
|
+
return result == GHOSTTY_SUCCESS && tracking;
|
|
1007
1164
|
}
|
|
1008
1165
|
|
|
1009
1166
|
static GhosttyTerminalScrollbar
|
|
@@ -1035,7 +1192,7 @@ namespace Reflex
|
|
|
1035
1192
|
{
|
|
1036
1193
|
// the terminal copies it and takes to tracking the text it covers,
|
|
1037
1194
|
// so the snapshot is ours to drop once this returns
|
|
1038
|
-
|
|
1195
|
+
set_option(self->terminal, GHOSTTY_TERMINAL_OPT_SELECTION, selection);
|
|
1039
1196
|
}
|
|
1040
1197
|
|
|
1041
1198
|
static void
|
|
@@ -1228,7 +1385,7 @@ namespace Reflex
|
|
|
1228
1385
|
Terminal::Cursor
|
|
1229
1386
|
Terminal::cursor () const
|
|
1230
1387
|
{
|
|
1231
|
-
Cursor cursor
|
|
1388
|
+
Cursor cursor;
|
|
1232
1389
|
if (!*this) return cursor;
|
|
1233
1390
|
|
|
1234
1391
|
bool has_value = false;
|
|
@@ -1256,14 +1413,6 @@ namespace Reflex
|
|
|
1256
1413
|
return cursor;
|
|
1257
1414
|
}
|
|
1258
1415
|
|
|
1259
|
-
enum {PALETTE_SIZE = Terminal::COLOR_PALETTE_LAST - Terminal::COLOR_PALETTE_FIRST + 1};
|
|
1260
|
-
|
|
1261
|
-
static bool
|
|
1262
|
-
is_palette (Terminal::ColorIndex index)
|
|
1263
|
-
{
|
|
1264
|
-
return Terminal::COLOR_PALETTE_FIRST <= index && index <= Terminal::COLOR_PALETTE_LAST;
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
1416
|
static GhosttyColorRgb
|
|
1268
1417
|
to_ghostty_color (const Color& color)
|
|
1269
1418
|
{
|
|
@@ -1314,23 +1463,6 @@ namespace Reflex
|
|
|
1314
1463
|
return GHOSTTY_TERMINAL_DATA_COLOR_FOREGROUND;
|
|
1315
1464
|
}
|
|
1316
1465
|
|
|
1317
|
-
static void
|
|
1318
|
-
get_palette (GhosttyTerminal terminal, GhosttyColorRgb* palette, bool default_)
|
|
1319
|
-
{
|
|
1320
|
-
GhosttyTerminalData data = default_
|
|
1321
|
-
? GHOSTTY_TERMINAL_DATA_COLOR_PALETTE_DEFAULT
|
|
1322
|
-
: GHOSTTY_TERMINAL_DATA_COLOR_PALETTE;
|
|
1323
|
-
|
|
1324
|
-
if (ghostty_terminal_get(terminal, data, palette) != GHOSTTY_SUCCESS)
|
|
1325
|
-
system_error(__FILE__, __LINE__, "failed to get a terminal palette");
|
|
1326
|
-
}
|
|
1327
|
-
|
|
1328
|
-
static void
|
|
1329
|
-
set_palette (GhosttyTerminal terminal, const GhosttyColorRgb* palette)
|
|
1330
|
-
{
|
|
1331
|
-
ghostty_terminal_set(terminal, GHOSTTY_TERMINAL_OPT_COLOR_PALETTE, palette);
|
|
1332
|
-
}
|
|
1333
|
-
|
|
1334
1466
|
static bool
|
|
1335
1467
|
get_terminal_color (
|
|
1336
1468
|
const Terminal& terminal, Terminal::ColorIndex index, Color* color, bool default_)
|
|
@@ -1373,7 +1505,7 @@ namespace Reflex
|
|
|
1373
1505
|
set_palette(self->terminal, palette);
|
|
1374
1506
|
}
|
|
1375
1507
|
else
|
|
1376
|
-
|
|
1508
|
+
set_option(self->terminal, to_color_option(index), &rgb);
|
|
1377
1509
|
}
|
|
1378
1510
|
|
|
1379
1511
|
void
|
|
@@ -1394,7 +1526,7 @@ namespace Reflex
|
|
|
1394
1526
|
set_palette(self->terminal, palette);
|
|
1395
1527
|
}
|
|
1396
1528
|
else
|
|
1397
|
-
|
|
1529
|
+
set_option(self->terminal, to_color_option(index), NULL);
|
|
1398
1530
|
}
|
|
1399
1531
|
|
|
1400
1532
|
bool
|
data/src/win32/pty.cpp
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// ConPTY needs windows 10 1809
|
|
2
2
|
#define XOT_WIN32_WINNT 0x0A00// for <xot/windows.h>
|
|
3
3
|
#define NTDDI_VERSION 0x0A000006// NTDDI_WIN10_RS5
|
|
4
|
+
#include <xot/windows.h>
|
|
4
5
|
|
|
5
6
|
#include "../terminal.h"
|
|
6
7
|
|
|
@@ -11,7 +12,6 @@
|
|
|
11
12
|
#include <string>
|
|
12
13
|
#include <vector>
|
|
13
14
|
#include <map>
|
|
14
|
-
#include <xot/windows.h>
|
|
15
15
|
#include <reflex/exception.h>
|
|
16
16
|
|
|
17
17
|
|
|
@@ -200,7 +200,7 @@ namespace Reflex
|
|
|
200
200
|
|
|
201
201
|
STARTUPINFOEXW startup = {};
|
|
202
202
|
startup.StartupInfo.cb = sizeof(startup);
|
|
203
|
-
startup.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)
|
|
203
|
+
startup.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST) attrs.data();
|
|
204
204
|
|
|
205
205
|
if (
|
|
206
206
|
!InitializeProcThreadAttributeList(startup.lpAttributeList, 1, 0, &attrs_size) ||
|