├── .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 |
5 |
6 |
7 |
8 |
9 | Pty for Dart and Flutter. Provides the ability to create processes with pseudo terminal file descriptors.
10 |
11 | ## Status
12 |
13 | [](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 |
--------------------------------------------------------------------------------