bubbletea 0.1.0-aarch64-linux-musl

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.
@@ -0,0 +1,251 @@
1
+ #include "extension.h"
2
+
3
+ static void program_free(void *pointer) {
4
+ bubbletea_program_t *program = (bubbletea_program_t *)pointer;
5
+
6
+ if (program->handle != 0) {
7
+ tea_free_program(program->handle);
8
+ }
9
+
10
+ xfree(program);
11
+ }
12
+
13
+ static size_t program_memsize(const void *pointer) {
14
+ return sizeof(bubbletea_program_t);
15
+ }
16
+
17
+ const rb_data_type_t program_type = {
18
+ .wrap_struct_name = "Bubbletea::Program",
19
+ .function = {
20
+ .dmark = NULL,
21
+ .dfree = program_free,
22
+ .dsize = program_memsize,
23
+ },
24
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY
25
+ };
26
+
27
+ static VALUE program_alloc(VALUE klass) {
28
+ bubbletea_program_t *program = ALLOC(bubbletea_program_t);
29
+ program->handle = tea_new_program();
30
+ return TypedData_Wrap_Struct(klass, &program_type, program);
31
+ }
32
+
33
+ static VALUE program_initialize(VALUE self) {
34
+ GET_PROGRAM(self, program);
35
+
36
+ tea_terminal_init(program->handle);
37
+
38
+ return self;
39
+ }
40
+
41
+ /* Terminal control methods */
42
+
43
+ static VALUE program_enter_raw_mode(VALUE self) {
44
+ GET_PROGRAM(self, program);
45
+ return tea_terminal_enter_raw_mode(program->handle) == 0 ? Qtrue : Qfalse;
46
+ }
47
+
48
+ static VALUE program_exit_raw_mode(VALUE self) {
49
+ GET_PROGRAM(self, program);
50
+ return tea_terminal_exit_raw_mode(program->handle) == 0 ? Qtrue : Qfalse;
51
+ }
52
+
53
+ static VALUE program_enter_alt_screen(VALUE self) {
54
+ GET_PROGRAM(self, program);
55
+ tea_terminal_enter_alt_screen(program->handle);
56
+ return Qnil;
57
+ }
58
+
59
+ static VALUE program_exit_alt_screen(VALUE self) {
60
+ GET_PROGRAM(self, program);
61
+ tea_terminal_exit_alt_screen(program->handle);
62
+ return Qnil;
63
+ }
64
+
65
+ static VALUE program_hide_cursor(VALUE self) {
66
+ GET_PROGRAM(self, program);
67
+ tea_terminal_hide_cursor(program->handle);
68
+ return Qnil;
69
+ }
70
+
71
+ static VALUE program_show_cursor(VALUE self) {
72
+ GET_PROGRAM(self, program);
73
+ tea_terminal_show_cursor(program->handle);
74
+ return Qnil;
75
+ }
76
+
77
+ static VALUE program_enable_mouse_cell_motion(VALUE self) {
78
+ GET_PROGRAM(self, program);
79
+ tea_terminal_enable_mouse_cell_motion(program->handle);
80
+ return Qnil;
81
+ }
82
+
83
+ static VALUE program_enable_mouse_all_motion(VALUE self) {
84
+ GET_PROGRAM(self, program);
85
+ tea_terminal_enable_mouse_all_motion(program->handle);
86
+ return Qnil;
87
+ }
88
+
89
+ static VALUE program_disable_mouse(VALUE self) {
90
+ GET_PROGRAM(self, program);
91
+ tea_terminal_disable_mouse(program->handle);
92
+ return Qnil;
93
+ }
94
+
95
+ static VALUE program_enable_bracketed_paste(VALUE self) {
96
+ GET_PROGRAM(self, program);
97
+ tea_terminal_enable_bracketed_paste(program->handle);
98
+ return Qnil;
99
+ }
100
+
101
+ static VALUE program_disable_bracketed_paste(VALUE self) {
102
+ GET_PROGRAM(self, program);
103
+ tea_terminal_disable_bracketed_paste(program->handle);
104
+ return Qnil;
105
+ }
106
+
107
+ static VALUE program_enable_report_focus(VALUE self) {
108
+ GET_PROGRAM(self, program);
109
+ tea_terminal_enable_report_focus(program->handle);
110
+ return Qnil;
111
+ }
112
+
113
+ static VALUE program_disable_report_focus(VALUE self) {
114
+ GET_PROGRAM(self, program);
115
+ tea_terminal_disable_report_focus(program->handle);
116
+ return Qnil;
117
+ }
118
+
119
+ static VALUE program_terminal_size(VALUE self) {
120
+ GET_PROGRAM(self, program);
121
+ int width, height;
122
+
123
+ if (tea_terminal_get_size(program->handle, &width, &height) == 0) {
124
+ return rb_ary_new_from_args(2, INT2NUM(width), INT2NUM(height));
125
+ }
126
+
127
+ return Qnil;
128
+ }
129
+
130
+ /* Input methods */
131
+
132
+ static VALUE program_start_input_reader(VALUE self) {
133
+ GET_PROGRAM(self, program);
134
+ return tea_input_start_reader(program->handle) == 0 ? Qtrue : Qfalse;
135
+ }
136
+
137
+ static VALUE program_stop_input_reader(VALUE self) {
138
+ GET_PROGRAM(self, program);
139
+ tea_input_stop_reader(program->handle);
140
+ return Qnil;
141
+ }
142
+
143
+ static VALUE program_read_raw_input(VALUE self, VALUE timeout_ms) {
144
+ GET_PROGRAM(self, program);
145
+
146
+ char buffer[256];
147
+ int bytes_read = tea_input_read_raw(program->handle, buffer, sizeof(buffer), NUM2INT(timeout_ms));
148
+
149
+ if (bytes_read > 0) {
150
+ return rb_str_new(buffer, bytes_read);
151
+ } else if (bytes_read == 0) {
152
+ return Qnil; // Timeout
153
+ } else {
154
+ return Qnil; // Error
155
+ }
156
+ }
157
+
158
+ static VALUE program_poll_event(VALUE self, VALUE timeout_ms) {
159
+ GET_PROGRAM(self, program);
160
+
161
+ char buffer[256];
162
+ int bytes_read = tea_input_read_raw(program->handle, buffer, sizeof(buffer), NUM2INT(timeout_ms));
163
+
164
+ if (bytes_read <= 0) {
165
+ return Qnil; // Timeout or error
166
+ }
167
+
168
+ int consumed;
169
+ char *json = tea_parse_input_with_consumed(buffer, bytes_read, &consumed);
170
+
171
+ if (json == NULL || json[0] == '\0') {
172
+ tea_free(json);
173
+ return Qnil;
174
+ }
175
+
176
+ VALUE rb_json = rb_utf8_str_new_cstr(json);
177
+ tea_free(json);
178
+
179
+ VALUE rb_json_module = rb_const_get(rb_cObject, rb_intern("JSON"));
180
+ VALUE rb_hash = rb_funcall(rb_json_module, rb_intern("parse"), 1, rb_json);
181
+
182
+ return rb_hash;
183
+ }
184
+
185
+ /* Renderer methods */
186
+
187
+ static VALUE program_create_renderer(VALUE self) {
188
+ GET_PROGRAM(self, program);
189
+ unsigned long long renderer_id = tea_renderer_new(program->handle);
190
+ return ULL2NUM(renderer_id);
191
+ }
192
+
193
+ static VALUE program_render(VALUE self, VALUE renderer_id, VALUE view) {
194
+ Check_Type(view, T_STRING);
195
+ tea_renderer_render(NUM2ULL(renderer_id), StringValueCStr(view));
196
+ return Qnil;
197
+ }
198
+
199
+ static VALUE program_renderer_set_size(VALUE self, VALUE renderer_id, VALUE width, VALUE height) {
200
+ tea_renderer_set_size(NUM2ULL(renderer_id), NUM2INT(width), NUM2INT(height));
201
+ return Qnil;
202
+ }
203
+
204
+ static VALUE program_renderer_set_alt_screen(VALUE self, VALUE renderer_id, VALUE enabled) {
205
+ tea_renderer_set_alt_screen(NUM2ULL(renderer_id), RTEST(enabled) ? 1 : 0);
206
+ return Qnil;
207
+ }
208
+
209
+ static VALUE program_renderer_clear(VALUE self, VALUE renderer_id) {
210
+ tea_renderer_clear(NUM2ULL(renderer_id));
211
+ return Qnil;
212
+ }
213
+
214
+ static VALUE program_string_width(VALUE self, VALUE str) {
215
+ Check_Type(str, T_STRING);
216
+ return INT2NUM(tea_string_width(StringValueCStr(str)));
217
+ }
218
+
219
+ void Init_bubbletea_program(void) {
220
+ cProgram = rb_define_class_under(mBubbletea, "Program", rb_cObject);
221
+
222
+ rb_define_alloc_func(cProgram, program_alloc);
223
+ rb_define_method(cProgram, "initialize", program_initialize, 0);
224
+
225
+ rb_define_method(cProgram, "enter_raw_mode", program_enter_raw_mode, 0);
226
+ rb_define_method(cProgram, "exit_raw_mode", program_exit_raw_mode, 0);
227
+ rb_define_method(cProgram, "enter_alt_screen", program_enter_alt_screen, 0);
228
+ rb_define_method(cProgram, "exit_alt_screen", program_exit_alt_screen, 0);
229
+ rb_define_method(cProgram, "hide_cursor", program_hide_cursor, 0);
230
+ rb_define_method(cProgram, "show_cursor", program_show_cursor, 0);
231
+ rb_define_method(cProgram, "enable_mouse_cell_motion", program_enable_mouse_cell_motion, 0);
232
+ rb_define_method(cProgram, "enable_mouse_all_motion", program_enable_mouse_all_motion, 0);
233
+ rb_define_method(cProgram, "disable_mouse", program_disable_mouse, 0);
234
+ rb_define_method(cProgram, "enable_bracketed_paste", program_enable_bracketed_paste, 0);
235
+ rb_define_method(cProgram, "disable_bracketed_paste", program_disable_bracketed_paste, 0);
236
+ rb_define_method(cProgram, "enable_report_focus", program_enable_report_focus, 0);
237
+ rb_define_method(cProgram, "disable_report_focus", program_disable_report_focus, 0);
238
+ rb_define_method(cProgram, "terminal_size", program_terminal_size, 0);
239
+
240
+ rb_define_method(cProgram, "start_input_reader", program_start_input_reader, 0);
241
+ rb_define_method(cProgram, "stop_input_reader", program_stop_input_reader, 0);
242
+ rb_define_method(cProgram, "read_raw_input", program_read_raw_input, 1);
243
+ rb_define_method(cProgram, "poll_event", program_poll_event, 1);
244
+
245
+ rb_define_method(cProgram, "create_renderer", program_create_renderer, 0);
246
+ rb_define_method(cProgram, "render", program_render, 2);
247
+ rb_define_method(cProgram, "renderer_set_size", program_renderer_set_size, 3);
248
+ rb_define_method(cProgram, "renderer_set_alt_screen", program_renderer_set_alt_screen, 2);
249
+ rb_define_method(cProgram, "renderer_clear", program_renderer_clear, 1);
250
+ rb_define_method(cProgram, "string_width", program_string_width, 1);
251
+ }
data/go/bubbletea.go ADDED
@@ -0,0 +1,98 @@
1
+ package main
2
+
3
+ /*
4
+ #include <stdlib.h>
5
+ */
6
+ import "C"
7
+
8
+ import (
9
+ "runtime/debug"
10
+ "sync"
11
+ "unsafe"
12
+ )
13
+
14
+ var (
15
+ nextID uint64 = 1
16
+ nextIDMu sync.Mutex
17
+ )
18
+
19
+ func getNextID() uint64 {
20
+ nextIDMu.Lock()
21
+ defer nextIDMu.Unlock()
22
+ id := nextID
23
+ nextID++
24
+ return id
25
+ }
26
+
27
+ var (
28
+ programs = make(map[uint64]*ProgramState)
29
+ programsMu sync.RWMutex
30
+ )
31
+
32
+ type ProgramState struct {
33
+ terminal *Terminal
34
+ input *InputReader
35
+ width int
36
+ height int
37
+ }
38
+
39
+ func getProgram(id uint64) *ProgramState {
40
+ programsMu.RLock()
41
+ defer programsMu.RUnlock()
42
+ return programs[id]
43
+ }
44
+
45
+ //export tea_free
46
+ func tea_free(pointer *C.char) {
47
+ C.free(unsafe.Pointer(pointer))
48
+ }
49
+
50
+ //export tea_new_program
51
+ func tea_new_program() C.ulonglong {
52
+ state := &ProgramState{}
53
+
54
+ programsMu.Lock()
55
+ id := getNextID()
56
+ programs[id] = state
57
+ programsMu.Unlock()
58
+
59
+ return C.ulonglong(id)
60
+ }
61
+
62
+ //export tea_free_program
63
+ func tea_free_program(id C.ulonglong) {
64
+ programsMu.Lock()
65
+ defer programsMu.Unlock()
66
+
67
+ state := programs[uint64(id)]
68
+ if state != nil {
69
+ if state.input != nil {
70
+ state.input.Stop()
71
+ }
72
+
73
+ if state.terminal != nil {
74
+ state.terminal.Restore()
75
+ }
76
+ }
77
+
78
+ delete(programs, uint64(id))
79
+ }
80
+
81
+ //export tea_upstream_version
82
+ func tea_upstream_version() *C.char {
83
+ info, ok := debug.ReadBuildInfo()
84
+
85
+ if !ok {
86
+ return C.CString("unknown")
87
+ }
88
+
89
+ for _, dep := range info.Deps {
90
+ if dep.Path == "github.com/charmbracelet/x/ansi" {
91
+ return C.CString(dep.Version)
92
+ }
93
+ }
94
+
95
+ return C.CString("unknown")
96
+ }
97
+
98
+ func main() {}
Binary file
@@ -0,0 +1,147 @@
1
+ /* Code generated by cmd/cgo; DO NOT EDIT. */
2
+
3
+ /* package github.com/marcoroth/bubbletea-ruby/go */
4
+
5
+
6
+ #line 1 "cgo-builtin-export-prolog"
7
+
8
+ #include <stddef.h>
9
+
10
+ #ifndef GO_CGO_EXPORT_PROLOGUE_H
11
+ #define GO_CGO_EXPORT_PROLOGUE_H
12
+
13
+ #ifndef GO_CGO_GOSTRING_TYPEDEF
14
+ typedef struct { const char *p; ptrdiff_t n; } _GoString_;
15
+ #endif
16
+
17
+ #endif
18
+
19
+ /* Start of preamble from import "C" comments. */
20
+
21
+
22
+ #line 3 "bubbletea.go"
23
+
24
+ #include <stdlib.h>
25
+
26
+ #line 1 "cgo-generated-wrapper"
27
+
28
+ #line 3 "input.go"
29
+
30
+ #include <stdlib.h>
31
+
32
+ #line 1 "cgo-generated-wrapper"
33
+
34
+ #line 3 "keys.go"
35
+
36
+ #include <stdlib.h>
37
+
38
+ #line 1 "cgo-generated-wrapper"
39
+
40
+ #line 3 "renderer.go"
41
+
42
+ #include <stdlib.h>
43
+
44
+ #line 1 "cgo-generated-wrapper"
45
+
46
+ #line 3 "terminal.go"
47
+
48
+ #include <stdlib.h>
49
+
50
+ #line 1 "cgo-generated-wrapper"
51
+
52
+
53
+ /* End of preamble from import "C" comments. */
54
+
55
+
56
+ /* Start of boilerplate cgo prologue. */
57
+ #line 1 "cgo-gcc-export-header-prolog"
58
+
59
+ #ifndef GO_CGO_PROLOGUE_H
60
+ #define GO_CGO_PROLOGUE_H
61
+
62
+ typedef signed char GoInt8;
63
+ typedef unsigned char GoUint8;
64
+ typedef short GoInt16;
65
+ typedef unsigned short GoUint16;
66
+ typedef int GoInt32;
67
+ typedef unsigned int GoUint32;
68
+ typedef long long GoInt64;
69
+ typedef unsigned long long GoUint64;
70
+ typedef GoInt64 GoInt;
71
+ typedef GoUint64 GoUint;
72
+ typedef size_t GoUintptr;
73
+ typedef float GoFloat32;
74
+ typedef double GoFloat64;
75
+ #ifdef _MSC_VER
76
+ #include <complex.h>
77
+ typedef _Fcomplex GoComplex64;
78
+ typedef _Dcomplex GoComplex128;
79
+ #else
80
+ typedef float _Complex GoComplex64;
81
+ typedef double _Complex GoComplex128;
82
+ #endif
83
+
84
+ /*
85
+ static assertion to make sure the file is being used on architecture
86
+ at least with matching size of GoInt.
87
+ */
88
+ typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
89
+
90
+ #ifndef GO_CGO_GOSTRING_TYPEDEF
91
+ typedef _GoString_ GoString;
92
+ #endif
93
+ typedef void *GoMap;
94
+ typedef void *GoChan;
95
+ typedef struct { void *t; void *v; } GoInterface;
96
+ typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
97
+
98
+ #endif
99
+
100
+ /* End of boilerplate cgo prologue. */
101
+
102
+ #ifdef __cplusplus
103
+ extern "C" {
104
+ #endif
105
+
106
+ extern void tea_free(char* pointer);
107
+ extern long long unsigned int tea_new_program();
108
+ extern void tea_free_program(long long unsigned int id);
109
+ extern char* tea_upstream_version();
110
+ extern int tea_input_start_reader(long long unsigned int programID);
111
+ extern void tea_input_stop_reader(long long unsigned int programID);
112
+ extern int tea_input_read_raw(long long unsigned int programID, char* buffer, int bufferSize, int timeoutMs);
113
+ extern char* tea_parse_input(char* data, int dataLength);
114
+ extern char* tea_parse_input_with_consumed(char* data, int dataLength, int* consumed);
115
+ extern char* tea_get_key_name(int keyType);
116
+ extern long long unsigned int tea_renderer_new(long long unsigned int programID);
117
+ extern void tea_renderer_free(long long unsigned int id);
118
+ extern void tea_renderer_set_size(long long unsigned int id, int width, int height);
119
+ extern void tea_renderer_set_alt_screen(long long unsigned int id, int enabled);
120
+ extern void tea_renderer_render(long long unsigned int id, char* view);
121
+ extern void tea_renderer_clear(long long unsigned int id);
122
+ extern int tea_string_width(char* s);
123
+ extern char* tea_truncate_string(char* s, int width);
124
+ extern int tea_terminal_init(long long unsigned int programID);
125
+ extern int tea_terminal_enter_raw_mode(long long unsigned int programID);
126
+ extern int tea_terminal_exit_raw_mode(long long unsigned int programID);
127
+ extern void tea_terminal_enter_alt_screen(long long unsigned int programID);
128
+ extern void tea_terminal_exit_alt_screen(long long unsigned int programID);
129
+ extern void tea_terminal_hide_cursor(long long unsigned int programID);
130
+ extern void tea_terminal_show_cursor(long long unsigned int programID);
131
+ extern void tea_terminal_enable_mouse_cell_motion(long long unsigned int programID);
132
+ extern void tea_terminal_enable_mouse_all_motion(long long unsigned int programID);
133
+ extern void tea_terminal_disable_mouse(long long unsigned int programID);
134
+ extern void tea_terminal_enable_bracketed_paste(long long unsigned int programID);
135
+ extern void tea_terminal_disable_bracketed_paste(long long unsigned int programID);
136
+ extern void tea_terminal_enable_report_focus(long long unsigned int programID);
137
+ extern void tea_terminal_disable_report_focus(long long unsigned int programID);
138
+ extern int tea_terminal_get_size(long long unsigned int programID, int* widthOut, int* heightOut);
139
+ extern void tea_terminal_set_window_title(char* title);
140
+ extern int tea_terminal_is_tty();
141
+ extern void tea_terminal_clear_screen();
142
+ extern void tea_terminal_erase_line();
143
+ extern void tea_terminal_cursor_home();
144
+
145
+ #ifdef __cplusplus
146
+ }
147
+ #endif
data/go/go.mod ADDED
@@ -0,0 +1,16 @@
1
+ module github.com/marcoroth/bubbletea-ruby/go
2
+
3
+ go 1.23.0
4
+
5
+ require (
6
+ github.com/charmbracelet/x/ansi v0.8.0
7
+ github.com/charmbracelet/x/term v0.2.1
8
+ github.com/muesli/cancelreader v0.2.2
9
+ )
10
+
11
+ require (
12
+ github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
13
+ github.com/mattn/go-runewidth v0.0.16 // indirect
14
+ github.com/rivo/uniseg v0.4.7 // indirect
15
+ golang.org/x/sys v0.30.0 // indirect
16
+ )
data/go/go.sum ADDED
@@ -0,0 +1,15 @@
1
+ github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
2
+ github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
3
+ github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
4
+ github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
5
+ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
6
+ github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
7
+ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
8
+ github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
9
+ github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
10
+ github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
11
+ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
12
+ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
13
+ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
14
+ golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
15
+ golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
data/go/input.go ADDED
@@ -0,0 +1,158 @@
1
+ package main
2
+
3
+ /*
4
+ #include <stdlib.h>
5
+ */
6
+ import "C"
7
+
8
+ import (
9
+ "context"
10
+ "os"
11
+ "sync"
12
+ "time"
13
+ "unsafe"
14
+ "github.com/muesli/cancelreader"
15
+ )
16
+
17
+ type InputReader struct {
18
+ cancelReader cancelreader.CancelReader
19
+ ctx context.Context
20
+ cancel context.CancelFunc
21
+ events chan []byte
22
+ mu sync.Mutex
23
+ running bool
24
+ }
25
+
26
+ func NewInputReader() (*InputReader, error) {
27
+ reader, err := cancelreader.NewReader(os.Stdin)
28
+
29
+ if err != nil {
30
+ return nil, err
31
+ }
32
+
33
+ ctx, cancel := context.WithCancel(context.Background())
34
+
35
+ return &InputReader{
36
+ cancelReader: reader,
37
+ ctx: ctx,
38
+ cancel: cancel,
39
+ events: make(chan []byte, 100),
40
+ }, nil
41
+ }
42
+
43
+ func (reader *InputReader) Start() {
44
+ reader.mu.Lock()
45
+
46
+ if reader.running {
47
+ reader.mu.Unlock()
48
+ return
49
+ }
50
+
51
+ reader.running = true
52
+ reader.mu.Unlock()
53
+
54
+ go reader.readLoop()
55
+ }
56
+
57
+ func (reader *InputReader) Stop() {
58
+ reader.mu.Lock()
59
+ defer reader.mu.Unlock()
60
+
61
+ if !reader.running {
62
+ return
63
+ }
64
+
65
+ reader.running = false
66
+ reader.cancel()
67
+ reader.cancelReader.Cancel()
68
+ reader.cancelReader.Close()
69
+ }
70
+
71
+ func (reader *InputReader) readLoop() {
72
+ var buf [256]byte
73
+
74
+ for {
75
+ select {
76
+ case <-reader.ctx.Done():
77
+ return
78
+ default:
79
+ }
80
+
81
+ n, err := reader.cancelReader.Read(buf[:])
82
+ if err != nil {
83
+ return
84
+ }
85
+
86
+ if n > 0 {
87
+ data := make([]byte, n)
88
+ copy(data, buf[:n])
89
+
90
+ select {
91
+ case reader.events <- data:
92
+ case <-reader.ctx.Done():
93
+ return
94
+ }
95
+ }
96
+ }
97
+ }
98
+
99
+ //export tea_input_start_reader
100
+ func tea_input_start_reader(programID C.ulonglong) C.int {
101
+ state := getProgram(uint64(programID))
102
+ if state == nil {
103
+ return -1
104
+ }
105
+
106
+ if state.input != nil {
107
+ return 0
108
+ }
109
+
110
+ reader, err := NewInputReader()
111
+ if err != nil {
112
+ return -1
113
+ }
114
+
115
+ state.input = reader
116
+ reader.Start()
117
+
118
+ return 0
119
+ }
120
+
121
+ //export tea_input_stop_reader
122
+ func tea_input_stop_reader(programID C.ulonglong) {
123
+ state := getProgram(uint64(programID))
124
+ if state == nil || state.input == nil {
125
+ return
126
+ }
127
+
128
+ state.input.Stop()
129
+ state.input = nil
130
+ }
131
+
132
+ //export tea_input_read_raw
133
+ func tea_input_read_raw(programID C.ulonglong, buffer *C.char, bufferSize C.int, timeoutMs C.int) C.int {
134
+ state := getProgram(uint64(programID))
135
+
136
+ if state == nil || state.input == nil {
137
+ return -1
138
+ }
139
+
140
+ timeout := time.Duration(timeoutMs) * time.Millisecond
141
+
142
+ select {
143
+ case data := <-state.input.events:
144
+ copyLength := len(data)
145
+
146
+ if copyLength > int(bufferSize) {
147
+ copyLength = int(bufferSize)
148
+ }
149
+
150
+ cBuffer := (*[1 << 20]byte)(unsafe.Pointer(buffer))[:copyLength:copyLength]
151
+ copy(cBuffer, data[:copyLength])
152
+
153
+ return C.int(copyLength)
154
+
155
+ case <-time.After(timeout):
156
+ return 0
157
+ }
158
+ }