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.
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
- bool any_button_pressed = false;
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
- int cell_width = 8, cell_height = 16;
60
+ coord cell_width = 8, cell_height = 16;
55
61
 
56
62
  int screen_width = 0, screen_height = 0;
57
63
 
58
- PTY pty;
64
+ uint pressed_buttons = 0;
59
65
 
60
- String pending_input;
66
+ float wheel_rows = 0;
61
67
 
62
- String title;
68
+ longlong bells = 0;
63
69
 
64
- longlong bells = 0;
70
+ bool spans_blink = false;
65
71
 
66
- RowList spans;
72
+ Cursor last_cursor;
67
73
 
68
- std::vector<uint> cell_offsets;
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
- ghostty_terminal_mode_set(terminal, GHOSTTY_MODE_GRAPHEME_CLUSTER, true);
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
- // UNDERLINE_MASK is a 3-bit field holding ghostty's style number --
155
- // 0: none, 1: single, 2: double, 3: curly, 4: dotted, 5: dashed
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 when it is printable and no
391
- // command/meta modifier is in effect
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->screen_width > 0
440
- ? self->screen_width : self->columns * self->cell_width;
441
- size.screen_height = self->screen_height > 0
442
- ? self->screen_height : self->rows * self->cell_height;
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, &self->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 = {x, y};
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->attribs != attribs ||
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
- GhosttyTerminalOptions options = {};
656
- options.cols = (uint16_t) columns;
657
- options.rows = (uint16_t) rows;
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, options.cols, options.rows, self->cell_width, self->cell_height);
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
- ghostty_terminal_set(self->terminal, GHOSTTY_TERMINAL_OPT_USERDATA, self.get());
669
- ghostty_terminal_set(self->terminal, GHOSTTY_TERMINAL_OPT_WRITE_PTY, (const void*) write_pty);
670
- ghostty_terminal_set(self->terminal, GHOSTTY_TERMINAL_OPT_BELL, (const void*) bell_rang);
671
- ghostty_terminal_set(
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(self->render_state, GHOSTTY_RENDER_STATE_DATA_COLS, &columns);
739
- ghostty_render_state_get(self->render_state, GHOSTTY_RENDER_STATE_DATA_ROWS, &rows);
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(self->render_state, GHOSTTY_RENDER_STATE_DATA_DIRTY, &dirty);
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 false;
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(self->render_state, GHOSTTY_RENDER_STATE_OPTION_DIRTY, &clean);
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
- int cell_width, int cell_height,
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: %dx%d", cell_width, cell_height);
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) cell_width, (uint32_t) cell_height);
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
- action = GHOSTTY_MOUSE_ACTION_RELEASE;
921
- break;
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
- case Pointer::MOVE:
924
- action = GHOSTTY_MOUSE_ACTION_MOTION;
925
- break;
1060
+ GhosttyMods mods = to_ghostty_mods(pointer.modifiers());
1061
+ coord x = pointer.position().x;
1062
+ coord y = pointer.position().y;
926
1063
 
927
- default: return;
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
- encode_mouse(
931
- self.get(), action, button,
932
- to_ghostty_mods(pointer.modifiers()),
933
- pointer.position().x, pointer.position().y);
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
- if (pointer.action() == Pointer::UP)
936
- self->any_button_pressed = false;
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
- int dy = (int) event.dposition().y;
946
- if (dy == 0) return;
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 = dy > 0 ? GHOSTTY_MOUSE_BUTTON_FIVE : GHOSTTY_MOUSE_BUTTON_FOUR;
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 = dy > 0 ? dy : -dy;
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
- bool bracketed = false;
973
- ghostty_terminal_mode_get(self->terminal, GHOSTTY_MODE_BRACKETED_PASTE, &bracketed);
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 = false;
1005
- ghostty_terminal_get(self->terminal, GHOSTTY_TERMINAL_DATA_MOUSE_TRACKING, &tracking);
1006
- return tracking;
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
- ghostty_terminal_set(self->terminal, GHOSTTY_TERMINAL_OPT_SELECTION, selection);
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 = {0, 0, Cursor::BLOCK, false};
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
- ghostty_terminal_set(self->terminal, to_color_option(index), &rgb);
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
- ghostty_terminal_set(self->terminal, to_color_option(index), NULL);
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) &attrs[0];
203
+ startup.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST) attrs.data();
204
204
 
205
205
  if (
206
206
  !InitializeProcThreadAttributeList(startup.lpAttributeList, 1, 0, &attrs_size) ||