cmdgui 0.1.1__tar.gz → 0.3.0__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.
- cmdgui-0.3.0/PKG-INFO +106 -0
- cmdgui-0.3.0/README.md +89 -0
- {cmdgui-0.1.1 → cmdgui-0.3.0}/pyproject.toml +1 -1
- cmdgui-0.3.0/src/cmdgui/__init__.py +14 -0
- {cmdgui-0.1.1 → cmdgui-0.3.0}/src/cmdgui/_platform.py +5 -2
- cmdgui-0.3.0/src/cmdgui/clipboard.py +53 -0
- {cmdgui-0.1.1 → cmdgui-0.3.0}/src/cmdgui/inputs.py +32 -3
- cmdgui-0.3.0/src/cmdgui/layout.py +444 -0
- cmdgui-0.3.0/src/cmdgui/raster.py +429 -0
- cmdgui-0.3.0/src/cmdgui/shorts.py +539 -0
- cmdgui-0.3.0/src/cmdgui/view.py +1348 -0
- cmdgui-0.3.0/src/cmdgui/widgets/__init__.py +14 -0
- cmdgui-0.3.0/src/cmdgui/widgets/base.py +353 -0
- cmdgui-0.3.0/src/cmdgui/widgets/container.py +83 -0
- cmdgui-0.3.0/src/cmdgui/widgets/controls.py +294 -0
- cmdgui-0.3.0/src/cmdgui/widgets/dates.py +231 -0
- cmdgui-0.3.0/src/cmdgui/widgets/graphics.py +282 -0
- cmdgui-0.3.0/src/cmdgui/widgets/lists.py +391 -0
- cmdgui-0.3.0/src/cmdgui/widgets/log.py +204 -0
- cmdgui-0.3.0/src/cmdgui/widgets/stdout.py +87 -0
- cmdgui-0.3.0/src/cmdgui/widgets/tabs.py +213 -0
- cmdgui-0.3.0/src/cmdgui/widgets/text.py +443 -0
- cmdgui-0.3.0/src/cmdgui.egg-info/PKG-INFO +106 -0
- cmdgui-0.3.0/src/cmdgui.egg-info/SOURCES.txt +26 -0
- cmdgui-0.1.1/PKG-INFO +0 -265
- cmdgui-0.1.1/README.md +0 -248
- cmdgui-0.1.1/src/cmdgui/__init__.py +0 -14
- cmdgui-0.1.1/src/cmdgui/layout.py +0 -249
- cmdgui-0.1.1/src/cmdgui/shorts.py +0 -307
- cmdgui-0.1.1/src/cmdgui/view.py +0 -713
- cmdgui-0.1.1/src/cmdgui/widgets.py +0 -577
- cmdgui-0.1.1/src/cmdgui.egg-info/PKG-INFO +0 -265
- cmdgui-0.1.1/src/cmdgui.egg-info/SOURCES.txt +0 -14
- {cmdgui-0.1.1 → cmdgui-0.3.0}/LICENSE +0 -0
- {cmdgui-0.1.1 → cmdgui-0.3.0}/setup.cfg +0 -0
- {cmdgui-0.1.1 → cmdgui-0.3.0}/src/cmdgui.egg-info/dependency_links.txt +0 -0
- {cmdgui-0.1.1 → cmdgui-0.3.0}/src/cmdgui.egg-info/top_level.txt +0 -0
cmdgui-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cmdgui
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Simple terminal GUIs: draw your layout as text, the view runs itself
|
|
5
|
+
Author: olliez-mods
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: homepage, https://github.com/olliez-mods/cmdgui
|
|
8
|
+
Keywords: terminal,tui,gui,cli,widgets
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Operating System :: MacOS
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
13
|
+
Requires-Python: >=3.9
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# cmdgui
|
|
19
|
+
|
|
20
|
+
Simple terminal GUIs in Python. Draw your layout as text, fill in the widgets,
|
|
21
|
+
and keep writing your program — the view runs on its own thread, so there's no
|
|
22
|
+
event loop to hand control to and no draw calls to make.
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
┌─ name ───────────────────┐
|
|
26
|
+
│quinn │[ Greet ] ON fast
|
|
27
|
+
├─ fruit ──────────────────┼─ scores ──────────────────────────────────┐
|
|
28
|
+
│ apple │name score city │
|
|
29
|
+
│ banana │Ada 98 London │
|
|
30
|
+
│ cherry │Linus 87 Helsinki │
|
|
31
|
+
│ ├─ log ─────────────────────────────────────┤
|
|
32
|
+
│ │Hello, quinn! 👋 │
|
|
33
|
+
│ │picked cherry │
|
|
34
|
+
└──────────────────────────┴───────────────────────────────────────────┘
|
|
35
|
+
████████████████████████████████░░░░░░░░░░░░░░░░ 67%[ ] done
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
No dependencies. macOS and Linux; Windows support is written but untested.
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install cmdgui
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Quick start
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from cmdgui import View, Button, Stdout
|
|
50
|
+
import time
|
|
51
|
+
|
|
52
|
+
class App(View):
|
|
53
|
+
layout = """
|
|
54
|
+
start pause .
|
|
55
|
+
log - -
|
|
56
|
+
"""
|
|
57
|
+
start = Button("Start", on_click=lambda: print("started"))
|
|
58
|
+
pause = Button("Pause", on_click=lambda: print("paused"))
|
|
59
|
+
log = Stdout()
|
|
60
|
+
|
|
61
|
+
view = App()
|
|
62
|
+
|
|
63
|
+
for i in range(100):
|
|
64
|
+
print(f"tick {i}") # shows up in the log box
|
|
65
|
+
time.sleep(0.5)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Press `q` to quit (or Ctrl+C). Because the widgets are class attributes, your
|
|
69
|
+
editor knows `view.start` is a `Button` — autocomplete and type checking work.
|
|
70
|
+
|
|
71
|
+
## What's in it
|
|
72
|
+
|
|
73
|
+
- **Layouts drawn as text**: each word is a grid cell, `-` and `|` stretch a widget
|
|
74
|
+
across cells, and borders between neighbours join up.
|
|
75
|
+
- **Widgets**: text, labels, buttons, one-line and multi-line text boxes (with password
|
|
76
|
+
mode and history), checkboxes, toggles, radio buttons, dropdowns, sliders, progress
|
|
77
|
+
bars, menus, folding trees, tables, a calendar and date picker, tabs, split panes to
|
|
78
|
+
drag, a log viewer with levels and search, a box that shows everything you print, and
|
|
79
|
+
a pixel canvas with lines, shapes and curves.
|
|
80
|
+
- **Styled text**: `"[bold red]Error:[/] couldn't open [cyan]notes.txt[/]"` in labels,
|
|
81
|
+
buttons and text.
|
|
82
|
+
- **Text boxes that behave**: select with the mouse or Shift, copy, cut and paste,
|
|
83
|
+
pasting in one go, and dim hints before, after and ahead of the cursor for autocomplete.
|
|
84
|
+
- **Inline views**: draw in a few rows under the prompt instead of the whole screen, with
|
|
85
|
+
printed text scrolling above.
|
|
86
|
+
- **Popups**: dialogs, dropdowns and command menus that float over the layout.
|
|
87
|
+
- **Mouse and keyboard**: clicks, dragging, the scroll wheel, Tab between widgets, and
|
|
88
|
+
your own key bindings.
|
|
89
|
+
- **Timers**: `view.every(1, tick)` and `view.after(5, fn)`, no threads needed.
|
|
90
|
+
- **Themes**: restyle any part, with 73 named colors, the 256-color palette, or exact
|
|
91
|
+
hex colors.
|
|
92
|
+
- **Your own widgets**: subclass `Widget`, draw into a canvas, and it works in layouts.
|
|
93
|
+
|
|
94
|
+
## Documentation
|
|
95
|
+
|
|
96
|
+
- [Overview](https://github.com/olliez-mods/cmdgui/blob/main/docs/index.md): ways to build a view, changing widgets, timers, running and quitting
|
|
97
|
+
- [Layouts](https://github.com/olliez-mods/cmdgui/blob/main/docs/layouts.md)
|
|
98
|
+
- [Widgets](https://github.com/olliez-mods/cmdgui/blob/main/docs/widgets.md)
|
|
99
|
+
- [Popups](https://github.com/olliez-mods/cmdgui/blob/main/docs/popups.md)
|
|
100
|
+
- [Keys and focus](https://github.com/olliez-mods/cmdgui/blob/main/docs/keys-and-focus.md)
|
|
101
|
+
- [Themes and colors](https://github.com/olliez-mods/cmdgui/blob/main/docs/themes.md)
|
|
102
|
+
- [Your own widgets](https://github.com/olliez-mods/cmdgui/blob/main/docs/custom-widgets.md)
|
|
103
|
+
|
|
104
|
+
The [examples](https://github.com/olliez-mods/cmdgui/tree/main/examples) folder has a
|
|
105
|
+
widget demo, a dashboard, popups, tabs, a console, a file browser, pixel graphics, a
|
|
106
|
+
log viewer with split panes, date pickers, and an inline progress display.
|
cmdgui-0.3.0/README.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# cmdgui
|
|
2
|
+
|
|
3
|
+
Simple terminal GUIs in Python. Draw your layout as text, fill in the widgets,
|
|
4
|
+
and keep writing your program — the view runs on its own thread, so there's no
|
|
5
|
+
event loop to hand control to and no draw calls to make.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
┌─ name ───────────────────┐
|
|
9
|
+
│quinn │[ Greet ] ON fast
|
|
10
|
+
├─ fruit ──────────────────┼─ scores ──────────────────────────────────┐
|
|
11
|
+
│ apple │name score city │
|
|
12
|
+
│ banana │Ada 98 London │
|
|
13
|
+
│ cherry │Linus 87 Helsinki │
|
|
14
|
+
│ ├─ log ─────────────────────────────────────┤
|
|
15
|
+
│ │Hello, quinn! 👋 │
|
|
16
|
+
│ │picked cherry │
|
|
17
|
+
└──────────────────────────┴───────────────────────────────────────────┘
|
|
18
|
+
████████████████████████████████░░░░░░░░░░░░░░░░ 67%[ ] done
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
No dependencies. macOS and Linux; Windows support is written but untested.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install cmdgui
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick start
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from cmdgui import View, Button, Stdout
|
|
33
|
+
import time
|
|
34
|
+
|
|
35
|
+
class App(View):
|
|
36
|
+
layout = """
|
|
37
|
+
start pause .
|
|
38
|
+
log - -
|
|
39
|
+
"""
|
|
40
|
+
start = Button("Start", on_click=lambda: print("started"))
|
|
41
|
+
pause = Button("Pause", on_click=lambda: print("paused"))
|
|
42
|
+
log = Stdout()
|
|
43
|
+
|
|
44
|
+
view = App()
|
|
45
|
+
|
|
46
|
+
for i in range(100):
|
|
47
|
+
print(f"tick {i}") # shows up in the log box
|
|
48
|
+
time.sleep(0.5)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Press `q` to quit (or Ctrl+C). Because the widgets are class attributes, your
|
|
52
|
+
editor knows `view.start` is a `Button` — autocomplete and type checking work.
|
|
53
|
+
|
|
54
|
+
## What's in it
|
|
55
|
+
|
|
56
|
+
- **Layouts drawn as text**: each word is a grid cell, `-` and `|` stretch a widget
|
|
57
|
+
across cells, and borders between neighbours join up.
|
|
58
|
+
- **Widgets**: text, labels, buttons, one-line and multi-line text boxes (with password
|
|
59
|
+
mode and history), checkboxes, toggles, radio buttons, dropdowns, sliders, progress
|
|
60
|
+
bars, menus, folding trees, tables, a calendar and date picker, tabs, split panes to
|
|
61
|
+
drag, a log viewer with levels and search, a box that shows everything you print, and
|
|
62
|
+
a pixel canvas with lines, shapes and curves.
|
|
63
|
+
- **Styled text**: `"[bold red]Error:[/] couldn't open [cyan]notes.txt[/]"` in labels,
|
|
64
|
+
buttons and text.
|
|
65
|
+
- **Text boxes that behave**: select with the mouse or Shift, copy, cut and paste,
|
|
66
|
+
pasting in one go, and dim hints before, after and ahead of the cursor for autocomplete.
|
|
67
|
+
- **Inline views**: draw in a few rows under the prompt instead of the whole screen, with
|
|
68
|
+
printed text scrolling above.
|
|
69
|
+
- **Popups**: dialogs, dropdowns and command menus that float over the layout.
|
|
70
|
+
- **Mouse and keyboard**: clicks, dragging, the scroll wheel, Tab between widgets, and
|
|
71
|
+
your own key bindings.
|
|
72
|
+
- **Timers**: `view.every(1, tick)` and `view.after(5, fn)`, no threads needed.
|
|
73
|
+
- **Themes**: restyle any part, with 73 named colors, the 256-color palette, or exact
|
|
74
|
+
hex colors.
|
|
75
|
+
- **Your own widgets**: subclass `Widget`, draw into a canvas, and it works in layouts.
|
|
76
|
+
|
|
77
|
+
## Documentation
|
|
78
|
+
|
|
79
|
+
- [Overview](https://github.com/olliez-mods/cmdgui/blob/main/docs/index.md): ways to build a view, changing widgets, timers, running and quitting
|
|
80
|
+
- [Layouts](https://github.com/olliez-mods/cmdgui/blob/main/docs/layouts.md)
|
|
81
|
+
- [Widgets](https://github.com/olliez-mods/cmdgui/blob/main/docs/widgets.md)
|
|
82
|
+
- [Popups](https://github.com/olliez-mods/cmdgui/blob/main/docs/popups.md)
|
|
83
|
+
- [Keys and focus](https://github.com/olliez-mods/cmdgui/blob/main/docs/keys-and-focus.md)
|
|
84
|
+
- [Themes and colors](https://github.com/olliez-mods/cmdgui/blob/main/docs/themes.md)
|
|
85
|
+
- [Your own widgets](https://github.com/olliez-mods/cmdgui/blob/main/docs/custom-widgets.md)
|
|
86
|
+
|
|
87
|
+
The [examples](https://github.com/olliez-mods/cmdgui/tree/main/examples) folder has a
|
|
88
|
+
widget demo, a dashboard, popups, tabs, a console, a file browser, pixel graphics, a
|
|
89
|
+
log viewer with split panes, date pickers, and an inline progress display.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from .view import View, Popup, Panel, Edge, Timer
|
|
2
|
+
from .widgets import (
|
|
3
|
+
Widget, Text, Label, Button, TextInput, TextArea, ProgressBar, Slider, Checkbox, Toggle,
|
|
4
|
+
RadioGroup, Select, Menu, Tree, Table, Tabs, Graphics, Calendar, DatePicker, Log, Stdout, DrawMouse, DEFAULT_THEME, field,
|
|
5
|
+
)
|
|
6
|
+
from .inputs import Input, mouse
|
|
7
|
+
from .layout import LayoutError
|
|
8
|
+
from .shorts import Canvas, style, styled, Color, ColorName
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"View", "Popup", "Panel", "Edge", "Timer", "Widget", "Text", "Label", "Button", "TextInput", "TextArea", "ProgressBar",
|
|
12
|
+
"Slider", "Checkbox", "Toggle", "RadioGroup", "Select", "Menu", "Tree", "Table", "Tabs", "Graphics", "Calendar", "DatePicker", "Log", "Stdout", "DrawMouse", "DEFAULT_THEME", "field",
|
|
13
|
+
"Input", "mouse", "LayoutError", "Canvas", "style", "styled", "Color", "ColorName",
|
|
14
|
+
]
|
|
@@ -22,8 +22,11 @@ if not WINDOWS:
|
|
|
22
22
|
fd = sys.stdin.fileno()
|
|
23
23
|
self._saved = termios.tcgetattr(fd)
|
|
24
24
|
attrs = termios.tcgetattr(fd)
|
|
25
|
-
# No line buffering
|
|
26
|
-
|
|
25
|
+
# No line buffering or echo. Ctrl+C, Ctrl+V, Ctrl+S and so on come through as keys
|
|
26
|
+
# (ISIG, IEXTEN, IXON off): the view turns Ctrl+C back into KeyboardInterrupt when
|
|
27
|
+
# it isn't copying, and Ctrl+S no longer freezes the output
|
|
28
|
+
attrs[3] &= ~(termios.ICANON | termios.ECHO | termios.ISIG | termios.IEXTEN)
|
|
29
|
+
attrs[0] &= ~termios.IXON
|
|
27
30
|
termios.tcsetattr(fd, termios.TCSANOW, attrs)
|
|
28
31
|
|
|
29
32
|
def disable(self):
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Copying to the system clipboard. A terminal app can't reach the clipboard
|
|
2
|
+
directly, so copy() tries both ways there are:
|
|
3
|
+
|
|
4
|
+
- OSC 52, an escape code asking the terminal to copy. Works over SSH. Most modern
|
|
5
|
+
terminals support it (kitty, WezTerm, Ghostty, Alacritty, Windows Terminal, foot,
|
|
6
|
+
iTerm2 once allowed in its settings); macOS's Terminal.app doesn't.
|
|
7
|
+
- the system's own tool (pbcopy, wl-copy, xclip, xsel, clip), when running locally.
|
|
8
|
+
|
|
9
|
+
Pasting from the system clipboard is done by the terminal (Cmd+V, or Ctrl+Shift+V),
|
|
10
|
+
which sends the text to the app as a paste. paste() gives what was last copied in
|
|
11
|
+
this program, for Ctrl+V."""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import base64
|
|
15
|
+
import os
|
|
16
|
+
import shutil
|
|
17
|
+
import subprocess
|
|
18
|
+
import sys
|
|
19
|
+
from .shorts import write
|
|
20
|
+
|
|
21
|
+
_last = "" # the last text copied, for paste()
|
|
22
|
+
|
|
23
|
+
# Clipboard tools, most likely first: (command, needs)
|
|
24
|
+
TOOLS = [
|
|
25
|
+
(["pbcopy"], "darwin"),
|
|
26
|
+
(["wl-copy"], "WAYLAND_DISPLAY"),
|
|
27
|
+
(["xclip", "-selection", "clipboard"], "DISPLAY"),
|
|
28
|
+
(["xsel", "--clipboard", "--input"], "DISPLAY"),
|
|
29
|
+
(["clip"], "win32"),
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def copy(text: str) -> None:
|
|
34
|
+
"""Put text on the system clipboard (as far as the terminal allows), and keep it
|
|
35
|
+
for paste()."""
|
|
36
|
+
global _last
|
|
37
|
+
_last = text
|
|
38
|
+
write("\x1b]52;c;" + base64.b64encode(text.encode("utf-8")).decode("ascii") + "\x07")
|
|
39
|
+
if os.environ.get("SSH_CONNECTION") or os.environ.get("SSH_TTY"):
|
|
40
|
+
return # the tools would copy on the remote machine, not yours
|
|
41
|
+
for command, needs in TOOLS:
|
|
42
|
+
if (needs == sys.platform or os.environ.get(needs)) and shutil.which(command[0]):
|
|
43
|
+
try:
|
|
44
|
+
subprocess.run(command, input=text.encode("utf-8"), timeout=2, check=False,
|
|
45
|
+
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
46
|
+
except (OSError, subprocess.SubprocessError):
|
|
47
|
+
continue
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def paste() -> str:
|
|
52
|
+
"""The text last copied with copy() in this program."""
|
|
53
|
+
return _last
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import queue
|
|
2
2
|
import re
|
|
3
3
|
import sys
|
|
4
|
+
import time
|
|
4
5
|
|
|
5
6
|
from .shorts import ESC, write
|
|
6
7
|
from ._platform import Terminal
|
|
@@ -57,6 +58,10 @@ def update_state(input):
|
|
|
57
58
|
# 1006 = SGR encoding, which gives readable decimal coordinates with no size limit
|
|
58
59
|
MOUSE_ON = ESC + "?1003h" + ESC + "?1006h"
|
|
59
60
|
MOUSE_OFF = ESC + "?1003l" + ESC + "?1006l"
|
|
61
|
+
# Bracketed paste: the terminal wraps pasted text in these, so it arrives as one
|
|
62
|
+
# "paste" input instead of keys (a pasted newline isn't Enter)
|
|
63
|
+
PASTE_ON, PASTE_OFF = ESC + "?2004h", ESC + "?2004l"
|
|
64
|
+
PASTE_START, PASTE_END = "\x1b[200~", "\x1b[201~"
|
|
60
65
|
|
|
61
66
|
# SGR mouse report: ESC [ < button ; x ; y then M (press/move) or m (release)
|
|
62
67
|
SGR_MOUSE = re.compile(r"\x1b\[<(\d+);(\d+);(\d+)([Mm])")
|
|
@@ -105,7 +110,7 @@ def enable():
|
|
|
105
110
|
"""Raw keyboard mode, mouse reporting, and capture print output."""
|
|
106
111
|
_stderr_log.clear()
|
|
107
112
|
_terminal.enable()
|
|
108
|
-
write(MOUSE_ON)
|
|
113
|
+
write(MOUSE_ON + PASTE_ON)
|
|
109
114
|
sys.stdout = StreamInterceptor(sys.stdout, "stdout")
|
|
110
115
|
sys.stderr = StreamInterceptor(sys.stderr, "stderr")
|
|
111
116
|
|
|
@@ -116,7 +121,7 @@ def disable():
|
|
|
116
121
|
sys.stdout = sys.stdout.real
|
|
117
122
|
if isinstance(sys.stderr, StreamInterceptor):
|
|
118
123
|
sys.stderr = sys.stderr.real
|
|
119
|
-
write(MOUSE_OFF)
|
|
124
|
+
write(MOUSE_OFF + PASTE_OFF)
|
|
120
125
|
_terminal.disable()
|
|
121
126
|
|
|
122
127
|
|
|
@@ -125,6 +130,23 @@ def captured_stderr():
|
|
|
125
130
|
return "".join(_stderr_log)
|
|
126
131
|
|
|
127
132
|
|
|
133
|
+
CURSOR_REPORT = re.compile(r"\x1b\[(\d+);(\d+)R")
|
|
134
|
+
|
|
135
|
+
def cursor_position(timeout=0.5):
|
|
136
|
+
"""Ask the terminal where the cursor is: (x, y), 0-based, or None if it doesn't say.
|
|
137
|
+
Call after enable() and before reading inputs; anything typed meanwhile is kept."""
|
|
138
|
+
global _buffer
|
|
139
|
+
write(ESC + "6n")
|
|
140
|
+
end = time.monotonic() + timeout
|
|
141
|
+
while time.monotonic() < end:
|
|
142
|
+
_buffer += _terminal.read(max(0.0, end - time.monotonic()))
|
|
143
|
+
match = CURSOR_REPORT.search(_buffer)
|
|
144
|
+
if match:
|
|
145
|
+
_buffer = _buffer[:match.start()] + _buffer[match.end():]
|
|
146
|
+
return int(match.group(2)) - 1, int(match.group(1)) - 1
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
|
|
128
150
|
def wake():
|
|
129
151
|
"""Make read_inputs() return straight away. Safe from any thread."""
|
|
130
152
|
_terminal.wake()
|
|
@@ -146,7 +168,14 @@ def read_inputs(timeout=0.1):
|
|
|
146
168
|
inputs.append(Input(kind, {"text": text}))
|
|
147
169
|
|
|
148
170
|
while _buffer:
|
|
149
|
-
if _buffer.startswith(
|
|
171
|
+
if _buffer.startswith(PASTE_START):
|
|
172
|
+
end = _buffer.find(PASTE_END)
|
|
173
|
+
if end < 0:
|
|
174
|
+
break # the rest of the paste is still coming
|
|
175
|
+
text = _buffer[len(PASTE_START):end].replace("\r\n", "\n").replace("\r", "\n")
|
|
176
|
+
inputs.append(Input("paste", {"text": text}))
|
|
177
|
+
_buffer = _buffer[end + len(PASTE_END):]
|
|
178
|
+
elif _buffer.startswith("\x1b[<"):
|
|
150
179
|
match = SGR_MOUSE.match(_buffer)
|
|
151
180
|
if not match:
|
|
152
181
|
break # incomplete mouse report, wait for the rest
|