qssh 0.2.2__tar.gz → 0.2.3__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.
- {qssh-0.2.2/src/qssh.egg-info → qssh-0.2.3}/PKG-INFO +1 -1
- {qssh-0.2.2 → qssh-0.2.3}/pyproject.toml +1 -1
- {qssh-0.2.2 → qssh-0.2.3}/src/qssh/__init__.py +1 -1
- {qssh-0.2.2 → qssh-0.2.3}/src/qssh/connector.py +42 -16
- {qssh-0.2.2 → qssh-0.2.3/src/qssh.egg-info}/PKG-INFO +1 -1
- {qssh-0.2.2 → qssh-0.2.3}/LICENSE +0 -0
- {qssh-0.2.2 → qssh-0.2.3}/README.md +0 -0
- {qssh-0.2.2 → qssh-0.2.3}/setup.cfg +0 -0
- {qssh-0.2.2 → qssh-0.2.3}/src/qssh/cli.py +0 -0
- {qssh-0.2.2 → qssh-0.2.3}/src/qssh/py.typed +0 -0
- {qssh-0.2.2 → qssh-0.2.3}/src/qssh/session.py +0 -0
- {qssh-0.2.2 → qssh-0.2.3}/src/qssh.egg-info/SOURCES.txt +0 -0
- {qssh-0.2.2 → qssh-0.2.3}/src/qssh.egg-info/dependency_links.txt +0 -0
- {qssh-0.2.2 → qssh-0.2.3}/src/qssh.egg-info/entry_points.txt +0 -0
- {qssh-0.2.2 → qssh-0.2.3}/src/qssh.egg-info/requires.txt +0 -0
- {qssh-0.2.2 → qssh-0.2.3}/src/qssh.egg-info/top_level.txt +0 -0
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
import sys
|
|
5
|
+
import signal
|
|
5
6
|
import select
|
|
6
7
|
import subprocess
|
|
7
8
|
import platform
|
|
@@ -173,12 +174,16 @@ class SSHConnector:
|
|
|
173
174
|
"""
|
|
174
175
|
import threading
|
|
175
176
|
import msvcrt
|
|
177
|
+
import time
|
|
176
178
|
|
|
177
|
-
|
|
179
|
+
# Ignore SIGINT (Ctrl+C) at the Python level - we'll handle it manually
|
|
180
|
+
original_sigint = signal.signal(signal.SIGINT, signal.SIG_IGN)
|
|
181
|
+
|
|
182
|
+
running = [True] # Use list to allow modification in nested function
|
|
178
183
|
|
|
179
184
|
def read_output():
|
|
180
185
|
"""Read from channel and print to stdout."""
|
|
181
|
-
while running:
|
|
186
|
+
while running[0]:
|
|
182
187
|
try:
|
|
183
188
|
if channel.recv_ready():
|
|
184
189
|
data = channel.recv(4096)
|
|
@@ -186,8 +191,18 @@ class SSHConnector:
|
|
|
186
191
|
sys.stdout.write(data.decode("utf-8", errors="replace"))
|
|
187
192
|
sys.stdout.flush()
|
|
188
193
|
else:
|
|
194
|
+
# Empty data means connection closed
|
|
195
|
+
running[0] = False
|
|
189
196
|
break
|
|
197
|
+
|
|
198
|
+
# Check if channel is closed
|
|
199
|
+
if channel.closed or channel.exit_status_ready():
|
|
200
|
+
running[0] = False
|
|
201
|
+
break
|
|
202
|
+
|
|
203
|
+
time.sleep(0.01)
|
|
190
204
|
except Exception:
|
|
205
|
+
running[0] = False
|
|
191
206
|
break
|
|
192
207
|
|
|
193
208
|
# Start output reader thread
|
|
@@ -195,16 +210,17 @@ class SSHConnector:
|
|
|
195
210
|
output_thread.start()
|
|
196
211
|
|
|
197
212
|
try:
|
|
198
|
-
while running and not channel.closed:
|
|
199
|
-
# Check for keyboard input
|
|
213
|
+
while running[0] and not channel.closed:
|
|
214
|
+
# Check for keyboard input (non-blocking)
|
|
200
215
|
if msvcrt.kbhit():
|
|
216
|
+
# Use getch for raw byte input (better for control chars)
|
|
201
217
|
char = msvcrt.getwch()
|
|
218
|
+
|
|
202
219
|
if char == '\r':
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
# Read the second byte for arrow keys etc.
|
|
220
|
+
# Enter key - send carriage return
|
|
221
|
+
channel.send('\r')
|
|
222
|
+
elif char == '\x00' or char == '\xe0':
|
|
223
|
+
# Special keys (arrows, function keys, etc.)
|
|
208
224
|
char2 = msvcrt.getwch()
|
|
209
225
|
# Map arrow keys to ANSI escape sequences
|
|
210
226
|
key_map = {
|
|
@@ -212,21 +228,31 @@ class SSHConnector:
|
|
|
212
228
|
'P': '\x1b[B', # Down
|
|
213
229
|
'M': '\x1b[C', # Right
|
|
214
230
|
'K': '\x1b[D', # Left
|
|
231
|
+
'G': '\x1b[H', # Home
|
|
232
|
+
'O': '\x1b[F', # End
|
|
233
|
+
'R': '\x1b[2~', # Insert
|
|
234
|
+
'S': '\x1b[3~', # Delete
|
|
235
|
+
'I': '\x1b[5~', # Page Up
|
|
236
|
+
'Q': '\x1b[6~', # Page Down
|
|
215
237
|
}
|
|
216
238
|
if char2 in key_map:
|
|
217
239
|
channel.send(key_map[char2])
|
|
240
|
+
elif char == '\x08':
|
|
241
|
+
# Backspace
|
|
242
|
+
channel.send('\x7f')
|
|
218
243
|
else:
|
|
244
|
+
# Send character as-is (includes Ctrl+C as \x03, Ctrl+D as \x04, etc.)
|
|
219
245
|
channel.send(char)
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if not channel.recv_ready():
|
|
223
|
-
import time
|
|
246
|
+
else:
|
|
247
|
+
# Small delay to prevent CPU spinning
|
|
224
248
|
time.sleep(0.01)
|
|
225
249
|
|
|
226
|
-
except KeyboardInterrupt:
|
|
227
|
-
pass
|
|
228
250
|
finally:
|
|
229
|
-
running = False
|
|
251
|
+
running[0] = False
|
|
252
|
+
# Wait for output thread to finish
|
|
253
|
+
output_thread.join(timeout=1.0)
|
|
254
|
+
# Restore original signal handler
|
|
255
|
+
signal.signal(signal.SIGINT, original_sigint)
|
|
230
256
|
|
|
231
257
|
def _unix_interactive_shell(self, channel) -> None:
|
|
232
258
|
"""Interactive shell for Unix systems using select.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|