Skip to content

Commit 95c80a2

Browse files
authored
Merge pull request #2 from devmil/devterm_additions
Add optional read acknowledge
2 parents 427bd52 + fad6ef2 commit 95c80a2

11 files changed

+176
-9
lines changed

.github/workflows/flutter.yml

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Flutter
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
test:
11+
runs-on: ${{ matrix.os }}
12+
13+
strategy:
14+
matrix:
15+
os: [ubuntu-latest, windows-latest]
16+
sdk: [stable, beta]
17+
18+
steps:
19+
- uses: actions/checkout@v2
20+
- uses: subosito/flutter-action@v2
21+
with:
22+
channdl: ${{ matrix.sdk }}
23+
24+
- name: Print Flutter SDK version
25+
run: flutter --version
26+
27+
- name: Install dependencies
28+
run: flutter pub get
29+
30+
- name: Verify formatting
31+
run: flutter format --output=none --set-exit-if-changed .
32+
33+
- name: Analyze project source
34+
run: flutter analyze
35+
36+
- name: Run tests
37+
run: flutter test integration_test
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import 'dart:convert';
2+
import 'dart:io';
3+
import 'dart:typed_data';
4+
5+
import 'package:flutter_pty/flutter_pty.dart';
6+
import 'package:flutter_test/flutter_test.dart';
7+
8+
String get shell {
9+
if (Platform.isWindows) {
10+
return 'cmd.exe';
11+
}
12+
13+
if (Platform.isLinux || Platform.isMacOS) {
14+
return 'bash';
15+
}
16+
17+
return 'sh';
18+
}
19+
20+
extension StringToUtf8 on String {
21+
Uint8List toUtf8() {
22+
return Uint8List.fromList(
23+
utf8.encode(this),
24+
);
25+
}
26+
}
27+
28+
void main() {
29+
test('Pty works', () async {
30+
final pty = Pty.start(shell);
31+
final input = 'random input'.toUtf8();
32+
pty.write(input);
33+
expect(await pty.output.first, input);
34+
});
35+
}

example/macos/Podfile.lock

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,4 @@ SPEC CHECKSUMS:
2525

2626
PODFILE CHECKSUM: 6eac6b3292e5142cfc23bdeb71848a40ec51c14c
2727

28-
COCOAPODS: 1.11.2
28+
COCOAPODS: 1.11.3

example/pubspec.lock

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ packages:
103103
path: ".."
104104
relative: true
105105
source: path
106-
version: "0.0.7"
106+
version: "0.1.1"
107107
flutter_test:
108108
dependency: "direct dev"
109109
description: flutter

example/pubspec.yaml

+3
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ dev_dependencies:
5050
flutter_test:
5151
sdk: flutter
5252

53+
integration_test:
54+
sdk: flutter
55+
5356
# The "flutter_lints" package below contains a set of recommended lints to
5457
# encourage good coding practices. The lint set provided by the package is
5558
# activated in the `analysis_options.yaml` file located at the root of your

lib/flutter_pty.dart

+10
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,15 @@ class Pty {
4343

4444
/// Spawns a process in a pseudo-terminal. The arguments have the same meaning
4545
/// as in [Process.start].
46+
/// [ackRead] indicates if the pty should wait for a call to [Pty.ackRead] before sending the next data.
4647
Pty.start(
4748
this.executable, {
4849
this.arguments = const [],
4950
String? workingDirectory,
5051
Map<String, String>? environment,
5152
int rows = 25,
5253
int columns = 80,
54+
bool ackRead = false,
5355
}) {
5456
_ensureInitialized();
5557

@@ -104,6 +106,7 @@ class Pty {
104106
options.ref.environment = envp.cast();
105107
options.ref.stdout_port = _stdoutPort.sendPort.nativePort;
106108
options.ref.exit_port = _exitPort.sendPort.nativePort;
109+
options.ref.ackRead = ackRead ? 1 : 0;
107110

108111
if (workingDirectory != null) {
109112
options.ref.working_directory = workingDirectory.toNativeUtf8().cast();
@@ -180,6 +183,13 @@ class Pty {
180183
bool kill([ProcessSignal signal = ProcessSignal.sigterm]) {
181184
return Process.killPid(pid, signal);
182185
}
186+
187+
/// indicates that a data chunk has been processed.
188+
/// This is needed when ackRead is set to true as the pty will wait for this signal to happen
189+
/// before any additional data is sent.
190+
void ackRead() {
191+
_bindings.pty_ack_read(_handle);
192+
}
183193
}
184194

185195
String? _getPtyError() {

lib/flutter_pty_bindings_generated.dart

+17
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,20 @@ class FlutterPtyBindings {
405405
late final _pty_write = _pty_writePtr.asFunction<
406406
void Function(ffi.Pointer<PtyHandle>, ffi.Pointer<ffi.Int8>, int)>();
407407

408+
void pty_ack_read(
409+
ffi.Pointer<PtyHandle> handle,
410+
) {
411+
return _pty_ack_read(
412+
handle,
413+
);
414+
}
415+
416+
late final _pty_ack_readPtr =
417+
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<PtyHandle>)>>(
418+
'pty_ack_read');
419+
late final _pty_ack_read =
420+
_pty_ack_readPtr.asFunction<void Function(ffi.Pointer<PtyHandle>)>();
421+
408422
int pty_resize(
409423
ffi.Pointer<PtyHandle> handle,
410424
int rows,
@@ -734,6 +748,9 @@ class PtyOptions extends ffi.Struct {
734748

735749
@Dart_Port()
736750
external int exit_port;
751+
752+
@ffi.Uint8()
753+
external int ackRead;
737754
}
738755

739756
class PtyHandle extends ffi.Opaque {}

pubspec.yaml

+3-1
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ dependencies:
1414
ffi: ^1.2.0-dev.0
1515

1616
dev_dependencies:
17-
ffigen: ^4.1.2
1817
flutter_test:
1918
sdk: flutter
19+
20+
ffigen: ^4.1.2
21+
2022
flutter_lints: ^1.0.0
2123

2224
# For information on the generic Dart part of this file, see the

src/flutter_pty.h

+4
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ typedef struct PtyOptions
3131

3232
Dart_Port exit_port;
3333

34+
bool ackRead;
35+
3436
} PtyOptions;
3537

3638
typedef struct PtyHandle PtyHandle;
@@ -39,6 +41,8 @@ FFI_PLUGIN_EXPORT PtyHandle *pty_create(PtyOptions *options);
3941

4042
FFI_PLUGIN_EXPORT void pty_write(PtyHandle *handle, char *buffer, int length);
4143

44+
FFI_PLUGIN_EXPORT void pty_ack_read(PtyHandle *handle);
45+
4246
FFI_PLUGIN_EXPORT int pty_resize(PtyHandle *handle, int rows, int cols);
4347

4448
FFI_PLUGIN_EXPORT int pty_getpid(PtyHandle *handle);

src/flutter_pty_unix.c

+31-2
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,22 @@ typedef struct PtyHandle
2121

2222
int pid;
2323

24+
pthread_mutex_t mutex;
25+
26+
bool ackRead;
27+
2428
} PtyHandle;
2529

2630
typedef struct ReadLoopOptions
2731
{
2832
int fd;
2933

34+
pthread_mutex_t* mutex;
35+
3036
Dart_Port port;
3137

38+
bool waitForReadAck;
39+
3240
} ReadLoopOptions;
3341

3442
static void *read_loop(void *arg)
@@ -39,6 +47,12 @@ static void *read_loop(void *arg)
3947

4048
while (1)
4149
{
50+
if(options->waitForReadAck)
51+
{
52+
// if we are in ack mode then we get a mutex here that is
53+
// freed again once the chunk of data has been processed
54+
pthread_mutex_lock(options->mutex);
55+
}
4256
ssize_t n = read(options->fd, buffer, sizeof(buffer));
4357

4458
if (n < 0)
@@ -64,13 +78,17 @@ static void *read_loop(void *arg)
6478
return NULL;
6579
}
6680

67-
static void start_read_thread(int fd, Dart_Port port)
81+
static void start_read_thread(int fd, Dart_Port port, pthread_mutex_t* mutex, bool waitForReadAck)
6882
{
6983
ReadLoopOptions *options = malloc(sizeof(ReadLoopOptions));
7084

7185
options->fd = fd;
7286

7387
options->port = port;
88+
89+
options->mutex = mutex;
90+
91+
options->waitForReadAck = waitForReadAck;
7492

7593
pthread_t _thread;
7694

@@ -167,8 +185,10 @@ FFI_PLUGIN_EXPORT PtyHandle *pty_create(PtyOptions *options)
167185

168186
handle->ptm = ptm;
169187
handle->pid = pid;
188+
pthread_mutex_init( &handle->mutex, NULL );
189+
handle->ackRead = options->ackRead;
170190

171-
start_read_thread(ptm, options->stdout_port);
191+
start_read_thread(ptm, options->stdout_port, &handle->mutex, options->ackRead);
172192

173193
start_wait_exit_thread(pid, options->exit_port);
174194

@@ -180,6 +200,15 @@ FFI_PLUGIN_EXPORT void pty_write(PtyHandle *handle, char *buffer, int length)
180200
write(handle->ptm, buffer, length);
181201
}
182202

203+
FFI_PLUGIN_EXPORT void pty_ack_read(PtyHandle *handle)
204+
{
205+
if(handle->ackRead)
206+
{
207+
// frees the mutex so that the next chunk of data can be read
208+
pthread_mutex_unlock( &handle->mutex );
209+
}
210+
}
211+
183212
FFI_PLUGIN_EXPORT int pty_resize(PtyHandle *handle, int rows, int cols)
184213
{
185214
struct winsize ws;

src/flutter_pty_win.c

+34-4
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,10 @@ typedef struct ReadLoopOptions
156156

157157
Dart_Port port;
158158

159+
HANDLE hMutex;
160+
161+
BOOL ackRead;
162+
159163
} ReadLoopOptions;
160164

161165
static DWORD WINAPI read_loop(LPVOID arg)
@@ -168,6 +172,11 @@ static DWORD WINAPI read_loop(LPVOID arg)
168172
{
169173
DWORD readlen = 0;
170174

175+
if (options->ackRead)
176+
{
177+
WaitForSingleObject(options->hMutex, INFINITE);
178+
}
179+
171180
BOOL ok = ReadFile(options->fd, buffer, sizeof(buffer), &readlen, NULL);
172181

173182
if (!ok)
@@ -192,12 +201,14 @@ static DWORD WINAPI read_loop(LPVOID arg)
192201
return 0;
193202
}
194203

195-
static void start_read_thread(HANDLE fd, Dart_Port port)
204+
static void start_read_thread(HANDLE fd, Dart_Port port, HANDLE mutex, BOOL ackRead)
196205
{
197206
ReadLoopOptions *options = malloc(sizeof(ReadLoopOptions));
198207

199208
options->fd = fd;
200209
options->port = port;
210+
options->hMutex = mutex;
211+
options->ackRead = ackRead;
201212

202213
DWORD thread_id;
203214

@@ -215,6 +226,7 @@ typedef struct WaitExitOptions
215226

216227
Dart_Port port;
217228

229+
HANDLE hMutex;
218230
} WaitExitOptions;
219231

220232
static DWORD WINAPI wait_exit_thread(LPVOID arg)
@@ -228,18 +240,20 @@ static DWORD WINAPI wait_exit_thread(LPVOID arg)
228240
GetExitCodeProcess(options->pid, &exit_code);
229241

230242
CloseHandle(options->pid);
243+
CloseHandle(options->hMutex);
231244

232245
Dart_PostInteger_DL(options->port, exit_code);
233246

234247
return 0;
235248
}
236249

237-
static void start_wait_exit_thread(HANDLE pid, Dart_Port port)
250+
static void start_wait_exit_thread(HANDLE pid, Dart_Port port, HANDLE mutex)
238251
{
239252
WaitExitOptions *options = malloc(sizeof(WaitExitOptions));
240253

241254
options->pid = pid;
242255
options->port = port;
256+
options->hMutex = mutex;
243257

244258
DWORD thread_id;
245259

@@ -261,6 +275,10 @@ typedef struct PtyHandle
261275

262276
HANDLE hProcess;
263277

278+
BOOL ackRead;
279+
280+
HANDLE hMutex;
281+
264282
} PtyHandle;
265283

266284
char *error_message = NULL;
@@ -386,9 +404,11 @@ FFI_PLUGIN_EXPORT PtyHandle *pty_create(PtyOptions *options)
386404

387405
// CloseHandle(processInfo.hThread);
388406

389-
start_read_thread(outputReadSide, options->stdout_port);
407+
HANDLE mutex = CreateMutex(NULL, FALSE, NULL);
390408

391-
start_wait_exit_thread(processInfo.hProcess, options->exit_port);
409+
start_read_thread(outputReadSide, options->stdout_port, mutex, options->ackRead);
410+
411+
start_wait_exit_thread(processInfo.hProcess, options->exit_port, mutex);
392412

393413
PtyHandle *pty = malloc(sizeof(PtyHandle));
394414

@@ -402,6 +422,8 @@ FFI_PLUGIN_EXPORT PtyHandle *pty_create(PtyOptions *options)
402422
pty->outputReadSide = outputReadSide;
403423
pty->hPty = hPty;
404424
pty->hProcess = processInfo.hProcess;
425+
pty->ackRead = options->ackRead;
426+
pty->hMutex = mutex;
405427

406428
return pty;
407429
}
@@ -417,6 +439,14 @@ FFI_PLUGIN_EXPORT void pty_write(PtyHandle *handle, char *buffer, int length)
417439
return;
418440
}
419441

442+
FFI_PLUGIN_EXPORT void pty_ack_read(PtyHandle *handle)
443+
{
444+
if (handle->ackRead)
445+
{
446+
ReleaseMutex(handle->hMutex);
447+
}
448+
}
449+
420450
FFI_PLUGIN_EXPORT int pty_resize(PtyHandle *handle, int rows, int cols)
421451
{
422452
COORD size;

0 commit comments

Comments
 (0)