cs-tty 20260912__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,288 @@
1
+ Metadata-Version: 2.4
2
+ Name: cs-tty
3
+ Version: 20260912
4
+ Summary: Functions related to terminals.
5
+ Keywords: python2,python3
6
+ Author-email: Cameron Simpson <cs@cskk.id.au>
7
+ Description-Content-Type: text/markdown
8
+ Classifier: Environment :: Console
9
+ Classifier: Operating System :: POSIX
10
+ Classifier: Programming Language :: Python
11
+ Classifier: Programming Language :: Python :: 2
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Terminals
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
17
+ Requires-Dist: cs.gimmicks>=20260311
18
+ Project-URL: MonoRepo Commits, https://bitbucket.org/cameron_simpson/css/commits/branch/main
19
+ Project-URL: Monorepo Git Mirror, https://github.com/cameron-simpson/css
20
+ Project-URL: Monorepo Hg/Mercurial Mirror, https://hg.sr.ht/~cameron-simpson/css
21
+ Project-URL: Source, https://github.com/cameron-simpson/css/blob/main/lib/python/cs/tty.py
22
+
23
+ Functions related to terminals.
24
+
25
+ *Latest release 20260912*:
26
+ New ttysizepx(fd) function returning the tty size in characters and pixels.
27
+
28
+
29
+
30
+ Short summary:
31
+
32
+
33
+ * `modify_termios`: Apply mode changes to a tty. Return the previous tty modes as from `termios.tcgetattr` or `None` if the changes could not be applied. If `strict`, raise an exception instead of returning `None`.
34
+
35
+
36
+ * `setupterm`: Run curses.setupterm, needed to be able to use the status line. Uses a global flag to avoid doing this twice.
37
+
38
+
39
+ * `stack_termios`: Context manager to apply and restore changes to a tty. Yield the previous tty modes as from `termios.tcgetattr` or `None` if the changes could not be applied. If `strict`, raise an exception instead of yielding `None`.
40
+
41
+
42
+ * `status`: Write a message to the terminal's status line.
43
+
44
+
45
+ * `statusline`: Update the status line.
46
+
47
+
48
+ * `statusline_bs`: Return a byte string to update the status line.
49
+
50
+
51
+ * `ttysize`: Return a (rows, columns) tuple for the specified file descriptor.
52
+
53
+
54
+ * `ttysizepx`: Return a `(rows,columns,widthpx,heightpx)` tuple for the specified file descriptor being the terminal character rows and columns and pixel width and height respectively.
55
+
56
+
57
+ * `WinSize`: WinSize(rows, columns).
58
+
59
+
60
+ * `WinSizePX`: WinSizePX(rows, columns, widthpx, heightpx).
61
+
62
+ # Functions
63
+
64
+ ## modify_termios(fd=0, set_modes=None, clear_modes=None, strict=False)
65
+
66
+ Apply mode changes to a tty.
67
+ Return the previous tty modes as from `termios.tcgetattr`
68
+ or `None` if the changes could not be applied.
69
+ If `strict`, raise an exception instead of returning `None`.
70
+
71
+ Parameters:
72
+ * `fd`: optional tty file descriptor, default `0`.
73
+ * `set_modes`: an optional mapping of attribute name to new value
74
+ for values to set
75
+ * `clear_modes`: an optional mapping of attribute name to new value
76
+ for values to clear
77
+ * `strict`: optional flag, default `False`;
78
+ if true, raise exceptions from failed `tcgetattr` and `tcsetattr` calls
79
+ otherwise issue a warning if the errno is not `ENOTTY` and proceed.
80
+ This aims to provide ease of use in batch mode by default
81
+ while providing a mode to fail overtly if required.
82
+
83
+ The attribute names are from
84
+ `iflag`, `oflag`, `cflag`, `lflag`, `ispeed`, `ospeed`, `cc`,
85
+ corresponding to the list entries defined by the `termios.tcgetattr`
86
+ call.
87
+
88
+ For `set_modes`, the attributes `ispeed`, `ospeed` and `cc`
89
+ are applied directly;
90
+ the other attributes are binary ORed into the existing modes.
91
+
92
+ For `clear_modes`, the attributes `ispeed`, `ospeed` and `cc`
93
+ cannot be cleared;
94
+ the other attributes are binary removed from the existing modes.
95
+
96
+ For example, to turn off the terminal echo during some operation:
97
+
98
+ old_modes = apply_termios(clear_modes={'lflag': termios.ECHO}):
99
+ ... do something with tty echo disabled ...
100
+ if old_modes:
101
+ termios.tcsetattr(fd, termios.TCSANOW, old_modes)
102
+
103
+ ## setupterm(*args)
104
+
105
+ Run curses.setupterm, needed to be able to use the status line.
106
+ Uses a global flag to avoid doing this twice.
107
+
108
+ ## stack_termios(fd=0, set_modes=None, clear_modes=None, strict=False)
109
+
110
+ Context manager to apply and restore changes to a tty.
111
+ Yield the previous tty modes as from `termios.tcgetattr`
112
+ or `None` if the changes could not be applied.
113
+ If `strict`, raise an exception instead of yielding `None`.
114
+
115
+ Parameters:
116
+ * `fd`: optional tty file descriptor, default `0`.
117
+ * `set_modes`: an optional mapping of attribute name to new value
118
+ for values to set
119
+ * `clear_modes`: an optional mapping of attribute name to new value
120
+ for values to clear
121
+ * `strict`: optional flag, default `False`;
122
+ if true, raise exceptions from failed `tcgetattr` and `tcsetattr` calls
123
+ otherwise issue a warning if the errno is not `ENOTTY` and proceed.
124
+ This aims to provide ease of use in batch mode by default
125
+ while providing a mode to fail overtly if required.
126
+
127
+ The attribute names are from
128
+ `iflag`, `oflag`, `cflag`, `lflag`, `ispeed`, `ospeed`, `cc`,
129
+ corresponding to the list entries defined by the `termios.tcgetattr`
130
+ call.
131
+
132
+ For `set_modes`, the attributes `ispeed`, `ospeed` and `cc`
133
+ are applied directly;
134
+ the other attributes are binary ORed into the existing modes.
135
+
136
+ For `clear_modes`, the attributes `ispeed`, `ospeed` and `cc`
137
+ cannot be cleared;
138
+ the other attributes are binary removed from the existing modes.
139
+
140
+ For example, to turn off the terminal echo during some operation:
141
+
142
+ with stack_termios(clear_modes={'lflag': termios.ECHO}):
143
+ ... do something with tty echo disabled ...
144
+
145
+ ## status(msg, *args, **kwargs)
146
+
147
+ Write a message to the terminal's status line.
148
+
149
+ Parameters:
150
+ * `msg`: message string
151
+ * `args`: if not empty, the message is %-formatted with `args`
152
+ * `file`: optional keyword argument specifying the output file.
153
+ Default: `sys.stderr`.
154
+
155
+ Hack: if there is no status line use the xterm title bar sequence :-(
156
+
157
+ ## statusline(text, fd=None, reverse=False, xpos=None, ypos=None)
158
+
159
+ Update the status line.
160
+
161
+ ## statusline_bs(text, reverse=False, xpos=None, ypos=None)
162
+
163
+ Return a byte string to update the status line.
164
+
165
+ ## ttysize(fd)
166
+
167
+ Return a (rows, columns) tuple for the specified file descriptor.
168
+
169
+ If the window size cannot be determined, None will be returned
170
+ for either or both of rows and columns.
171
+
172
+ This function relies on the UNIX `stty` command.
173
+
174
+ ## ttysizepx(fd)
175
+
176
+ Return a `(rows,columns,widthpx,heightpx)` tuple for the
177
+ specified file descriptor being the terminal character rows
178
+ and columns and pixel width and height respectively.
179
+
180
+ This function relies on the `fcntl.ioctl` using `termios.TIOCGWINSZ`.
181
+
182
+ # Classes
183
+
184
+ ## class WinSize(builtins.tuple)
185
+
186
+ WinSize(rows, columns)
187
+
188
+ ### `WinSize.__match_args__`
189
+
190
+ Built-in immutable sequence.
191
+
192
+ If no argument is given, the constructor returns an empty tuple.
193
+ If iterable is specified the tuple is initialized from iterable's items.
194
+
195
+ If the argument is a tuple, the return value is the same object.
196
+
197
+ ### `WinSize.__replace__(self, /, **kwds)`
198
+
199
+ Return a new WinSize object replacing specified fields with new values
200
+
201
+ ### `WinSize.__slots__`
202
+
203
+ Built-in immutable sequence.
204
+
205
+ If no argument is given, the constructor returns an empty tuple.
206
+ If iterable is specified the tuple is initialized from iterable's items.
207
+
208
+ If the argument is a tuple, the return value is the same object.
209
+
210
+ ### `WinSize.columns`
211
+
212
+ Alias for field number 1
213
+
214
+ ### `WinSize.rows`
215
+
216
+ Alias for field number 0
217
+
218
+ ## class WinSizePX(builtins.tuple)
219
+
220
+ WinSizePX(rows, columns, widthpx, heightpx)
221
+
222
+ ### `WinSizePX.__match_args__`
223
+
224
+ Built-in immutable sequence.
225
+
226
+ If no argument is given, the constructor returns an empty tuple.
227
+ If iterable is specified the tuple is initialized from iterable's items.
228
+
229
+ If the argument is a tuple, the return value is the same object.
230
+
231
+ ### `WinSizePX.__replace__(self, /, **kwds)`
232
+
233
+ Return a new WinSizePX object replacing specified fields with new values
234
+
235
+ ### `WinSizePX.__slots__`
236
+
237
+ Built-in immutable sequence.
238
+
239
+ If no argument is given, the constructor returns an empty tuple.
240
+ If iterable is specified the tuple is initialized from iterable's items.
241
+
242
+ If the argument is a tuple, the return value is the same object.
243
+
244
+ ### `WinSizePX.columns`
245
+
246
+ Alias for field number 1
247
+
248
+ ### `WinSizePX.heightpx`
249
+
250
+ Alias for field number 3
251
+
252
+ ### `WinSizePX.rows`
253
+
254
+ Alias for field number 0
255
+
256
+ ### `WinSizePX.widthpx`
257
+
258
+ Alias for field number 2
259
+
260
+ # Release Log
261
+
262
+
263
+
264
+ *Release 20260912*:
265
+ New ttysizepx(fd) function returning the tty size in characters and pixels.
266
+
267
+ *Release 20210316*:
268
+ * ttysize: discard the Popen object earlier.
269
+ * ttysize: close Popen.stdout after use. seems to leak.
270
+
271
+ *Release 20201102*:
272
+ New modify_termios and stack_termios to apply (and restore) termios modes.
273
+
274
+ *Release 20200521*:
275
+ * New status() function dragged in from cs.logutils, which uses cs.upd for status() -- needs some refactoring to match with the other functions in cs.tty -- text vs bytes, stdout vs stderr, etc.
276
+ * Get warning() from cs.gimmicks.
277
+
278
+ *Release 20190101*:
279
+ Small bugfix for setupterm.
280
+
281
+ *Release 20170903*:
282
+ add statusline and statusline_s functions; ttysize: support BSD stty output format
283
+
284
+ *Release 20160828*:
285
+ Use "install_requires" instead of "requires" in DISTINFO, add PyPI category.
286
+
287
+ *Release 20150116*:
288
+ Initial PyPI release.
@@ -0,0 +1,313 @@
1
+ [project]
2
+ name = "cs-tty"
3
+ description = "Functions related to terminals."
4
+ authors = [
5
+ { name = "Cameron Simpson", email = "cs@cskk.id.au" },
6
+ ]
7
+ keywords = [
8
+ "python2",
9
+ "python3",
10
+ ]
11
+ dependencies = [
12
+ "cs.gimmicks>=20260311",
13
+ ]
14
+ classifiers = [
15
+ "Environment :: Console",
16
+ "Operating System :: POSIX",
17
+ "Programming Language :: Python",
18
+ "Programming Language :: Python :: 2",
19
+ "Programming Language :: Python :: 3",
20
+ "Topic :: Terminals",
21
+ "Development Status :: 4 - Beta",
22
+ "Intended Audience :: Developers",
23
+ "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
24
+ ]
25
+ version = "20260912"
26
+
27
+ [project.license]
28
+ text = "GNU General Public License v3 or later (GPLv3+)"
29
+
30
+ [project.urls]
31
+ "Monorepo Hg/Mercurial Mirror" = "https://hg.sr.ht/~cameron-simpson/css"
32
+ "Monorepo Git Mirror" = "https://github.com/cameron-simpson/css"
33
+ "MonoRepo Commits" = "https://bitbucket.org/cameron_simpson/css/commits/branch/main"
34
+ Source = "https://github.com/cameron-simpson/css/blob/main/lib/python/cs/tty.py"
35
+
36
+ [project.readme]
37
+ text = """
38
+ Functions related to terminals.
39
+
40
+ *Latest release 20260912*:
41
+ New ttysizepx(fd) function returning the tty size in characters and pixels.
42
+
43
+
44
+
45
+ Short summary:
46
+
47
+
48
+ * `modify_termios`: Apply mode changes to a tty. Return the previous tty modes as from `termios.tcgetattr` or `None` if the changes could not be applied. If `strict`, raise an exception instead of returning `None`.
49
+
50
+
51
+ * `setupterm`: Run curses.setupterm, needed to be able to use the status line. Uses a global flag to avoid doing this twice.
52
+
53
+
54
+ * `stack_termios`: Context manager to apply and restore changes to a tty. Yield the previous tty modes as from `termios.tcgetattr` or `None` if the changes could not be applied. If `strict`, raise an exception instead of yielding `None`.
55
+
56
+
57
+ * `status`: Write a message to the terminal's status line.
58
+
59
+
60
+ * `statusline`: Update the status line.
61
+
62
+
63
+ * `statusline_bs`: Return a byte string to update the status line.
64
+
65
+
66
+ * `ttysize`: Return a (rows, columns) tuple for the specified file descriptor.
67
+
68
+
69
+ * `ttysizepx`: Return a `(rows,columns,widthpx,heightpx)` tuple for the specified file descriptor being the terminal character rows and columns and pixel width and height respectively.
70
+
71
+
72
+ * `WinSize`: WinSize(rows, columns).
73
+
74
+
75
+ * `WinSizePX`: WinSizePX(rows, columns, widthpx, heightpx).
76
+
77
+ # Functions
78
+
79
+ ## modify_termios(fd=0, set_modes=None, clear_modes=None, strict=False)
80
+
81
+ Apply mode changes to a tty.
82
+ Return the previous tty modes as from `termios.tcgetattr`
83
+ or `None` if the changes could not be applied.
84
+ If `strict`, raise an exception instead of returning `None`.
85
+
86
+ Parameters:
87
+ * `fd`: optional tty file descriptor, default `0`.
88
+ * `set_modes`: an optional mapping of attribute name to new value
89
+ for values to set
90
+ * `clear_modes`: an optional mapping of attribute name to new value
91
+ for values to clear
92
+ * `strict`: optional flag, default `False`;
93
+ if true, raise exceptions from failed `tcgetattr` and `tcsetattr` calls
94
+ otherwise issue a warning if the errno is not `ENOTTY` and proceed.
95
+ This aims to provide ease of use in batch mode by default
96
+ while providing a mode to fail overtly if required.
97
+
98
+ The attribute names are from
99
+ `iflag`, `oflag`, `cflag`, `lflag`, `ispeed`, `ospeed`, `cc`,
100
+ corresponding to the list entries defined by the `termios.tcgetattr`
101
+ call.
102
+
103
+ For `set_modes`, the attributes `ispeed`, `ospeed` and `cc`
104
+ are applied directly;
105
+ the other attributes are binary ORed into the existing modes.
106
+
107
+ For `clear_modes`, the attributes `ispeed`, `ospeed` and `cc`
108
+ cannot be cleared;
109
+ the other attributes are binary removed from the existing modes.
110
+
111
+ For example, to turn off the terminal echo during some operation:
112
+
113
+ old_modes = apply_termios(clear_modes={'lflag': termios.ECHO}):
114
+ ... do something with tty echo disabled ...
115
+ if old_modes:
116
+ termios.tcsetattr(fd, termios.TCSANOW, old_modes)
117
+
118
+ ## setupterm(*args)
119
+
120
+ Run curses.setupterm, needed to be able to use the status line.
121
+ Uses a global flag to avoid doing this twice.
122
+
123
+ ## stack_termios(fd=0, set_modes=None, clear_modes=None, strict=False)
124
+
125
+ Context manager to apply and restore changes to a tty.
126
+ Yield the previous tty modes as from `termios.tcgetattr`
127
+ or `None` if the changes could not be applied.
128
+ If `strict`, raise an exception instead of yielding `None`.
129
+
130
+ Parameters:
131
+ * `fd`: optional tty file descriptor, default `0`.
132
+ * `set_modes`: an optional mapping of attribute name to new value
133
+ for values to set
134
+ * `clear_modes`: an optional mapping of attribute name to new value
135
+ for values to clear
136
+ * `strict`: optional flag, default `False`;
137
+ if true, raise exceptions from failed `tcgetattr` and `tcsetattr` calls
138
+ otherwise issue a warning if the errno is not `ENOTTY` and proceed.
139
+ This aims to provide ease of use in batch mode by default
140
+ while providing a mode to fail overtly if required.
141
+
142
+ The attribute names are from
143
+ `iflag`, `oflag`, `cflag`, `lflag`, `ispeed`, `ospeed`, `cc`,
144
+ corresponding to the list entries defined by the `termios.tcgetattr`
145
+ call.
146
+
147
+ For `set_modes`, the attributes `ispeed`, `ospeed` and `cc`
148
+ are applied directly;
149
+ the other attributes are binary ORed into the existing modes.
150
+
151
+ For `clear_modes`, the attributes `ispeed`, `ospeed` and `cc`
152
+ cannot be cleared;
153
+ the other attributes are binary removed from the existing modes.
154
+
155
+ For example, to turn off the terminal echo during some operation:
156
+
157
+ with stack_termios(clear_modes={'lflag': termios.ECHO}):
158
+ ... do something with tty echo disabled ...
159
+
160
+ ## status(msg, *args, **kwargs)
161
+
162
+ Write a message to the terminal's status line.
163
+
164
+ Parameters:
165
+ * `msg`: message string
166
+ * `args`: if not empty, the message is %-formatted with `args`
167
+ * `file`: optional keyword argument specifying the output file.
168
+ Default: `sys.stderr`.
169
+
170
+ Hack: if there is no status line use the xterm title bar sequence :-(
171
+
172
+ ## statusline(text, fd=None, reverse=False, xpos=None, ypos=None)
173
+
174
+ Update the status line.
175
+
176
+ ## statusline_bs(text, reverse=False, xpos=None, ypos=None)
177
+
178
+ Return a byte string to update the status line.
179
+
180
+ ## ttysize(fd)
181
+
182
+ Return a (rows, columns) tuple for the specified file descriptor.
183
+
184
+ If the window size cannot be determined, None will be returned
185
+ for either or both of rows and columns.
186
+
187
+ This function relies on the UNIX `stty` command.
188
+
189
+ ## ttysizepx(fd)
190
+
191
+ Return a `(rows,columns,widthpx,heightpx)` tuple for the
192
+ specified file descriptor being the terminal character rows
193
+ and columns and pixel width and height respectively.
194
+
195
+ This function relies on the `fcntl.ioctl` using `termios.TIOCGWINSZ`.
196
+
197
+ # Classes
198
+
199
+ ## class WinSize(builtins.tuple)
200
+
201
+ WinSize(rows, columns)
202
+
203
+ ### `WinSize.__match_args__`
204
+
205
+ Built-in immutable sequence.
206
+
207
+ If no argument is given, the constructor returns an empty tuple.
208
+ If iterable is specified the tuple is initialized from iterable's items.
209
+
210
+ If the argument is a tuple, the return value is the same object.
211
+
212
+ ### `WinSize.__replace__(self, /, **kwds)`
213
+
214
+ Return a new WinSize object replacing specified fields with new values
215
+
216
+ ### `WinSize.__slots__`
217
+
218
+ Built-in immutable sequence.
219
+
220
+ If no argument is given, the constructor returns an empty tuple.
221
+ If iterable is specified the tuple is initialized from iterable's items.
222
+
223
+ If the argument is a tuple, the return value is the same object.
224
+
225
+ ### `WinSize.columns`
226
+
227
+ Alias for field number 1
228
+
229
+ ### `WinSize.rows`
230
+
231
+ Alias for field number 0
232
+
233
+ ## class WinSizePX(builtins.tuple)
234
+
235
+ WinSizePX(rows, columns, widthpx, heightpx)
236
+
237
+ ### `WinSizePX.__match_args__`
238
+
239
+ Built-in immutable sequence.
240
+
241
+ If no argument is given, the constructor returns an empty tuple.
242
+ If iterable is specified the tuple is initialized from iterable's items.
243
+
244
+ If the argument is a tuple, the return value is the same object.
245
+
246
+ ### `WinSizePX.__replace__(self, /, **kwds)`
247
+
248
+ Return a new WinSizePX object replacing specified fields with new values
249
+
250
+ ### `WinSizePX.__slots__`
251
+
252
+ Built-in immutable sequence.
253
+
254
+ If no argument is given, the constructor returns an empty tuple.
255
+ If iterable is specified the tuple is initialized from iterable's items.
256
+
257
+ If the argument is a tuple, the return value is the same object.
258
+
259
+ ### `WinSizePX.columns`
260
+
261
+ Alias for field number 1
262
+
263
+ ### `WinSizePX.heightpx`
264
+
265
+ Alias for field number 3
266
+
267
+ ### `WinSizePX.rows`
268
+
269
+ Alias for field number 0
270
+
271
+ ### `WinSizePX.widthpx`
272
+
273
+ Alias for field number 2
274
+
275
+ # Release Log
276
+
277
+
278
+
279
+ *Release 20260912*:
280
+ New ttysizepx(fd) function returning the tty size in characters and pixels.
281
+
282
+ *Release 20210316*:
283
+ * ttysize: discard the Popen object earlier.
284
+ * ttysize: close Popen.stdout after use. seems to leak.
285
+
286
+ *Release 20201102*:
287
+ New modify_termios and stack_termios to apply (and restore) termios modes.
288
+
289
+ *Release 20200521*:
290
+ * New status() function dragged in from cs.logutils, which uses cs.upd for status() -- needs some refactoring to match with the other functions in cs.tty -- text vs bytes, stdout vs stderr, etc.
291
+ * Get warning() from cs.gimmicks.
292
+
293
+ *Release 20190101*:
294
+ Small bugfix for setupterm.
295
+
296
+ *Release 20170903*:
297
+ add statusline and statusline_s functions; ttysize: support BSD stty output format
298
+
299
+ *Release 20160828*:
300
+ Use \"install_requires\" instead of \"requires\" in DISTINFO, add PyPI category.
301
+
302
+ *Release 20150116*:
303
+ Initial PyPI release."""
304
+ content-type = "text/markdown"
305
+
306
+ [build-system]
307
+ build-backend = "flit_core.buildapi"
308
+ requires = [
309
+ "flit_core >=3.2,<4",
310
+ ]
311
+
312
+ [tool.flit.module]
313
+ name = "cs.tty"
@@ -0,0 +1,339 @@
1
+ #!/usr/bin/python
2
+ #
3
+ # Facilities for terminals.
4
+ # - Cameron Simpson <cs@cskk.id.au>
5
+ #
6
+
7
+ ''' Functions related to terminals.
8
+ '''
9
+
10
+ from __future__ import print_function
11
+ import array
12
+ from collections import namedtuple
13
+ from contextlib import contextmanager
14
+ import errno
15
+ import fcntl
16
+ import os
17
+ import re
18
+ from subprocess import Popen, PIPE
19
+ import sys
20
+ from termios import tcsetattr, tcgetattr, TCSANOW, TIOCGWINSZ
21
+ from cs.gimmicks import warning
22
+
23
+ __version__ = '20260912'
24
+
25
+ DISTINFO = {
26
+ 'keywords': ["python2", "python3"],
27
+ 'classifiers': [
28
+ "Environment :: Console",
29
+ "Operating System :: POSIX",
30
+ "Programming Language :: Python",
31
+ "Programming Language :: Python :: 2",
32
+ "Programming Language :: Python :: 3",
33
+ "Topic :: Terminals",
34
+ ],
35
+ 'install_requires': ['cs.gimmicks'],
36
+ }
37
+
38
+ WinSize = namedtuple('WinSize', 'rows columns')
39
+
40
+ def ttysize(fd):
41
+ ''' Return a (rows, columns) tuple for the specified file descriptor.
42
+
43
+ If the window size cannot be determined, None will be returned
44
+ for either or both of rows and columns.
45
+
46
+ This function relies on the UNIX `stty` command.
47
+ '''
48
+ if not isinstance(fd, int):
49
+ fd = fd.fileno()
50
+ P = Popen(['stty', '-a'], stdin=fd, stdout=PIPE, universal_newlines=True)
51
+ stty = P.stdout.read()
52
+ P.stdout.close()
53
+ xit = P.wait()
54
+ del P
55
+ if xit != 0:
56
+ return None
57
+ m = re.compile(r' rows (\d+); columns (\d+)').search(stty)
58
+ if m:
59
+ rows, columns = int(m.group(1)), int(m.group(2))
60
+ else:
61
+ m = re.compile(r' (\d+) rows; (\d+) columns').search(stty)
62
+ if m:
63
+ rows, columns = int(m.group(1)), int(m.group(2))
64
+ else:
65
+ rows, columns = None, None
66
+ return WinSize(rows, columns)
67
+
68
+ WinSizePX = namedtuple('WinSizePX', 'rows columns widthpx heightpx')
69
+
70
+ def ttysizepx(fd):
71
+ ''' Return a `(rows,columns,widthpx,heightpx)` tuple for the
72
+ specified file descriptor being the terminal character rows
73
+ and columns and pixel width and height respectively.
74
+
75
+ This function relies on the `fcntl.ioctl` using `termios.TIOCGWINSZ`.
76
+ '''
77
+ if not isinstance(fd, int):
78
+ fd = fd.fileno()
79
+ buf = array.array('H', [0, 0, 0, 0])
80
+ fcntl.ioctl(fd, TIOCGWINSZ, buf)
81
+ return WinSizePX(*buf)
82
+
83
+ _ti_setup = False
84
+
85
+ def setupterm(*args):
86
+ ''' Run curses.setupterm, needed to be able to use the status line.
87
+ Uses a global flag to avoid doing this twice.
88
+ '''
89
+ global _ti_setup # pylint: disable=global-statement
90
+ if _ti_setup:
91
+ return True
92
+ termstr = None
93
+ fd = None
94
+ if args:
95
+ args = list(args)
96
+ termstr = args.pop(0)
97
+ if args:
98
+ fd = args.pop(0)
99
+ if args:
100
+ raise ValueError("extra arguments after termstr and fd: %r" % (args,))
101
+ if termstr is None:
102
+ termstr = os.environ['TERM']
103
+ if fd is None:
104
+ fd = sys.stdout.fileno()
105
+ import curses # pylint: disable=import-outside-toplevel
106
+ curses.setupterm(termstr, fd)
107
+ _ti_setup = True
108
+ return True
109
+
110
+ def statusline_bs(text, reverse=False, xpos=None, ypos=None):
111
+ ''' Return a byte string to update the status line.
112
+ '''
113
+ from curses import tigetstr, tparm, tigetflag # pylint: disable=import-outside-toplevel
114
+ setupterm()
115
+ if tigetflag('hs'):
116
+ seq = (
117
+ tigetstr('tsl'),
118
+ tigetstr('dsl'),
119
+ tigetstr('rev') if reverse else b'',
120
+ text.encode(),
121
+ tigetstr('fsl'),
122
+ )
123
+ else:
124
+ # save cursor position, position, reverse, restore position
125
+ if xpos is None:
126
+ xpos = 0
127
+ if ypos is None:
128
+ ypos = 0
129
+ seq = (
130
+ tigetstr('sc'), # save cursor position
131
+ tparm(tigetstr("cup"), xpos, ypos),
132
+ tigetstr('rev') if reverse else b'',
133
+ text.encode(),
134
+ tigetstr('el'),
135
+ tigetstr('rc')
136
+ )
137
+ return b''.join(seq)
138
+
139
+ def statusline(text, fd=None, reverse=False, xpos=None, ypos=None):
140
+ ''' Update the status line.
141
+ '''
142
+ if fd is None:
143
+ fd = sys.stdout.fileno()
144
+ os.write(fd, statusline_bs(text, reverse=reverse, xpos=xpos, ypos=ypos))
145
+
146
+ def status(msg, *args, **kwargs):
147
+ ''' Write a message to the terminal's status line.
148
+
149
+ Parameters:
150
+ * `msg`: message string
151
+ * `args`: if not empty, the message is %-formatted with `args`
152
+ * `file`: optional keyword argument specifying the output file.
153
+ Default: `sys.stderr`.
154
+
155
+ Hack: if there is no status line use the xterm title bar sequence :-(
156
+ '''
157
+ if args:
158
+ msg = msg % args
159
+ f = kwargs.pop('file', None)
160
+ if kwargs:
161
+ raise ValueError("unexpected keyword arguments: %r" % (kwargs,))
162
+ if f is None:
163
+ f = sys.stderr
164
+ try:
165
+ has_ansi_status = f.has_ansi_status
166
+ except AttributeError:
167
+ try:
168
+ import curses # pylint: disable=import-outside-toplevel
169
+ except ImportError:
170
+ has_ansi_status = None
171
+ else:
172
+ curses.setupterm()
173
+ has_status = curses.tigetflag('hs')
174
+ if has_status == -1:
175
+ warning(
176
+ 'status: curses.tigetflag(hs): not a Boolean capability, presuming false'
177
+ )
178
+ has_ansi_status = None
179
+ elif has_status > 0:
180
+ has_ansi_status = (
181
+ curses.tigetstr('to_status_line'),
182
+ curses.tigetstr('from_status_line')
183
+ )
184
+ else:
185
+ warning('status: hs=%s, presuming false', has_status)
186
+ has_ansi_status = None
187
+ f.has_ansi_status = has_ansi_status
188
+ if has_ansi_status:
189
+ msg = has_ansi_status[0] + msg + has_ansi_status[1]
190
+ else:
191
+ msg = '\033]0;' + msg + '\007'
192
+ f.write(msg)
193
+ f.flush()
194
+
195
+ _termios_modes_names = {
196
+ name: index
197
+ for index, name in
198
+ enumerate(('iflag', 'oflag', 'cflag', 'lflag', 'ispeed', 'ospeed', 'cc'))
199
+ }
200
+
201
+ # pylint: disable=too-many-branches
202
+ def modify_termios(fd=0, set_modes=None, clear_modes=None, strict=False):
203
+ ''' Apply mode changes to a tty.
204
+ Return the previous tty modes as from `termios.tcgetattr`
205
+ or `None` if the changes could not be applied.
206
+ If `strict`, raise an exception instead of returning `None`.
207
+
208
+ Parameters:
209
+ * `fd`: optional tty file descriptor, default `0`.
210
+ * `set_modes`: an optional mapping of attribute name to new value
211
+ for values to set
212
+ * `clear_modes`: an optional mapping of attribute name to new value
213
+ for values to clear
214
+ * `strict`: optional flag, default `False`;
215
+ if true, raise exceptions from failed `tcgetattr` and `tcsetattr` calls
216
+ otherwise issue a warning if the errno is not `ENOTTY` and proceed.
217
+ This aims to provide ease of use in batch mode by default
218
+ while providing a mode to fail overtly if required.
219
+
220
+ The attribute names are from
221
+ `iflag`, `oflag`, `cflag`, `lflag`, `ispeed`, `ospeed`, `cc`,
222
+ corresponding to the list entries defined by the `termios.tcgetattr`
223
+ call.
224
+
225
+ For `set_modes`, the attributes `ispeed`, `ospeed` and `cc`
226
+ are applied directly;
227
+ the other attributes are binary ORed into the existing modes.
228
+
229
+ For `clear_modes`, the attributes `ispeed`, `ospeed` and `cc`
230
+ cannot be cleared;
231
+ the other attributes are binary removed from the existing modes.
232
+
233
+ For example, to turn off the terminal echo during some operation:
234
+
235
+ old_modes = apply_termios(clear_modes={'lflag': termios.ECHO}):
236
+ ... do something with tty echo disabled ...
237
+ if old_modes:
238
+ termios.tcsetattr(fd, termios.TCSANOW, old_modes)
239
+ '''
240
+ if set_modes:
241
+ if not all(map(lambda k: k in _termios_modes_names, set_modes.keys())):
242
+ raise ValueError(
243
+ "set_modes: invalid mode keys: known=%r, supplied=%r" %
244
+ (sorted(_termios_modes_names.keys()), set_modes)
245
+ )
246
+ if clear_modes:
247
+ if not all(map(lambda k: k in _termios_modes_names, clear_modes.keys())):
248
+ raise ValueError(
249
+ "clear_modes: invalid mode keys: known=%r, supplied=%r" %
250
+ (sorted(_termios_modes_names.keys()), clear_modes)
251
+ )
252
+ for k in 'ispeed', 'ospeed', 'cc':
253
+ if k in clear_modes:
254
+ raise ValueError("clear_modes: cannot clear %r" % (k,))
255
+ try:
256
+ original_modes = tcgetattr(fd)
257
+ except OSError as e:
258
+ if strict:
259
+ raise
260
+ if e.errno != errno.ENOTTY:
261
+ warning("tcgetattr(%d): %s", fd, e)
262
+ original_modes = None
263
+ restore_modes = None
264
+ if original_modes:
265
+ new_modes = list(original_modes)
266
+ if set_modes:
267
+ for k, v in set_modes.items():
268
+ i = _termios_modes_names[k]
269
+ if k in ('ispeed', 'ospeed', 'cc'):
270
+ new_modes[i] = v
271
+ else:
272
+ new_modes[i] |= v
273
+ if clear_modes:
274
+ for k, v in clear_modes.items():
275
+ i = _termios_modes_names[k]
276
+ new_modes[i] &= ~v
277
+ if new_modes == original_modes:
278
+ restore_modes = None
279
+ else:
280
+ try:
281
+ tcsetattr(fd, TCSANOW, new_modes)
282
+ except OSError as e:
283
+ if strict:
284
+ raise
285
+ warning("tcsetattr(%d,TCSANOW,%r): %e", fd, new_modes, e)
286
+ else:
287
+ restore_modes = original_modes
288
+ return restore_modes
289
+
290
+ @contextmanager
291
+ def stack_termios(fd=0, set_modes=None, clear_modes=None, strict=False):
292
+ ''' Context manager to apply and restore changes to a tty.
293
+ Yield the previous tty modes as from `termios.tcgetattr`
294
+ or `None` if the changes could not be applied.
295
+ If `strict`, raise an exception instead of yielding `None`.
296
+
297
+ Parameters:
298
+ * `fd`: optional tty file descriptor, default `0`.
299
+ * `set_modes`: an optional mapping of attribute name to new value
300
+ for values to set
301
+ * `clear_modes`: an optional mapping of attribute name to new value
302
+ for values to clear
303
+ * `strict`: optional flag, default `False`;
304
+ if true, raise exceptions from failed `tcgetattr` and `tcsetattr` calls
305
+ otherwise issue a warning if the errno is not `ENOTTY` and proceed.
306
+ This aims to provide ease of use in batch mode by default
307
+ while providing a mode to fail overtly if required.
308
+
309
+ The attribute names are from
310
+ `iflag`, `oflag`, `cflag`, `lflag`, `ispeed`, `ospeed`, `cc`,
311
+ corresponding to the list entries defined by the `termios.tcgetattr`
312
+ call.
313
+
314
+ For `set_modes`, the attributes `ispeed`, `ospeed` and `cc`
315
+ are applied directly;
316
+ the other attributes are binary ORed into the existing modes.
317
+
318
+ For `clear_modes`, the attributes `ispeed`, `ospeed` and `cc`
319
+ cannot be cleared;
320
+ the other attributes are binary removed from the existing modes.
321
+
322
+ For example, to turn off the terminal echo during some operation:
323
+
324
+ with stack_termios(clear_modes={'lflag': termios.ECHO}):
325
+ ... do something with tty echo disabled ...
326
+ '''
327
+ try:
328
+ restore_modes = modify_termios(
329
+ fd, set_modes=set_modes, clear_modes=clear_modes, strict=strict
330
+ )
331
+ yield restore_modes
332
+ finally:
333
+ if restore_modes:
334
+ try:
335
+ tcsetattr(fd, TCSANOW, restore_modes)
336
+ except OSError as e:
337
+ if strict:
338
+ raise
339
+ warning("tcsetattr(%d,TCSANOW,%r): %e", fd, restore_modes, e)