From 0a7121270bb7c74916f8f496caea4d08fd287d1d Mon Sep 17 00:00:00 2001 From: devmil Date: Sat, 14 May 2022 16:41:49 +0200 Subject: [PATCH 1/7] Current state: ARM works cmatrix -r in high speed is problematic (maybe the ack mechanism is needed again) composing state input doesn't work --- example/pubspec.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/pubspec.lock b/example/pubspec.lock index fb9ac32..56147f9 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -103,7 +103,7 @@ packages: path: ".." relative: true source: path - version: "0.0.7" + version: "0.1.1" flutter_test: dependency: "direct dev" description: flutter From f328fc721a733e4ffe53ca0a349146299d7e8baf Mon Sep 17 00:00:00 2001 From: devmil Date: Sat, 14 May 2022 21:12:06 +0200 Subject: [PATCH 2/7] adds read acknowledge option to pty this is handy to avoid the read thread reading stuff faster than the processing can process it --- lib/flutter_pty.dart | 6 +++++ lib/flutter_pty_bindings_generated.dart | 17 ++++++++++++++ src/flutter_pty.h | 4 ++++ src/flutter_pty_unix.c | 30 +++++++++++++++++++++++-- 4 files changed, 55 insertions(+), 2 deletions(-) diff --git a/lib/flutter_pty.dart b/lib/flutter_pty.dart index 08d3476..958cf10 100644 --- a/lib/flutter_pty.dart +++ b/lib/flutter_pty.dart @@ -50,6 +50,7 @@ class Pty { Map? environment, int rows = 25, int columns = 80, + bool ackRead = false, }) { _ensureInitialized(); @@ -104,6 +105,7 @@ class Pty { options.ref.environment = envp.cast(); options.ref.stdout_port = _stdoutPort.sendPort.nativePort; options.ref.exit_port = _exitPort.sendPort.nativePort; + options.ref.ackRead = ackRead ? 1 : 0; if (workingDirectory != null) { options.ref.working_directory = workingDirectory.toNativeUtf8().cast(); @@ -180,6 +182,10 @@ class Pty { bool kill([ProcessSignal signal = ProcessSignal.sigterm]) { return Process.killPid(pid, signal); } + + void ackRead() { + _bindings.pty_ack_read(_handle); + } } String? _getPtyError() { diff --git a/lib/flutter_pty_bindings_generated.dart b/lib/flutter_pty_bindings_generated.dart index 7042cae..deabd78 100644 --- a/lib/flutter_pty_bindings_generated.dart +++ b/lib/flutter_pty_bindings_generated.dart @@ -405,6 +405,20 @@ class FlutterPtyBindings { late final _pty_write = _pty_writePtr.asFunction< void Function(ffi.Pointer, ffi.Pointer, int)>(); + void pty_ack_read( + ffi.Pointer handle, + ) { + return _pty_ack_read( + handle, + ); + } + + late final _pty_ack_readPtr = + _lookup)>>( + 'pty_ack_read'); + late final _pty_ack_read = + _pty_ack_readPtr.asFunction)>(); + int pty_resize( ffi.Pointer handle, int rows, @@ -734,6 +748,9 @@ class PtyOptions extends ffi.Struct { @Dart_Port() external int exit_port; + + @ffi.Uint8() + external int ackRead; } class PtyHandle extends ffi.Opaque {} diff --git a/src/flutter_pty.h b/src/flutter_pty.h index d00feea..db19049 100644 --- a/src/flutter_pty.h +++ b/src/flutter_pty.h @@ -31,6 +31,8 @@ typedef struct PtyOptions Dart_Port exit_port; + bool ackRead; + } PtyOptions; typedef struct PtyHandle PtyHandle; @@ -39,6 +41,8 @@ FFI_PLUGIN_EXPORT PtyHandle *pty_create(PtyOptions *options); FFI_PLUGIN_EXPORT void pty_write(PtyHandle *handle, char *buffer, int length); +FFI_PLUGIN_EXPORT void pty_ack_read(PtyHandle *handle); + FFI_PLUGIN_EXPORT int pty_resize(PtyHandle *handle, int rows, int cols); FFI_PLUGIN_EXPORT int pty_getpid(PtyHandle *handle); diff --git a/src/flutter_pty_unix.c b/src/flutter_pty_unix.c index 5d2eefb..3ecf038 100644 --- a/src/flutter_pty_unix.c +++ b/src/flutter_pty_unix.c @@ -21,14 +21,22 @@ typedef struct PtyHandle int pid; + pthread_mutex_t mutex; + + bool ackRead; + } PtyHandle; typedef struct ReadLoopOptions { int fd; + pthread_mutex_t* mutex; + Dart_Port port; + bool waitForReadAck; + } ReadLoopOptions; static void *read_loop(void *arg) @@ -39,6 +47,10 @@ static void *read_loop(void *arg) while (1) { + if(options->waitForReadAck) + { + pthread_mutex_lock(options->mutex); + } ssize_t n = read(options->fd, buffer, sizeof(buffer)); if (n < 0) @@ -64,13 +76,17 @@ static void *read_loop(void *arg) return NULL; } -static void start_read_thread(int fd, Dart_Port port) +static void start_read_thread(int fd, Dart_Port port, pthread_mutex_t* mutex, bool waitForReadAck) { ReadLoopOptions *options = malloc(sizeof(ReadLoopOptions)); options->fd = fd; options->port = port; + + options->mutex = mutex; + + options->waitForReadAck = waitForReadAck; pthread_t _thread; @@ -167,8 +183,10 @@ FFI_PLUGIN_EXPORT PtyHandle *pty_create(PtyOptions *options) handle->ptm = ptm; handle->pid = pid; + pthread_mutex_init( &handle->mutex, NULL ); + handle->ackRead = options->ackRead; - start_read_thread(ptm, options->stdout_port); + start_read_thread(ptm, options->stdout_port, &handle->mutex, options->ackRead); start_wait_exit_thread(pid, options->exit_port); @@ -180,6 +198,14 @@ FFI_PLUGIN_EXPORT void pty_write(PtyHandle *handle, char *buffer, int length) write(handle->ptm, buffer, length); } +FFI_PLUGIN_EXPORT void pty_ack_read(PtyHandle *handle) +{ + if(handle->ackRead) + { + pthread_mutex_unlock( &handle->mutex ); + } +} + FFI_PLUGIN_EXPORT int pty_resize(PtyHandle *handle, int rows, int cols) { struct winsize ws; From 0a4ef0425aa1592478b8c2c9ff5e58dfff344306 Mon Sep 17 00:00:00 2001 From: devmil Date: Sat, 14 May 2022 21:20:32 +0200 Subject: [PATCH 3/7] fixes mutex position --- src/flutter_pty_unix.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/flutter_pty_unix.c b/src/flutter_pty_unix.c index 3ecf038..ac974a0 100644 --- a/src/flutter_pty_unix.c +++ b/src/flutter_pty_unix.c @@ -47,10 +47,6 @@ static void *read_loop(void *arg) while (1) { - if(options->waitForReadAck) - { - pthread_mutex_lock(options->mutex); - } ssize_t n = read(options->fd, buffer, sizeof(buffer)); if (n < 0) @@ -71,6 +67,10 @@ static void *read_loop(void *arg) result.value.as_typed_data.values = (uint8_t *)buffer; Dart_PostCObject_DL(options->port, &result); + if(options->waitForReadAck) + { + pthread_mutex_lock(options->mutex); + } } return NULL; From 0e69c7e73c586b4251ba633e18f42986bb9366ce Mon Sep 17 00:00:00 2001 From: devmil Date: Sat, 14 May 2022 21:28:13 +0200 Subject: [PATCH 4/7] adds comments --- lib/flutter_pty.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/flutter_pty.dart b/lib/flutter_pty.dart index 958cf10..100397f 100644 --- a/lib/flutter_pty.dart +++ b/lib/flutter_pty.dart @@ -43,6 +43,7 @@ class Pty { /// Spawns a process in a pseudo-terminal. The arguments have the same meaning /// as in [Process.start]. + /// [ackRead] indicates if the pty should wait for a call to [Pty.ackRead] before sending the next data. Pty.start( this.executable, { this.arguments = const [], @@ -183,6 +184,9 @@ class Pty { return Process.killPid(pid, signal); } + /// indicates that a data chunk has been processed. + /// This is needed when ackRead is set to true as the pty will wait for this signal to happen + /// before any additional data is sent. void ackRead() { _bindings.pty_ack_read(_handle); } From 4694012efcffea0b4aa909f01a2630a829dfc5bf Mon Sep 17 00:00:00 2001 From: devmil Date: Sat, 14 May 2022 21:40:48 +0200 Subject: [PATCH 5/7] moves mutex back again and adds some comments --- src/flutter_pty_unix.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/flutter_pty_unix.c b/src/flutter_pty_unix.c index ac974a0..9384838 100644 --- a/src/flutter_pty_unix.c +++ b/src/flutter_pty_unix.c @@ -47,6 +47,12 @@ static void *read_loop(void *arg) while (1) { + if(options->waitForReadAck) + { + // if we are in ack mode then we get a mutex here that is + // freed again once the chunk of data has been processed + pthread_mutex_lock(options->mutex); + } ssize_t n = read(options->fd, buffer, sizeof(buffer)); if (n < 0) @@ -67,10 +73,6 @@ static void *read_loop(void *arg) result.value.as_typed_data.values = (uint8_t *)buffer; Dart_PostCObject_DL(options->port, &result); - if(options->waitForReadAck) - { - pthread_mutex_lock(options->mutex); - } } return NULL; @@ -202,6 +204,7 @@ FFI_PLUGIN_EXPORT void pty_ack_read(PtyHandle *handle) { if(handle->ackRead) { + // frees the mutex so that the next chunk of data can be read pthread_mutex_unlock( &handle->mutex ); } } From 165596b99d110b0f0d9bce890bcb46d512343f62 Mon Sep 17 00:00:00 2001 From: Michael Lamers Date: Sat, 21 May 2022 20:26:46 +0200 Subject: [PATCH 6/7] adds mutex for the windows implementation of flutter_pty --- src/flutter_pty_win.c | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/src/flutter_pty_win.c b/src/flutter_pty_win.c index 77fb487..204ff7d 100644 --- a/src/flutter_pty_win.c +++ b/src/flutter_pty_win.c @@ -156,6 +156,10 @@ typedef struct ReadLoopOptions Dart_Port port; + HANDLE hMutex; + + BOOL ackRead; + } ReadLoopOptions; static DWORD WINAPI read_loop(LPVOID arg) @@ -168,6 +172,11 @@ static DWORD WINAPI read_loop(LPVOID arg) { DWORD readlen = 0; + if (options->ackRead) + { + WaitForSingleObject(options->hMutex, INFINITE); + } + BOOL ok = ReadFile(options->fd, buffer, sizeof(buffer), &readlen, NULL); if (!ok) @@ -192,12 +201,14 @@ static DWORD WINAPI read_loop(LPVOID arg) return 0; } -static void start_read_thread(HANDLE fd, Dart_Port port) +static void start_read_thread(HANDLE fd, Dart_Port port, HANDLE mutex, BOOL ackRead) { ReadLoopOptions *options = malloc(sizeof(ReadLoopOptions)); options->fd = fd; options->port = port; + options->hMutex = mutex; + options->ackRead = ackRead; DWORD thread_id; @@ -215,6 +226,7 @@ typedef struct WaitExitOptions Dart_Port port; + HANDLE hMutex; } WaitExitOptions; static DWORD WINAPI wait_exit_thread(LPVOID arg) @@ -228,18 +240,20 @@ static DWORD WINAPI wait_exit_thread(LPVOID arg) GetExitCodeProcess(options->pid, &exit_code); CloseHandle(options->pid); + CloseHandle(options->hMutex); Dart_PostInteger_DL(options->port, exit_code); return 0; } -static void start_wait_exit_thread(HANDLE pid, Dart_Port port) +static void start_wait_exit_thread(HANDLE pid, Dart_Port port, HANDLE mutex) { WaitExitOptions *options = malloc(sizeof(WaitExitOptions)); options->pid = pid; options->port = port; + options->hMutex = mutex; DWORD thread_id; @@ -261,6 +275,10 @@ typedef struct PtyHandle HANDLE hProcess; + BOOL ackRead; + + HANDLE hMutex; + } PtyHandle; char *error_message = NULL; @@ -386,9 +404,11 @@ FFI_PLUGIN_EXPORT PtyHandle *pty_create(PtyOptions *options) // CloseHandle(processInfo.hThread); - start_read_thread(outputReadSide, options->stdout_port); + HANDLE mutex = CreateMutex(NULL, FALSE, NULL); - start_wait_exit_thread(processInfo.hProcess, options->exit_port); + start_read_thread(outputReadSide, options->stdout_port, mutex, options->ackRead); + + start_wait_exit_thread(processInfo.hProcess, options->exit_port, mutex); PtyHandle *pty = malloc(sizeof(PtyHandle)); @@ -402,6 +422,8 @@ FFI_PLUGIN_EXPORT PtyHandle *pty_create(PtyOptions *options) pty->outputReadSide = outputReadSide; pty->hPty = hPty; pty->hProcess = processInfo.hProcess; + pty->ackRead = options->ackRead; + pty->hMutex = mutex; return pty; } @@ -417,6 +439,14 @@ FFI_PLUGIN_EXPORT void pty_write(PtyHandle *handle, char *buffer, int length) return; } +FFI_PLUGIN_EXPORT void pty_ack_read(PtyHandle *handle) +{ + if (handle->ackRead) + { + ReleaseMutex(handle->hMutex); + } +} + FFI_PLUGIN_EXPORT int pty_resize(PtyHandle *handle, int rows, int cols) { COORD size; From fad6ef2fcc6846a012f56f9aa82cb7659d3e27f1 Mon Sep 17 00:00:00 2001 From: xuty Date: Tue, 24 May 2022 12:26:38 +0800 Subject: [PATCH 7/7] Add integration_test --- .github/workflows/flutter.yml | 37 +++++++++++++++++++ .../integration_test/flutter_pty_test.dart | 35 ++++++++++++++++++ example/macos/Podfile.lock | 2 +- example/pubspec.yaml | 3 ++ pubspec.yaml | 4 +- 5 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/flutter.yml create mode 100644 example/integration_test/flutter_pty_test.dart diff --git a/.github/workflows/flutter.yml b/.github/workflows/flutter.yml new file mode 100644 index 0000000..293cb22 --- /dev/null +++ b/.github/workflows/flutter.yml @@ -0,0 +1,37 @@ +name: Flutter + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + sdk: [stable, beta] + + steps: + - uses: actions/checkout@v2 + - uses: subosito/flutter-action@v2 + with: + channdl: ${{ matrix.sdk }} + + - name: Print Flutter SDK version + run: flutter --version + + - name: Install dependencies + run: flutter pub get + + - name: Verify formatting + run: flutter format --output=none --set-exit-if-changed . + + - name: Analyze project source + run: flutter analyze + + - name: Run tests + run: flutter test integration_test diff --git a/example/integration_test/flutter_pty_test.dart b/example/integration_test/flutter_pty_test.dart new file mode 100644 index 0000000..b95cdcf --- /dev/null +++ b/example/integration_test/flutter_pty_test.dart @@ -0,0 +1,35 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:flutter_pty/flutter_pty.dart'; +import 'package:flutter_test/flutter_test.dart'; + +String get shell { + if (Platform.isWindows) { + return 'cmd.exe'; + } + + if (Platform.isLinux || Platform.isMacOS) { + return 'bash'; + } + + return 'sh'; +} + +extension StringToUtf8 on String { + Uint8List toUtf8() { + return Uint8List.fromList( + utf8.encode(this), + ); + } +} + +void main() { + test('Pty works', () async { + final pty = Pty.start(shell); + final input = 'random input'.toUtf8(); + pty.write(input); + expect(await pty.output.first, input); + }); +} diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock index 8a1b6bb..8b14d0a 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -25,4 +25,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 6eac6b3292e5142cfc23bdeb71848a40ec51c14c -COCOAPODS: 1.11.2 +COCOAPODS: 1.11.3 diff --git a/example/pubspec.yaml b/example/pubspec.yaml index cfa085e..b867924 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -50,6 +50,9 @@ dev_dependencies: flutter_test: sdk: flutter + integration_test: + sdk: flutter + # The "flutter_lints" package below contains a set of recommended lints to # encourage good coding practices. The lint set provided by the package is # activated in the `analysis_options.yaml` file located at the root of your diff --git a/pubspec.yaml b/pubspec.yaml index 2e52ccf..95f1f09 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,9 +14,11 @@ dependencies: ffi: ^1.2.0-dev.0 dev_dependencies: - ffigen: ^4.1.2 flutter_test: sdk: flutter + + ffigen: ^4.1.2 + flutter_lints: ^1.0.0 # For information on the generic Dart part of this file, see the