0
# Terminal Control
1
2
Low-level terminal management for handling raw mode, TTY size detection, and terminal attribute manipulation. This module provides the foundation for dockerpty's terminal control capabilities.
3
4
## Capabilities
5
6
### Terminal Class
7
8
Wrapper functionality to temporarily make the TTY raw. This is useful when streaming data from a pseudo-terminal into the TTY.
9
10
```python { .api }
11
class Terminal:
12
def __init__(self, fd, raw=True):
13
"""
14
Initialize a terminal for the tty with stdin attached to fd.
15
16
Initializing the Terminal has no immediate side effects. The start()
17
method must be invoked, or 'with Terminal:' used before the terminal is affected.
18
19
Parameters:
20
- fd: file-like object, stdin file descriptor for the terminal
21
- raw: bool, whether to operate in raw mode (default: True)
22
"""
23
24
def __enter__(self):
25
"""
26
Context manager entry - invoked when entering a 'with' block.
27
28
Calls start() automatically.
29
30
Returns:
31
self
32
"""
33
34
def __exit__(self, *_):
35
"""
36
Context manager exit - invoked when exiting a 'with' block.
37
38
Calls stop() automatically.
39
40
Parameters:
41
- *_: Exception information (ignored)
42
43
Returns:
44
None
45
"""
46
47
def israw(self):
48
"""
49
Returns True if the TTY should operate in raw mode.
50
51
Returns:
52
bool - True if terminal should use raw mode
53
"""
54
55
def start(self):
56
"""
57
Saves the current terminal attributes and makes the tty raw.
58
59
This method returns immediately. If the fd is not a TTY or raw=False,
60
no changes are made.
61
62
Returns:
63
None
64
"""
65
66
def stop(self):
67
"""
68
Restores the terminal attributes back to before setting raw mode.
69
70
If the raw terminal was not started, does nothing.
71
72
Returns:
73
None
74
"""
75
```
76
77
Usage example:
78
79
```python
80
import sys
81
from dockerpty.tty import Terminal
82
83
# Use as context manager (recommended)
84
with Terminal(sys.stdin, raw=True):
85
# Terminal is now in raw mode
86
# Keys are passed through without interpretation
87
do_pty_operations()
88
# Terminal attributes automatically restored
89
90
# Or manual management
91
terminal = Terminal(sys.stdin, raw=True)
92
terminal.start()
93
try:
94
do_pty_operations()
95
finally:
96
terminal.stop() # Always restore terminal
97
```
98
99
### TTY Size Detection
100
101
Function to determine the size of a TTY in rows and columns.
102
103
```python { .api }
104
def size(fd):
105
"""
106
Return a tuple (rows,cols) representing the size of the TTY fd.
107
108
The provided file descriptor should be the stdout stream of the TTY.
109
Uses TIOCGWINSZ ioctl to get terminal dimensions, with fallback to
110
environment variables LINES and COLUMNS.
111
112
Parameters:
113
- fd: file-like object, stdout stream of the TTY
114
115
Returns:
116
tuple - (rows, cols) as integers, or None if size cannot be determined
117
"""
118
```
119
120
Usage example:
121
122
```python
123
import sys
124
from dockerpty.tty import size
125
126
# Get current terminal size
127
terminal_size = size(sys.stdout)
128
if terminal_size:
129
rows, cols = terminal_size
130
print(f"Terminal is {cols}x{rows}")
131
else:
132
print("Not running in a terminal")
133
```
134
135
### File Descriptor Control
136
137
Utility functions for managing file descriptor blocking modes and stream selection.
138
139
```python { .api }
140
def set_blocking(fd, blocking=True):
141
"""
142
Set the given file-descriptor blocking or non-blocking.
143
144
Uses fcntl to modify the O_NONBLOCK flag on the file descriptor.
145
146
Parameters:
147
- fd: file descriptor or file-like object with fileno() method
148
- blocking: bool, True for blocking mode, False for non-blocking (default: True)
149
150
Returns:
151
bool - original blocking status (True if was blocking, False if was non-blocking)
152
"""
153
154
def select(read_streams, write_streams, timeout=0):
155
"""
156
Select the streams ready for reading, and streams ready for writing.
157
158
Uses select.select() internally but only returns two lists of ready streams.
159
Handles POSIX signal interrupts (EINTR) gracefully by returning empty lists.
160
161
Parameters:
162
- read_streams: list of file-like objects to check for read readiness
163
- write_streams: list of file-like objects to check for write readiness
164
- timeout: float, timeout in seconds (default: 0 for immediate return)
165
166
Returns:
167
tuple - (ready_read_streams, ready_write_streams)
168
169
Raises:
170
select.error - for non-EINTR select errors
171
"""
172
```
173
174
Usage examples:
175
176
```python
177
import sys
178
import socket
179
from dockerpty.io import set_blocking, select
180
181
# Make stdin non-blocking
182
original_blocking = set_blocking(sys.stdin, False)
183
184
# Use select to wait for input
185
ready_read, ready_write = select([sys.stdin], [], timeout=5.0)
186
if ready_read:
187
data = sys.stdin.read()
188
189
# Restore original blocking mode
190
set_blocking(sys.stdin, original_blocking)
191
```
192
193
## Raw Mode Terminal Operation
194
195
### What Raw Mode Does
196
197
Raw mode disables terminal input processing, meaning:
198
199
- **No line buffering**: Characters are available immediately, not after Enter
200
- **No signal generation**: Ctrl+C, Ctrl+Z don't generate signals
201
- **No character interpretation**: No backspace processing, no echo
202
- **Direct key forwarding**: All keystrokes passed through to the application
203
204
This is essential for dockerpty because it needs to forward all user input directly to the container's PTY without the host terminal interpreting special keys.
205
206
### Terminal Attribute Management
207
208
The Terminal class manages terminal attributes using the `termios` module:
209
210
1. **Save current attributes**: `termios.tcgetattr()` saves original terminal state
211
2. **Set raw mode**: `tty.setraw()` configures raw mode attributes
212
3. **Restore on exit**: `termios.tcsetattr()` restores original attributes
213
214
This ensures the user's terminal is always restored to its original state, even if dockerpty exits unexpectedly.
215
216
## Window Size Handling
217
218
### Size Detection Methods
219
220
The `size()` function uses multiple methods to determine terminal size:
221
222
1. **TIOCGWINSZ ioctl**: Primary method using system call to get window size
223
2. **Environment variables**: Fallback to `LINES` and `COLUMNS` environment variables
224
3. **Return None**: If neither method works (not a terminal)
225
226
### Automatic Resize Handling
227
228
dockerpty uses SIGWINCH signal handling to automatically resize the container's PTY when the terminal window changes size:
229
230
1. **Signal installation**: WINCHHandler installs SIGWINCH handler
231
2. **Size detection**: Handler calls `tty.size()` to get new dimensions
232
3. **PTY resize**: Calls Docker API to resize container's PTY to match
233
4. **Signal restoration**: Original SIGWINCH handler restored on exit
234
235
## Error Handling
236
237
### Terminal Detection
238
239
Functions safely handle non-terminal file descriptors:
240
- `os.isatty()` checks if fd is a terminal before terminal operations
241
- Operations are skipped gracefully if not running in a terminal
242
- No errors are raised for non-terminal usage
243
244
### Signal Interruption
245
246
The `select()` function handles POSIX signal interruption:
247
- Catches `errno.EINTR` from interrupted system calls
248
- Returns empty lists to allow graceful continuation
249
- Re-raises other select errors that indicate real problems
250
251
### Attribute Restoration
252
253
Terminal attribute restoration is robust:
254
- Attributes are saved before any modifications
255
- Restoration happens in exception handlers and context manager exits
256
- Multiple restoration calls are safe (idempotent)
257
- Original handler restoration prevents signal handler leaks