├── .github └── workflows │ └── dart.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── example ├── pty_example.dart ├── test.bat └── test.sh ├── lib ├── pty.dart └── src │ ├── impl │ ├── unix.dart │ └── windows.dart │ ├── pty.dart │ ├── pty_core.dart │ ├── pty_error.dart │ └── util │ ├── unix_const.dart │ ├── unix_ffi.dart │ └── word_size.dart ├── pubspec.yaml ├── test └── pty_test.dart └── utils ├── benchmark.dart ├── bufsize.py ├── dump_const.c └── unistd.dart.backup /.github/workflows/dart.yml: -------------------------------------------------------------------------------- 1 | name: Dart 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | test: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | os: [ubuntu-latest, macos-latest, windows-latest] 15 | # sdk: [stable, beta, dev, 2.10.3, 2.12.0-29.10.beta] 16 | sdk: [stable, dev] 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - uses: dart-lang/setup-dart@v1 21 | with: 22 | sdk: ${{ matrix.sdk }} 23 | 24 | - name: Print Dart SDK version 25 | run: dart --version 26 | 27 | - name: Install dependencies 28 | run: dart pub get 29 | 30 | # Uncomment this step to verify the use of 'dart format' on each commit. 31 | # - name: Verify formatting 32 | # run: dart format --output=none --set-exit-if-changed . 33 | 34 | # Consider passing '--fatal-infos' for slightly stricter analysis. 35 | - name: Analyze project source 36 | run: dart analyze 37 | 38 | # Your project will need to have tests in test/ and a dependency on 39 | # package:test for this step to succeed. Note that Flutter projects will 40 | # want to change this to 'flutter test'. 41 | - name: Run tests 42 | run: dart test 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub 2 | .dart_tool/ 3 | .packages 4 | 5 | # Omit commiting pubspec.lock for library packages: 6 | # https://dart.dev/guides/libraries/private-files#pubspeclock 7 | pubspec.lock 8 | 9 | # Conventional directory for build outputs 10 | build/ 11 | 12 | # Directory created by dartdoc 13 | doc/api/ 14 | .idea 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.2.2-pre 2 | 3 | - Upgrade win32 package to 2.1.x 4 | - Fix Windows stdio handle leak issue. 5 | 6 | ## 0.2.1-pre 7 | 8 | - Bugfix: path error macos zsh [#4](https://github.com/TerminalStudio/pty/pull/4), thanks [@devmil](https://github.com/devmil) 9 | 10 | ## 0.2.0-pre 11 | 12 | - Using forkpty to set up the pty connection [#2](https://github.com/TerminalStudio/pty/pull/2), thanks [@devmil](https://github.com/devmil) 13 | 14 | ## 0.1.1 15 | 16 | - Fix pid reference 17 | 18 | ## 0.1.0 19 | 20 | - Migrate to nnbd 21 | 22 | ## 0.0.1 23 | 24 | - Initial version 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 xuty 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pty 2 | 3 |

4 | GitHub repo size 5 | GitHub issues 6 | GitHub pull requests 7 |

8 | 9 | Pty for Dart and Flutter. Provides the ability to create processes with pseudo terminal file descriptors. 10 | 11 | ## Status 12 | 13 | [![test](https://github.com/TerminalStudio/pty/actions/workflows/dart.yml/badge.svg)](https://github.com/TerminalStudio/pty/actions/workflows/dart.yml) 14 | 15 | 16 | | **Platform** | **JIT(Debug)** | **AOT(Release)** | 17 | | ------------- | :------------: | :--------------: | 18 | | **Windows** | Crash | Works | 19 | | **Linux x64** | Works | Works | 20 | | **Linux x86** | Not tested | Not tested | 21 | | **macOS** | Works | Works | 22 | 23 | ## Usage 24 | 25 | A simple usage example: 26 | 27 | ```dart 28 | import 'package:pty/pty.dart'; 29 | 30 | void main() async { 31 | final pty = PseudoTerminal.start('bash', []); 32 | 33 | pty.write('ls\n'); 34 | 35 | pty.out.listen((data) { 36 | print(data); 37 | }); 38 | 39 | print(await pty.exitCode); 40 | } 41 | ``` 42 | 43 | ## Features and bugs 44 | 45 | Please file feature requests and bugs at the [issue tracker][tracker]. 46 | 47 | [tracker]: https://github.com/TerminalStudio/pty/issues 48 | 49 | ## License 50 | 51 | This project is licensed under an MIT license. -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # Defines a default set of lint rules enforced for 2 | # projects at Google. For details and rationale, 3 | # see https://github.com/dart-lang/pedantic#enabled-lints. 4 | include: package:pedantic/analysis_options.yaml 5 | 6 | # For lint rules and documentation, see http://dart-lang.github.io/linter/lints. 7 | # Uncomment to specify additional rules. 8 | # linter: 9 | # rules: 10 | # - camel_case_types 11 | 12 | analyzer: 13 | # exclude: 14 | # - path/to/excluded/files/** 15 | -------------------------------------------------------------------------------- /example/pty_example.dart: -------------------------------------------------------------------------------- 1 | import 'package:pty/pty.dart'; 2 | 3 | void main() async { 4 | final pty = PseudoTerminal.start('bash', []); 5 | 6 | pty.write('ls\n'); 7 | 8 | pty.out.listen((data) { 9 | print(data); 10 | }); 11 | 12 | print(await pty.exitCode); 13 | } 14 | -------------------------------------------------------------------------------- /example/test.bat: -------------------------------------------------------------------------------- 1 | echo hahaha 2 | 3 | tree /f >list.txt -------------------------------------------------------------------------------- /example/test.sh: -------------------------------------------------------------------------------- 1 | bash -c 'echo {1..100000}' -------------------------------------------------------------------------------- /lib/pty.dart: -------------------------------------------------------------------------------- 1 | library pty; 2 | 3 | import 'dart:io'; 4 | 5 | import 'package:pty/src/impl/unix.dart'; 6 | import 'package:pty/src/impl/windows.dart'; 7 | import 'package:pty/src/pty.dart'; 8 | import 'package:pty/src/pty_core.dart'; 9 | 10 | export 'src/pty.dart'; 11 | 12 | abstract class PseudoTerminal { 13 | /// If [blocking] is [true], the PseudoTerminal starts in blocking mode 14 | /// (better suited for flutter release mode), otherwise in polling mode 15 | /// (better suited for flutter debug mode). 16 | static PseudoTerminal start( 17 | String executable, 18 | List arguments, { 19 | String? workingDirectory, 20 | Map? environment, 21 | bool blocking = false, 22 | bool ackProcessed = false, 23 | // bool includeParentEnvironment = true, 24 | // bool runInShell = false, 25 | // ProcessStartMode mode = ProcessStartMode.normal, 26 | }) { 27 | late PtyCore core; 28 | 29 | if (Platform.isWindows) { 30 | core = PtyCoreWindows.start( 31 | executable, 32 | arguments, 33 | workingDirectory: workingDirectory, 34 | environment: environment, 35 | blocking: blocking, 36 | ); 37 | } else { 38 | //add '-l' as argument for the shell to perform a login 39 | arguments = List.generate(arguments.length + 1, 40 | (index) => index == 0 ? '-l' : arguments[index - 1]); 41 | 42 | core = PtyCoreUnix.start( 43 | executable, 44 | arguments, 45 | workingDirectory: workingDirectory, 46 | environment: environment, 47 | blocking: blocking, 48 | ); 49 | } 50 | 51 | if (blocking) { 52 | return BlockingPseudoTerminal(core, ackProcessed); 53 | } else { 54 | return PollingPseudoTerminal(core); 55 | } 56 | } 57 | 58 | void init(); 59 | bool kill([ProcessSignal signal = ProcessSignal.sigterm]); 60 | 61 | Future get exitCode; 62 | 63 | // int get pid { 64 | // return _core.pid; 65 | // } 66 | 67 | void write(String input); 68 | 69 | Stream get out; 70 | 71 | void ackProcessed(); 72 | 73 | void resize(int width, int height); 74 | } 75 | -------------------------------------------------------------------------------- /lib/src/impl/unix.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ffi'; 2 | import 'dart:io'; 3 | import 'dart:typed_data'; 4 | 5 | import 'package:ffi/ffi.dart'; 6 | import 'package:pty/src/pty_core.dart'; 7 | import 'package:pty/src/pty_error.dart'; 8 | import 'package:pty/src/util/unix_const.dart'; 9 | import 'package:pty/src/util/unix_ffi.dart'; 10 | 11 | void _setNonblock(int fd) { 12 | var flag = unix.fcntl(fd, consts.F_GETFL); 13 | 14 | flag |= consts.O_NONBLOCK; 15 | 16 | final ret = unix.fcntl3(fd, consts.F_SETFL, flag); 17 | if (ret == -1) { 18 | unix.perror(nullptr); 19 | // throw PtyError('fcntl3 failed.'); 20 | } 21 | } 22 | 23 | class PtyCoreUnix implements PtyCore { 24 | factory PtyCoreUnix.start( 25 | String executable, 26 | List arguments, { 27 | String? workingDirectory, 28 | Map? environment, 29 | bool blocking = false, 30 | }) { 31 | var effectiveEnv = {}; 32 | 33 | effectiveEnv['TERM'] = 'xterm-256color'; 34 | // Without this, tools like "vi" produce sequences that are not UTF-8 friendly 35 | effectiveEnv['LANG'] = 'en_US.UTF-8'; 36 | 37 | var envValuesToCopy = [ 38 | 'LOGNAME', 39 | 'USER', 40 | 'DISPLAY', 41 | 'LC_TYPE', 42 | 'HOME', 43 | 'PATH' 44 | ]; 45 | 46 | for (var entry in Platform.environment.entries) { 47 | if (envValuesToCopy.contains(entry.key)) { 48 | effectiveEnv[entry.key] = entry.value; 49 | } 50 | } 51 | 52 | if (environment != null) { 53 | for (var entry in environment.entries) { 54 | effectiveEnv[entry.key] = entry.value; 55 | } 56 | } 57 | 58 | final pPtm = calloc(); 59 | pPtm.value = -1; 60 | 61 | final sz = calloc(); 62 | sz.ref.ws_col = 80; 63 | sz.ref.ws_row = 20; 64 | 65 | final pid = unix.forkpty(pPtm, nullptr, nullptr, sz); 66 | calloc.free(sz); 67 | 68 | var ptm = pPtm.value; 69 | calloc.free(pPtm); 70 | 71 | if (pid < 0) { 72 | throw PtyException('fork failed.'); 73 | } else if (pid == 0) { 74 | // set working directory 75 | if (workingDirectory != null) { 76 | unix.chdir(workingDirectory.toNativeUtf8()); 77 | } 78 | 79 | // build argv 80 | final argv = calloc>(arguments.length + 2); 81 | argv.elementAt(0).value = executable.toNativeUtf8(); 82 | argv.elementAt(arguments.length + 1).value = nullptr; 83 | for (var i = 0; i < arguments.length; i++) { 84 | argv.elementAt(i + 1).value = arguments[i].toNativeUtf8(); 85 | } 86 | 87 | //build env 88 | final env = calloc>(effectiveEnv.length + 1); 89 | env.elementAt(effectiveEnv.length).value = nullptr; 90 | var cnt = 0; 91 | for (var entry in effectiveEnv.entries) { 92 | final envVal = '${entry.key}=${entry.value}'; 93 | env.elementAt(cnt).value = envVal.toNativeUtf8(); 94 | cnt++; 95 | } 96 | 97 | unix.execve(executable.toNativeUtf8(), argv, env); 98 | } else { 99 | unix.setsid(); 100 | 101 | if (!blocking) { 102 | _setNonblock(ptm); 103 | } 104 | 105 | return PtyCoreUnix._(pid, ptm); 106 | } 107 | 108 | throw PtyException('unreachable'); 109 | } 110 | 111 | PtyCoreUnix._(this._pid, this._ptm) { 112 | // final devname = unix.ptsname(_ptm); 113 | // _pts = unix.open(devname, consts.O_RDWR); 114 | } 115 | 116 | final int _pid; 117 | final int _ptm; 118 | // late final int _pts; 119 | 120 | static const _bufferSize = 81920; 121 | final _buffer = calloc(_bufferSize + 1).address; 122 | 123 | @override 124 | Uint8List? read() { 125 | final buffer = Pointer.fromAddress(_buffer); 126 | final readlen = unix.read(_ptm, buffer.cast(), _bufferSize); 127 | 128 | if (readlen <= 0) { 129 | return null; 130 | } 131 | 132 | return buffer.cast().asTypedList(readlen); 133 | } 134 | 135 | @override 136 | int? exitCodeNonBlocking() { 137 | final statusPointer = calloc(); 138 | final pid = unix.waitpid(_pid, statusPointer, consts.WNOHANG); 139 | 140 | final status = statusPointer.value; 141 | calloc.free(statusPointer); 142 | 143 | if (pid == 0) { 144 | return null; 145 | } 146 | 147 | return status; 148 | } 149 | 150 | @override 151 | int exitCodeBlocking() { 152 | final statusPointer = calloc(); 153 | unix.waitpid(_pid, statusPointer, 0); 154 | 155 | final status = statusPointer.value; 156 | calloc.free(statusPointer); 157 | 158 | return status; 159 | } 160 | 161 | @override 162 | bool kill([ProcessSignal signal = ProcessSignal.sigterm]) { 163 | return unix.kill(_pid, consts.SIGKILL) == 0; 164 | } 165 | 166 | @override 167 | void resize(int width, int height) { 168 | final sz = calloc(); 169 | sz.ref.ws_col = width; 170 | sz.ref.ws_row = height; 171 | 172 | final ret = unix.ioctl(_ptm, consts.TIOCSWINSZ, sz.cast()); 173 | calloc.free(sz); 174 | 175 | if (ret == -1) { 176 | print(_ptm); 177 | // print(unix.errno.value); 178 | unix.perror(nullptr); 179 | } 180 | } 181 | 182 | // @override 183 | // int get pid { 184 | // return _pid; 185 | // } 186 | 187 | @override 188 | void write(List data) { 189 | final buf = calloc(data.length); 190 | buf.asTypedList(data.length).setAll(0, data); 191 | unix.write(_ptm, buf.cast(), data.length); 192 | calloc.free(buf); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /lib/src/impl/windows.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ffi'; 2 | import 'dart:io'; 3 | import 'dart:typed_data'; 4 | 5 | import 'package:ffi/ffi.dart'; 6 | import 'package:pty/src/pty_core.dart'; 7 | import 'package:pty/src/pty_error.dart'; 8 | // import 'package:pty/src/util/win32_additional.dart'; 9 | import 'package:win32/win32.dart' as win32; 10 | 11 | class _NamedPipe { 12 | _NamedPipe({bool nowait = false}) { 13 | final pipeName = r'\\.\pipe\dart-pty-pipe'; 14 | final pPipeName = pipeName.toNativeUtf16(); 15 | 16 | final waitMode = nowait ? win32.PIPE_NOWAIT : win32.PIPE_WAIT; 17 | 18 | final namedPipe = win32.CreateNamedPipe( 19 | pPipeName, 20 | win32.PIPE_ACCESS_DUPLEX, 21 | waitMode | win32.PIPE_READMODE_MESSAGE | win32.PIPE_TYPE_MESSAGE, 22 | win32.PIPE_UNLIMITED_INSTANCES, 23 | 4096, 24 | 4096, 25 | 0, 26 | nullptr, 27 | ); 28 | 29 | if (namedPipe == win32.INVALID_HANDLE_VALUE) { 30 | throw PtyException('CreateNamedPipe failed: ${win32.GetLastError()}'); 31 | } 32 | 33 | final namedPipeClient = win32.CreateFile( 34 | pPipeName, 35 | win32.GENERIC_READ | win32.GENERIC_WRITE, 36 | 0, // no sharing 37 | nullptr, // default security attributes 38 | win32.OPEN_EXISTING, // opens existing pipe , 39 | 0, // default attributes 40 | 0, // no template file 41 | ); 42 | calloc.free(pPipeName); 43 | 44 | if (namedPipeClient == win32.INVALID_HANDLE_VALUE) { 45 | throw PtyException('CreateFile on named pipe failed'); 46 | } 47 | 48 | readSide = namedPipe; 49 | writeSide = namedPipeClient; 50 | } 51 | 52 | late final int readSide; 53 | late final int writeSide; 54 | } 55 | 56 | class PtyCoreWindows implements PtyCore { 57 | factory PtyCoreWindows.start( 58 | String executable, 59 | List arguments, { 60 | String? workingDirectory, 61 | Map? environment, 62 | bool blocking = false, 63 | }) { 64 | // create input pipe 65 | final hReadPipe = calloc(); 66 | final hWritePipe = calloc(); 67 | final pipe2 = win32.CreatePipe(hReadPipe, hWritePipe, nullptr, 512); 68 | if (pipe2 == win32.INVALID_HANDLE_VALUE) { 69 | throw PtyException('CreatePipe failed: ${win32.GetLastError()}'); 70 | } 71 | final inputWriteSide = hWritePipe.value; 72 | final inputReadSide = hReadPipe.value; 73 | 74 | // create output pipe 75 | final pipe1 = _NamedPipe(nowait: !blocking); 76 | final outputReadSide = pipe1.readSide; 77 | final outputWriteSide = pipe1.writeSide; 78 | 79 | // final pipe2 = _NamedPipe(nowait: false); 80 | // final inputWriteSide = pipe2.writeSide; 81 | // final inputReadSide = pipe2.readSide; 82 | 83 | // create pty 84 | final _hPty = calloc(); 85 | final size = calloc().ref; 86 | size.X = 80; 87 | size.Y = 25; 88 | final hr = win32.CreatePseudoConsole( 89 | size, 90 | inputReadSide, 91 | outputWriteSide, 92 | 0, 93 | _hPty, 94 | ); 95 | 96 | if (win32.FAILED(hr)) { 97 | throw PtyException('CreatePseudoConsole failed.'); 98 | } 99 | 100 | // Setup startup info 101 | final si = calloc(); 102 | si.ref.StartupInfo.cb = sizeOf(); 103 | 104 | // Explicitly set stdio of the child process to NULL. This is required for 105 | // ConPTY to work properly. 106 | si.ref.StartupInfo.hStdInput = nullptr.address; 107 | si.ref.StartupInfo.hStdOutput = nullptr.address; 108 | si.ref.StartupInfo.hStdError = nullptr.address; 109 | si.ref.StartupInfo.dwFlags = win32.STARTF_USESTDHANDLES; 110 | 111 | final bytesRequired = calloc(); 112 | win32.InitializeProcThreadAttributeList(nullptr, 1, 0, bytesRequired); 113 | si.ref.lpAttributeList = calloc(bytesRequired.value); 114 | 115 | var ret = win32.InitializeProcThreadAttributeList( 116 | si.ref.lpAttributeList, 1, 0, bytesRequired); 117 | 118 | if (ret == win32.FALSE) { 119 | throw PtyException('InitializeProcThreadAttributeList failed.'); 120 | } 121 | 122 | // use pty 123 | ret = win32.UpdateProcThreadAttribute( 124 | si.ref.lpAttributeList, 125 | 0, 126 | win32.PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, 127 | Pointer.fromAddress(_hPty.value), 128 | sizeOf(), 129 | nullptr, 130 | nullptr, 131 | ); 132 | 133 | if (ret == win32.FALSE) { 134 | throw PtyException('UpdateProcThreadAttribute failed.'); 135 | } 136 | 137 | // build command line 138 | final commandBuffer = StringBuffer(); 139 | commandBuffer.write(executable); 140 | if (arguments.isNotEmpty) { 141 | for (var argument in arguments) { 142 | commandBuffer.write(' '); 143 | commandBuffer.write(argument); 144 | } 145 | } 146 | final pCommandLine = commandBuffer.toString().toNativeUtf16(); 147 | 148 | // build current directory 149 | Pointer pCurrentDirectory = nullptr; 150 | if (workingDirectory != null) { 151 | pCurrentDirectory = workingDirectory.toNativeUtf16(); 152 | } 153 | 154 | // build environment 155 | Pointer pEnvironment = nullptr; 156 | if (environment != null && environment.isNotEmpty) { 157 | final buffer = StringBuffer(); 158 | 159 | for (var env in environment.entries) { 160 | buffer.write(env.key); 161 | buffer.write('='); 162 | buffer.write(env.value); 163 | buffer.write('\u0000'); 164 | } 165 | 166 | pEnvironment = buffer.toString().toNativeUtf16(); 167 | } 168 | 169 | // start the process. 170 | final pi = calloc(); 171 | ret = win32.CreateProcess( 172 | nullptr, 173 | pCommandLine, 174 | nullptr, 175 | nullptr, 176 | win32.FALSE, 177 | win32.EXTENDED_STARTUPINFO_PRESENT | win32.CREATE_UNICODE_ENVIRONMENT, 178 | // pass pEnvironment here causes crash 179 | // TODO: fix this 180 | // pEnvironment, 181 | nullptr, 182 | pCurrentDirectory, 183 | si.cast(), 184 | pi, 185 | ); 186 | 187 | calloc.free(pCommandLine); 188 | 189 | if (pCurrentDirectory != nullptr) { 190 | calloc.free(pCurrentDirectory); 191 | } 192 | 193 | if (pEnvironment != nullptr) { 194 | calloc.free(pEnvironment); 195 | } 196 | 197 | if (ret == 0) { 198 | throw PtyException('CreateProcess failed: ${win32.GetLastError()}'); 199 | } 200 | 201 | return PtyCoreWindows._( 202 | inputWriteSide, 203 | outputReadSide, 204 | _hPty.value, 205 | pi.ref.hProcess, 206 | ); 207 | } 208 | 209 | PtyCoreWindows._( 210 | this._inputWriteSide, 211 | this._outputReadSide, 212 | this._hPty, 213 | this._hProcess, 214 | ); 215 | 216 | final int _inputWriteSide; 217 | final int _outputReadSide; 218 | final int _hPty; 219 | final int _hProcess; 220 | 221 | static const _bufferSize = 4096; 222 | final _buffer = calloc(_bufferSize + 1).address; 223 | 224 | @override 225 | Uint8List? read() { 226 | final pReadlen = calloc(); 227 | final buffer = Pointer.fromAddress(_buffer); 228 | final ret = win32.ReadFile( 229 | _outputReadSide, 230 | buffer, 231 | _bufferSize, 232 | pReadlen, 233 | nullptr, 234 | ); 235 | 236 | final readlen = pReadlen.value; 237 | calloc.free(pReadlen); 238 | 239 | if (ret == 0) { 240 | return null; 241 | } 242 | 243 | if (readlen <= 0) { 244 | return null; 245 | } else { 246 | return buffer.cast().asTypedList(readlen); 247 | } 248 | } 249 | 250 | @override 251 | int? exitCodeNonBlocking() { 252 | final exitCodePtr = calloc(); 253 | final ret = win32.GetExitCodeProcess(_hProcess, exitCodePtr); 254 | 255 | final exitCode = exitCodePtr.value; 256 | calloc.free(exitCodePtr); 257 | 258 | const STILL_ACTIVE = 259; 259 | if (ret == 0 || exitCode == STILL_ACTIVE) { 260 | return null; 261 | } 262 | 263 | return exitCode; 264 | } 265 | 266 | @override 267 | int exitCodeBlocking() { 268 | const n = 1; 269 | final pid = calloc(n); 270 | final infinite = 0xFFFFFFFF; 271 | pid.elementAt(0).value = _hProcess; 272 | win32.MsgWaitForMultipleObjects(n, pid, 1, infinite, win32.QS_ALLEVENTS); 273 | return pid.elementAt(0).value; 274 | } 275 | 276 | @override 277 | bool kill([ProcessSignal signal = ProcessSignal.sigterm]) { 278 | final ret = win32.TerminateProcess(_hProcess, nullptr.address); 279 | win32.ClosePseudoConsole(_hPty); 280 | return ret != 0; 281 | } 282 | 283 | @override 284 | void resize(int width, int height) { 285 | final size = calloc(); 286 | size.ref.X = width; 287 | size.ref.Y = height; 288 | final hr = win32.ResizePseudoConsole(_hPty, size.ref); 289 | if (win32.FAILED(hr)) { 290 | throw PtyException('ResizePseudoConsole failed.'); 291 | } 292 | calloc.free(size); 293 | } 294 | 295 | // @override 296 | // int get pid { 297 | // return _hProcess; 298 | // } 299 | 300 | @override 301 | void write(List data) { 302 | final buffer = calloc(data.length); 303 | buffer.asTypedList(data.length).setAll(0, data); 304 | final written = calloc(); 305 | win32.WriteFile(_inputWriteSide, buffer, data.length, written, nullptr); 306 | calloc.free(buffer); 307 | calloc.free(written); 308 | } 309 | } 310 | 311 | // void rawWait(int hProcess) { 312 | // // final status = allocate(); 313 | // // unistd.waitpid(pid, status, 0); 314 | // final count = 1; 315 | // final pids = calloc(count); 316 | // final infinite = 0xFFFFFFFF; 317 | // pids.elementAt(0).value = hProcess; 318 | // win32.MsgWaitForMultipleObjects(count, pids, 1, infinite, win32.QS_ALLEVENTS); 319 | // } 320 | -------------------------------------------------------------------------------- /lib/src/pty.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | import 'dart:io'; 4 | import 'dart:isolate'; 5 | 6 | import 'package:pty/pty.dart'; 7 | import 'package:pty/src/pty_core.dart'; 8 | 9 | abstract class BasePseudoTerminal implements PseudoTerminal { 10 | BasePseudoTerminal(this._core); 11 | 12 | late final PtyCore _core; 13 | 14 | @override 15 | bool kill([ProcessSignal signal = ProcessSignal.sigterm]) { 16 | return _core.kill(signal); 17 | } 18 | 19 | // int get pid { 20 | // return _core.pid; 21 | // } 22 | 23 | @override 24 | void write(String input) { 25 | final data = utf8.encode(input); 26 | _core.write(data); 27 | } 28 | 29 | @override 30 | void resize(int width, int height) { 31 | _core.resize(width, height); 32 | } 33 | } 34 | 35 | /// A polling based PseudoTerminal implementation. Mainly used in flutter debug 36 | /// mode to make hot reload work. The underlying PtyCore must be non-blocking. 37 | class PollingPseudoTerminal extends BasePseudoTerminal { 38 | PollingPseudoTerminal(PtyCore _core) : super(_core); 39 | 40 | //initialize them late to avoid having any closures in the instance 41 | //so that this PollingPseudoTerminal can be passed to an Isolate 42 | late Completer _exitCode; 43 | late StreamController _out; 44 | bool _initialized = false; 45 | 46 | @override 47 | void init() { 48 | _exitCode = Completer(); 49 | _out = StreamController(); 50 | _initialized = true; 51 | Timer.run(() { 52 | _poll(); 53 | }); 54 | } 55 | 56 | List _createDelayMicrosecondsStepList( 57 | Map delayMicrosecondsToAmountMap) { 58 | final result = List.empty(growable: true); 59 | final sortedKeys = delayMicrosecondsToAmountMap.keys.toList(growable: false) 60 | ..sort(); 61 | for (final key in sortedKeys) { 62 | result.addAll(List.filled(delayMicrosecondsToAmountMap[key]!, key)); 63 | } 64 | return result; 65 | } 66 | 67 | void _poll() async { 68 | final delayMicrosecondsSteps = _createDelayMicrosecondsStepList({ 69 | 200: 50, 70 | 500: 50, 71 | 1000: 50, 72 | 2000: 50, 73 | 3000: 40, 74 | 4000: 30, 75 | 5000: 20, 76 | 10000: 10, 77 | 20000: 5, 78 | 50000: 2, 79 | 70000: 1, 80 | 100000: 1, 81 | }); 82 | 83 | var delayStepIndex = 0; 84 | 85 | var exitCodeCheckNeeded = true; 86 | 87 | final rawDataBuffer = List.empty(growable: true); 88 | 89 | while (true) { 90 | if (exitCodeCheckNeeded) { 91 | final exit = _core.exitCodeNonBlocking(); 92 | if (exit != null) { 93 | _exitCode.complete(exit); 94 | await _out.close(); 95 | return; 96 | } 97 | exitCodeCheckNeeded = false; 98 | Timer(Duration(milliseconds: 500), () { 99 | exitCodeCheckNeeded = true; 100 | }); 101 | } 102 | 103 | var receivedSomething = false; 104 | 105 | var data = _core.read(); 106 | while (data != null) { 107 | receivedSomething = true; 108 | rawDataBuffer.addAll(data); 109 | data = _core.read(); 110 | } 111 | if (!receivedSomething) { 112 | // when we did not receive anything then we increase the delay time 113 | if (delayStepIndex < delayMicrosecondsSteps.length - 1) { 114 | delayStepIndex++; 115 | } 116 | } else { 117 | //when we received something we jump to the lowest delay time 118 | delayStepIndex = 0; 119 | } 120 | 121 | if (_initialized) { 122 | if (rawDataBuffer.isNotEmpty) { 123 | try { 124 | final strContent = utf8.decode(rawDataBuffer); 125 | rawDataBuffer.clear(); 126 | _out.add(strContent); 127 | } on FormatException catch (_) { 128 | // FormatException is thrown when the data contains incomplete 129 | // UTF-8 byte sequences. 130 | // int this case we do nothing and wait for the next chunk of data 131 | // to arrive 132 | } 133 | } 134 | } 135 | await Future.delayed( 136 | Duration(microseconds: delayMicrosecondsSteps[delayStepIndex])); 137 | } 138 | } 139 | 140 | @override 141 | Future get exitCode { 142 | return _exitCode.future; 143 | } 144 | 145 | @override 146 | Stream get out { 147 | return _out.stream; 148 | } 149 | 150 | @override 151 | void ackProcessed() { 152 | // NOOP 153 | } 154 | } 155 | 156 | /// An isolate based PseudoTerminal implementation. Performs better than 157 | /// PollingPseudoTerminal and requires less resource. However this prevents 158 | /// flutter hot reload from working. Ideal for release builds. The underlying 159 | /// PtyCore must be blocking. 160 | class BlockingPseudoTerminal extends BasePseudoTerminal { 161 | BlockingPseudoTerminal(PtyCore _core, this._syncProcessed) : super(_core); 162 | 163 | late SendPort _sendPort; 164 | final bool _syncProcessed; 165 | late final StreamController _outStreamController; 166 | 167 | @override 168 | void init() { 169 | _outStreamController = StreamController(); 170 | out = _outStreamController.stream; 171 | 172 | final receivePort = ReceivePort(); 173 | var first = true; 174 | receivePort.listen((msg) { 175 | if (first) { 176 | _sendPort = msg; 177 | } else { 178 | _outStreamController.sink.add(msg); 179 | } 180 | first = false; 181 | }); 182 | Isolate.spawn(_readUntilExit, 183 | _IsolateArgs(receivePort.sendPort, _core, _syncProcessed)); 184 | } 185 | 186 | @override 187 | Future get exitCode async { 188 | final receivePort = ReceivePort(); 189 | // ignore: unawaited_futures 190 | Isolate.spawn(_waitForExitCode, 191 | _IsolateArgs(receivePort.sendPort, _core, _syncProcessed)); 192 | return (await receivePort.first) as int; 193 | } 194 | 195 | @override 196 | late Stream out; 197 | 198 | @override 199 | void ackProcessed() { 200 | if (_syncProcessed) { 201 | _sendPort.send(true); 202 | } 203 | } 204 | } 205 | 206 | /// Argument to a isolate entry point, with a sendPort and a custom value. 207 | /// Reduces the effort to establish bi-directional communication between isolate 208 | /// and main thread in many cases. 209 | class _IsolateArgs { 210 | _IsolateArgs(this.sendPort, this.arg, this.syncProcessed); 211 | 212 | final SendPort sendPort; 213 | final T arg; 214 | final bool syncProcessed; 215 | } 216 | 217 | void _waitForExitCode(_IsolateArgs ctx) async { 218 | final exitCode = ctx.arg.exitCodeBlocking(); 219 | ctx.sendPort.send(exitCode); 220 | } 221 | 222 | void _readUntilExit(_IsolateArgs ctx) async { 223 | final rp = ReceivePort(); 224 | ctx.sendPort.send(rp.sendPort); 225 | 226 | // set [sync] to true because PtyCore.read() is blocking and prevents the 227 | // event loop from working. 228 | final input = StreamController>(sync: true); 229 | 230 | input.stream.transform(utf8.decoder).listen(ctx.sendPort.send); 231 | 232 | final loopController = StreamController(); 233 | 234 | if (ctx.syncProcessed) { 235 | rp.listen((message) { 236 | loopController.sink.add(message); 237 | }); 238 | } 239 | loopController.sink.add(true); //enable the first iteration 240 | 241 | await for (final _ in loopController.stream) { 242 | final data = ctx.arg.read(); 243 | 244 | if (data == null) { 245 | await input.close(); 246 | break; 247 | } 248 | 249 | input.sink.add(data); 250 | 251 | // when we don't sync with the data processing then just schedule the next loop 252 | // iteration 253 | // Otherwise the loop will continue when the processing of the data is 254 | // finished (signaled via [PseudoTerminal.ackProcessed]) 255 | if (!ctx.syncProcessed) { 256 | loopController.sink.add(true); 257 | } 258 | } 259 | await loopController.close(); 260 | } 261 | -------------------------------------------------------------------------------- /lib/src/pty_core.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'dart:typed_data'; 4 | 5 | abstract class PtyCore { 6 | Uint8List? read(); 7 | 8 | int? exitCodeNonBlocking(); 9 | 10 | int exitCodeBlocking(); 11 | 12 | bool kill([ProcessSignal signal = ProcessSignal.sigterm]); 13 | 14 | void resize(int width, int height); 15 | 16 | // int get pid; 17 | 18 | void write(List data); 19 | } 20 | -------------------------------------------------------------------------------- /lib/src/pty_error.dart: -------------------------------------------------------------------------------- 1 | class PtyException implements Exception { 2 | PtyException(this.message); 3 | 4 | final String message; 5 | 6 | @override 7 | String toString() { 8 | return message; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/src/util/unix_const.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: annotate_overrides 2 | 3 | import 'dart:io'; 4 | 5 | final consts = Platform.isAndroid 6 | ? AndroidConst() 7 | : Platform.isLinux 8 | ? LinuxConst() 9 | : BsdConst(); 10 | 11 | class AndroidConst extends BsdConst { 12 | @override 13 | final TIOCSWINSZ = 0x5414; 14 | @override 15 | final O_NONBLOCK = 4000; 16 | } 17 | 18 | class LinuxConst implements BsdConst { 19 | final IUTF8 = 16384; 20 | 21 | final IXON = 1024; 22 | final IXOFF = 4096; 23 | 24 | final TCSANOW = 0; 25 | 26 | final WNOHANG = 1; 27 | 28 | final SIGINT = 2; 29 | final SIGILL = 4; 30 | final SIGABRT = 6; 31 | final SIGFPE = 8; 32 | final SIGSEGV = 11; 33 | final SIGTERM = 15; 34 | final SIGHUP = 1; 35 | final SIGQUIT = 3; 36 | final SIGTRAP = 5; 37 | final SIGKILL = 9; 38 | final SIGBUS = 7; 39 | final SIGSYS = 31; 40 | final SIGPIPE = 13; 41 | final SIGALRM = 14; 42 | final SIGURG = 23; 43 | final SIGSTOP = 19; 44 | final SIGTSTP = 20; 45 | final SIGCONT = 18; 46 | final SIGCHLD = 17; 47 | final SIGTTIN = 21; 48 | final SIGTTOU = 22; 49 | final SIGPOLL = 29; 50 | final SIGXCPU = 24; 51 | final SIGXFSZ = 25; 52 | final SIGVTALRM = 26; 53 | final SIGPROF = 27; 54 | final SIGUSR1 = 10; 55 | final SIGUSR2 = 12; 56 | final SIGWINCH = 28; 57 | final SIGIO = 29; 58 | final SIGIOT = 6; 59 | 60 | final F_DUPFD = 0; 61 | final F_GETFD = 1; 62 | final F_SETFD = 2; 63 | final F_GETFL = 3; 64 | final F_SETFL = 4; 65 | 66 | final O_ACCMODE = 3; 67 | final O_RDONLY = 0; 68 | final O_WRONLY = 1; 69 | final O_RDWR = 2; 70 | final O_CREAT = 64; 71 | final O_EXCL = 128; 72 | final O_NOCTTY = 256; 73 | final O_TRUNC = 512; 74 | final O_APPEND = 1024; 75 | final O_NONBLOCK = 2048; 76 | final O_NDELAY = 2048; 77 | final O_SYNC = 1052672; 78 | final O_FSYNC = 1052672; 79 | final O_ASYNC = 8192; 80 | 81 | final O_DIRECTORY = 65536; 82 | final O_NOFOLLOW = 131072; 83 | final O_CLOEXEC = 524288; 84 | final O_DSYNC = 4096; 85 | 86 | final TIOCSWINSZ = 21524; 87 | } 88 | 89 | class BsdConst { 90 | final IUTF8 = 16384; 91 | 92 | final IXON = 512; 93 | final IXOFF = 1024; 94 | 95 | final TCSANOW = 0; 96 | 97 | final WNOHANG = 1; 98 | 99 | final SIGINT = 2; 100 | final SIGILL = 4; 101 | final SIGABRT = 6; 102 | final SIGFPE = 8; 103 | final SIGSEGV = 11; 104 | final SIGTERM = 15; 105 | final SIGHUP = 1; 106 | final SIGQUIT = 3; 107 | final SIGTRAP = 5; 108 | final SIGKILL = 9; 109 | final SIGBUS = 10; 110 | final SIGSYS = 12; 111 | final SIGPIPE = 13; 112 | final SIGALRM = 14; 113 | final SIGURG = 16; 114 | final SIGSTOP = 17; 115 | final SIGTSTP = 18; 116 | final SIGCONT = 19; 117 | final SIGCHLD = 20; 118 | final SIGTTIN = 21; 119 | final SIGTTOU = 22; 120 | final SIGXCPU = 24; 121 | final SIGXFSZ = 25; 122 | final SIGVTALRM = 26; 123 | final SIGPROF = 27; 124 | final SIGUSR1 = 30; 125 | final SIGUSR2 = 31; 126 | final SIGWINCH = 28; 127 | final SIGIO = 23; 128 | final SIGIOT = 6; 129 | 130 | final F_DUPFD = 0; 131 | final F_GETFD = 1; 132 | final F_SETFD = 2; 133 | final F_GETFL = 3; 134 | final F_SETFL = 4; 135 | 136 | final O_ACCMODE = 3; 137 | final O_RDONLY = 0; 138 | final O_WRONLY = 1; 139 | final O_RDWR = 2; 140 | final O_CREAT = 512; 141 | final O_EXCL = 2048; 142 | final O_NOCTTY = 131072; 143 | final O_TRUNC = 1024; 144 | final O_APPEND = 8; 145 | final O_NONBLOCK = 4; 146 | final O_NDELAY = 4; 147 | final O_SYNC = 128; 148 | final O_FSYNC = 128; 149 | final O_ASYNC = 64; 150 | final O_DIRECTORY = 1048576; 151 | final O_NOFOLLOW = 256; 152 | final O_CLOEXEC = 16777216; 153 | final O_DSYNC = 4194304; 154 | 155 | final TIOCSWINSZ = 2148037735; 156 | } 157 | -------------------------------------------------------------------------------- /lib/src/util/unix_ffi.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ffi'; 2 | import 'dart:io'; 3 | 4 | import 'package:ffi/ffi.dart'; 5 | 6 | Unix? _unix; 7 | Unix get unix { 8 | _unix ??= Unix(DynamicLibrary.process()); 9 | return _unix!; 10 | } 11 | 12 | typedef _c_open = Int32 Function(Pointer __file, Int32 __oflag); 13 | typedef _dart_open = int Function(Pointer __file, int __oflag); 14 | 15 | typedef _c_grantpt = Int64 Function(Int64 __fd); 16 | typedef _dart_grantpt = int Function(int __fd); 17 | 18 | typedef _c_unlockpt = Int64 Function(Int64 __fd); 19 | typedef _dart_unlockpt = int Function(int __fd); 20 | 21 | typedef _c_tcgetattr = Int32 Function(Int32 __fd, Pointer __termios_p); 22 | typedef _dart_tcgetattr = int Function(int __fd, Pointer __termios_p); 23 | 24 | typedef _c_tcsetattr = Int32 Function( 25 | Int32 __fd, 26 | Int32 __optional_actions, 27 | Pointer __termios_p, 28 | ); 29 | typedef _dart_tcsetattr = int Function( 30 | int __fd, 31 | int __optional_actions, 32 | Pointer __termios_p, 33 | ); 34 | 35 | typedef _c_fork = Int32 Function(); 36 | typedef _dart_fork = int Function(); 37 | 38 | typedef _c_setsid = Int32 Function(); 39 | typedef _dart_setsid = int Function(); 40 | 41 | typedef _c_ptsname = Pointer Function(Int64 __sysno); 42 | typedef _dart_ptsname = Pointer Function(int __sysno); 43 | 44 | typedef _c_dup2 = Int32 Function(Int32 __fd, Int32 __fd2); 45 | typedef _dart_dup2 = int Function(int __fd, int __fd2); 46 | 47 | typedef _c_execvp = Int32 Function( 48 | Pointer __file, 49 | Pointer> __argv, 50 | ); 51 | typedef _dart_execvp = int Function( 52 | Pointer __file, 53 | Pointer> __argv, 54 | ); 55 | 56 | typedef _c_forkpty = Int32 Function( 57 | Pointer masterRef, 58 | Pointer name, 59 | Pointer termp, 60 | Pointer winp, 61 | ); 62 | typedef _dart_forkpty = int Function( 63 | Pointer masterRef, 64 | Pointer name, 65 | Pointer termp, 66 | Pointer winp, 67 | ); 68 | 69 | typedef _c_execve = Int32 Function( 70 | Pointer __file, 71 | Pointer> __argv, 72 | Pointer> __envp, 73 | ); 74 | typedef _dart_execve = int Function( 75 | Pointer __file, 76 | Pointer> __argv, 77 | Pointer> __envp, 78 | ); 79 | 80 | typedef _c_read = Int64 Function(Int32 _fd, Pointer _buf, Int32 _nbytes); 81 | typedef _dart_read = int Function(int _fd, Pointer _buf, int _nbytes); 82 | 83 | typedef _c_waitpid = Int32 Function( 84 | Int32 __pid, 85 | Pointer __stat_loc, 86 | Int32 __options, 87 | ); 88 | typedef _dart_waitpid = int Function( 89 | int __pid, 90 | Pointer __stat_loc, 91 | int __options, 92 | ); 93 | 94 | typedef _c_kill = Int32 Function(Int32 pid, Int32 sig); 95 | typedef _dart_kill = int Function(int pid, int sig); 96 | 97 | typedef _c_write = Int64 Function(Int32 fd, Pointer buf, Int32 n); 98 | typedef _dart_write = int Function(int fd, Pointer buf, int n); 99 | 100 | typedef _c_ioctl = Int32 Function(Int32 fd, Uint64 request, Pointer sz); 101 | typedef _dart_ioctl = int Function(int fd, int request, Pointer sz); 102 | 103 | typedef _c_fcntl = Int32 Function(Int32 fd, Int32 cmd); 104 | typedef _dart_fcntl = int Function(int fd, int cmd); 105 | 106 | typedef _c_fcntl3 = Int32 Function(Int32 fd, Int32 cmd, Int32 flags); 107 | typedef _dart_fcntl3 = int Function(int fd, int cmd, int flags); 108 | 109 | typedef _c_perror = Void Function(Pointer s); 110 | typedef _dart_perror = void Function(Pointer s); 111 | 112 | typedef _c_close = Int32 Function(Int32 fd); 113 | typedef _dart_close = int Function(int fd); 114 | 115 | typedef _c_putenv = Int32 Function(Pointer string); 116 | typedef _dart_putenv = int Function(Pointer string); 117 | 118 | typedef _c_setenv = Int32 Function( 119 | Pointer name, 120 | Pointer value, 121 | Int32 replace, 122 | ); 123 | typedef _dart_setenv = int Function( 124 | Pointer name, 125 | Pointer value, 126 | int replace, 127 | ); 128 | 129 | typedef _c_chdir = Int32 Function(Pointer __path); 130 | typedef _dart_chdir = int Function(Pointer __path); 131 | 132 | class Unix { 133 | Unix(DynamicLibrary lib) { 134 | final utilsLib = 135 | Platform.isLinux ? DynamicLibrary.open('libutil.so') : null; 136 | 137 | if (!Platform.isAndroid) { 138 | // this line will crash on android 139 | errno = lib.lookup('errno'); 140 | } else {} 141 | 142 | open = lib.lookupFunction<_c_open, _dart_open>('open'); 143 | grantpt = lib.lookupFunction<_c_grantpt, _dart_grantpt>('grantpt'); 144 | unlockpt = lib.lookupFunction<_c_unlockpt, _dart_unlockpt>('unlockpt'); 145 | tcgetattr = lib.lookupFunction<_c_tcgetattr, _dart_tcgetattr>('tcgetattr'); 146 | tcsetattr = lib.lookupFunction<_c_tcsetattr, _dart_tcsetattr>('tcsetattr'); 147 | fork = lib.lookupFunction<_c_fork, _dart_fork>('fork'); 148 | setsid = lib.lookupFunction<_c_setsid, _dart_setsid>('setsid'); 149 | ptsname = lib.lookupFunction<_c_ptsname, _dart_ptsname>('ptsname'); 150 | dup2 = lib.lookupFunction<_c_dup2, _dart_dup2>('dup2'); 151 | execvp = lib.lookupFunction<_c_execvp, _dart_execvp>('execvp'); 152 | execve = lib.lookupFunction<_c_execve, _dart_execve>('execve'); 153 | read = lib.lookupFunction<_c_read, _dart_read>('read'); 154 | waitpid = lib.lookupFunction<_c_waitpid, _dart_waitpid>('waitpid'); 155 | kill = lib.lookupFunction<_c_kill, _dart_kill>('kill'); 156 | write = lib.lookupFunction<_c_write, _dart_write>('write'); 157 | ioctl = lib.lookupFunction<_c_ioctl, _dart_ioctl>('ioctl'); 158 | fcntl = lib.lookupFunction<_c_fcntl, _dart_fcntl>('fcntl'); 159 | fcntl3 = lib.lookupFunction<_c_fcntl3, _dart_fcntl3>('fcntl'); 160 | perror = lib.lookupFunction<_c_perror, _dart_perror>('perror'); 161 | close = lib.lookupFunction<_c_close, _dart_close>('close'); 162 | putenv = lib.lookupFunction<_c_putenv, _dart_putenv>('putenv'); 163 | setenv = lib.lookupFunction<_c_setenv, _dart_setenv>('setenv'); 164 | chdir = lib.lookupFunction<_c_chdir, _dart_chdir>('chdir'); 165 | if (utilsLib != null) { 166 | forkpty = utilsLib.lookupFunction<_c_forkpty, _dart_forkpty>('forkpty'); 167 | } else { 168 | forkpty = lib.lookupFunction<_c_forkpty, _dart_forkpty>('forkpty'); 169 | } 170 | } 171 | 172 | Pointer? errno; 173 | 174 | late final _dart_open open; 175 | late final _dart_grantpt grantpt; 176 | late final _dart_unlockpt unlockpt; 177 | late final _dart_tcgetattr tcgetattr; 178 | late final _dart_tcsetattr tcsetattr; 179 | late final _dart_fork fork; 180 | late final _dart_setsid setsid; 181 | late final _dart_ptsname ptsname; 182 | late final _dart_dup2 dup2; 183 | late final _dart_execvp execvp; 184 | late final _dart_execve execve; 185 | late final _dart_forkpty forkpty; 186 | late final _dart_read read; 187 | late final _dart_waitpid waitpid; 188 | late final _dart_kill kill; 189 | late final _dart_write write; 190 | late final _dart_ioctl ioctl; 191 | late final _dart_fcntl fcntl; 192 | late final _dart_fcntl3 fcntl3; 193 | late final _dart_perror perror; 194 | late final _dart_close close; 195 | late final _dart_putenv putenv; 196 | late final _dart_setenv setenv; 197 | late final _dart_chdir chdir; 198 | } 199 | 200 | class termios extends Struct { 201 | @Uint64() 202 | external int c_iflag; /* input mode flags */ 203 | @Uint64() 204 | external int c_oflag; /* output mode flags */ 205 | @Uint64() 206 | external int c_cflag; /* control mode flags */ 207 | @Uint64() 208 | external int c_lflag; /* local mode flags */ 209 | 210 | @Uint8() 211 | external int c_line; /* line discipline */ 212 | 213 | @Int8() 214 | external int c_cc_0; 215 | @Int8() 216 | external int c_cc_1; 217 | @Int8() 218 | external int c_cc_2; 219 | @Int8() 220 | external int c_cc_3; 221 | @Int8() 222 | external int c_cc_4; 223 | @Int8() 224 | external int c_cc_5; 225 | @Int8() 226 | external int c_cc_6; 227 | @Int8() 228 | external int c_cc_7; 229 | @Int8() 230 | external int c_cc_8; 231 | @Int8() 232 | external int c_cc_9; 233 | @Int8() 234 | external int c_cc_10; 235 | @Int8() 236 | external int c_cc_11; 237 | @Int8() 238 | external int c_cc_12; 239 | @Int8() 240 | external int c_cc_13; 241 | @Int8() 242 | external int c_cc_14; 243 | @Int8() 244 | external int c_cc_15; 245 | @Int8() 246 | external int c_cc_16; 247 | @Int8() 248 | external int c_cc_17; 249 | @Int8() 250 | external int c_cc_18; 251 | @Int8() 252 | external int c_cc_19; 253 | @Int8() 254 | external int c_cc_20; 255 | @Int8() 256 | external int c_cc_21; 257 | @Int8() 258 | external int c_cc_22; 259 | @Int8() 260 | external int c_cc_23; 261 | @Int8() 262 | external int c_cc_24; 263 | @Int8() 264 | external int c_cc_25; 265 | @Int8() 266 | external int c_cc_26; 267 | @Int8() 268 | external int c_cc_27; 269 | @Int8() 270 | external int c_cc_28; 271 | @Int8() 272 | external int c_cc_29; 273 | @Int8() 274 | external int c_cc_30; 275 | @Int8() 276 | external int c_cc_31; 277 | 278 | @Uint64() 279 | external int c_ispeed; /* input speed */ 280 | @Uint64() 281 | external int c_ospeed; /* output speed */ 282 | } 283 | 284 | class winsize extends Struct { 285 | @Uint16() 286 | external int ws_row; 287 | 288 | @Uint16() 289 | external int ws_col; 290 | 291 | @Uint16() 292 | external int ws_xpixel; 293 | 294 | @Uint16() 295 | external int ws_ypixel; 296 | } 297 | -------------------------------------------------------------------------------- /lib/src/util/word_size.dart: -------------------------------------------------------------------------------- 1 | int wordSize() { 2 | var t = 1; 3 | var i = 0; 4 | 5 | for (i = 0; i < 65; i++) { 6 | t <<= 1; 7 | 8 | if (t == 0) { 9 | return i + 1; 10 | } 11 | } 12 | 13 | return 64; 14 | } 15 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: pty 2 | description: Pty for Dart and Flutter. Provides the ability to create processes with pseudo terminal file descriptors. 3 | version: 0.2.2-pre 4 | homepage: https://github.com/TerminalStudio/pty 5 | 6 | environment: 7 | sdk: '>=2.12.0 <3.0.0' 8 | 9 | dependencies: 10 | win32: ^2.1.0 11 | ffi: ^1.0.0 12 | 13 | dev_dependencies: 14 | pedantic: ^1.10.0 15 | test: ^1.16.4 16 | -------------------------------------------------------------------------------- /test/pty_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:pty/pty.dart'; 4 | import 'package:test/test.dart'; 5 | 6 | void main() { 7 | test('Can instantiate and kill PseudoTerminal', () async { 8 | final pty = PseudoTerminal.start(_getShell(), []); 9 | pty.kill(); 10 | await pty.exitCode; 11 | }, timeout: Timeout.factor(0.3)); 12 | 13 | // on windows PseudoTerminal only works in Flutter release mode.. 14 | 15 | // test('Can read exit code', () async { 16 | // final pty = PseudoTerminal.start(_getShell(), []); 17 | // pty.write('exit 3\n'); 18 | // expect(await pty.exitCode, equals(3)); 19 | // }, timeout: Timeout.factor(0.3)); 20 | 21 | // test('echo test', () async { 22 | // final pty = PseudoTerminal.start(_getShell(), []); 23 | // pty.write('echo hello world\n'); 24 | 25 | // final output = await pty.out.single.timeout(Duration(seconds: 10)); 26 | // expect(output, equals('hello world')); 27 | 28 | // pty.kill(); 29 | // await pty.exitCode.timeout(Duration(seconds: 10)); 30 | // }, timeout: Timeout.factor(0.3)); 31 | } 32 | 33 | String _getShell() { 34 | if (Platform.isWindows) { 35 | return 'cmd'; 36 | } 37 | 38 | return 'sh'; 39 | } 40 | -------------------------------------------------------------------------------- /utils/benchmark.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ffi'; 2 | import 'dart:typed_data'; 3 | 4 | import 'package:ffi/ffi.dart'; 5 | 6 | void main() async { 7 | BenchmarkMalloc().run(); 8 | } 9 | 10 | abstract class Benchmark { 11 | String explain(); 12 | 13 | void benchmark(); 14 | 15 | void run() { 16 | print('benchmark: ${explain()}'); 17 | print('preheating...'); 18 | benchmark(); 19 | final sw = Stopwatch()..start(); 20 | print('running...'); 21 | benchmark(); 22 | sw.stop(); 23 | print('result: ${sw.elapsedMilliseconds} ms'); 24 | } 25 | } 26 | 27 | class BenchmarkMalloc extends Benchmark { 28 | static const cycle = 1; 29 | 30 | static const cellSize = 14; 31 | static const cellPerLine = 120; 32 | static const lineSize = cellSize * cellPerLine; 33 | 34 | static const linePerBuffer = 10000; 35 | 36 | static final randomData = Uint8List.fromList(List.filled(lineSize, 123)); 37 | 38 | @override 39 | String explain() { 40 | return 'allocate buffer for $cycle times'; 41 | } 42 | 43 | @override 44 | void benchmark() { 45 | for (var i = 0; i < cycle; i++) { 46 | for (var i = 0; i < linePerBuffer; i++) { 47 | final b = malloc.allocate(lineSize).cast().asTypedList(lineSize); 48 | b.setAll(0, randomData); 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /utils/bufsize.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Pty buffer size detect script 4 | # From: https://superuser.com/a/1452858 5 | 6 | # Results: 7 | # MacOS 11.2.3: pts write blocked after 1023 bytes (0 KiB) 8 | 9 | import os 10 | from pty import openpty 11 | from fcntl import fcntl, F_GETFL, F_SETFL 12 | from itertools import count 13 | 14 | def set_nonblock(fd): 15 | flags = fcntl(fd, F_GETFL) 16 | flags |= os.O_NONBLOCK 17 | fcntl(fd, F_SETFL, flags) 18 | 19 | master, slave = openpty() 20 | 21 | set_nonblock(slave) 22 | 23 | for i in count(): 24 | try: 25 | os.write(slave, b'a') 26 | except BlockingIOError: 27 | i -= 1 28 | break 29 | 30 | print("pts write blocked after {} bytes ({} KiB)".format(i, i//1024)) -------------------------------------------------------------------------------- /utils/dump_const.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define DUMP(name) printf("final %s = %d;\n", "" #name "", name); 13 | #define DUMPL(name) printf("final %s = %ld;\n", "" #name "", name); 14 | #define BR() printf("\n"); 15 | 16 | int main() { 17 | DUMP(IUTF8) 18 | 19 | BR() 20 | 21 | DUMP(IXON) 22 | DUMP(IXOFF) 23 | 24 | BR() 25 | 26 | DUMP(TCSANOW) 27 | 28 | BR() 29 | 30 | DUMP(WNOHANG) 31 | 32 | BR() 33 | 34 | DUMP(SIGINT) 35 | DUMP(SIGILL) 36 | DUMP(SIGABRT) 37 | DUMP(SIGFPE) 38 | DUMP(SIGSEGV) 39 | DUMP(SIGTERM) 40 | DUMP(SIGHUP) 41 | DUMP(SIGQUIT) 42 | DUMP(SIGTRAP) 43 | DUMP(SIGKILL) 44 | DUMP(SIGBUS) 45 | DUMP(SIGSYS) 46 | DUMP(SIGPIPE) 47 | DUMP(SIGALRM) 48 | DUMP(SIGURG) 49 | DUMP(SIGSTOP) 50 | DUMP(SIGTSTP) 51 | DUMP(SIGCONT) 52 | DUMP(SIGCHLD) 53 | DUMP(SIGTTIN) 54 | DUMP(SIGTTOU) 55 | // DUMP(SIGPOLL) 56 | DUMP(SIGXCPU) 57 | DUMP(SIGXFSZ) 58 | DUMP(SIGVTALRM) 59 | DUMP(SIGPROF) 60 | DUMP(SIGUSR1) 61 | DUMP(SIGUSR2) 62 | DUMP(SIGWINCH) 63 | DUMP(SIGIO) 64 | DUMP(SIGIOT) 65 | // DUMP(SIGCLD) 66 | 67 | BR() 68 | 69 | DUMP(F_DUPFD) 70 | DUMP(F_GETFD) 71 | DUMP(F_SETFD) 72 | DUMP(F_GETFL) 73 | DUMP(F_SETFL) 74 | 75 | BR() 76 | 77 | DUMP(O_ACCMODE) 78 | DUMP(O_RDONLY) 79 | DUMP(O_WRONLY) 80 | DUMP(O_RDWR) 81 | DUMP(O_CREAT) 82 | DUMP(O_EXCL) 83 | DUMP(O_NOCTTY) 84 | DUMP(O_TRUNC) 85 | DUMP(O_APPEND) 86 | DUMP(O_NONBLOCK) 87 | DUMP(O_NDELAY) 88 | DUMP(O_SYNC) 89 | DUMP(O_FSYNC) 90 | DUMP(O_ASYNC) 91 | DUMP(O_DIRECTORY) 92 | DUMP(O_NOFOLLOW) 93 | DUMP(O_CLOEXEC) 94 | DUMP(O_DSYNC) 95 | // DUMP(O_RSYNC) 96 | 97 | BR() 98 | 99 | DUMPL(TIOCSWINSZ) 100 | 101 | return 0; 102 | } 103 | --------------------------------------------------------------------------------