qssh 0.2.3__tar.gz → 0.2.4__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qssh
3
- Version: 0.2.3
3
+ Version: 0.2.4
4
4
  Summary: Quick SSH session manager - save your VM credentials and connect with a single command
5
5
  Author: joan-code6
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "qssh"
7
- version = "0.2.3"
7
+ version = "0.2.4"
8
8
  description = "Quick SSH session manager - save your VM credentials and connect with a single command"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  """qssh - Quick SSH session manager."""
2
2
 
3
- __version__ = "0.2.3"
3
+ __version__ = "0.2.4"
4
4
  __author__ = "bennet"
5
5
 
6
6
  from .session import SessionManager
@@ -173,13 +173,35 @@ class SSHConnector:
173
173
  channel: SSH channel
174
174
  """
175
175
  import threading
176
- import msvcrt
176
+ import ctypes
177
177
  import time
178
+ from ctypes import wintypes
178
179
 
179
- # Ignore SIGINT (Ctrl+C) at the Python level - we'll handle it manually
180
+ # Windows Console API constants
181
+ STD_INPUT_HANDLE = -10
182
+ ENABLE_PROCESSED_INPUT = 0x0001
183
+ ENABLE_LINE_INPUT = 0x0002
184
+ ENABLE_ECHO_INPUT = 0x0004
185
+ ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200
186
+
187
+ kernel32 = ctypes.windll.kernel32
188
+
189
+ # Get console handle
190
+ stdin_handle = kernel32.GetStdHandle(STD_INPUT_HANDLE)
191
+
192
+ # Save original console mode
193
+ original_mode = wintypes.DWORD()
194
+ kernel32.GetConsoleMode(stdin_handle, ctypes.byref(original_mode))
195
+
196
+ # Set console to raw mode (disable processed input to capture Ctrl+C)
197
+ new_mode = original_mode.value & ~(ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT)
198
+ new_mode |= ENABLE_VIRTUAL_TERMINAL_INPUT
199
+ kernel32.SetConsoleMode(stdin_handle, new_mode)
200
+
201
+ # Ignore SIGINT at Python level as backup
180
202
  original_sigint = signal.signal(signal.SIGINT, signal.SIG_IGN)
181
203
 
182
- running = [True] # Use list to allow modification in nested function
204
+ running = [True]
183
205
 
184
206
  def read_output():
185
207
  """Read from channel and print to stdout."""
@@ -191,11 +213,9 @@ class SSHConnector:
191
213
  sys.stdout.write(data.decode("utf-8", errors="replace"))
192
214
  sys.stdout.flush()
193
215
  else:
194
- # Empty data means connection closed
195
216
  running[0] = False
196
217
  break
197
218
 
198
- # Check if channel is closed
199
219
  if channel.closed or channel.exit_status_ready():
200
220
  running[0] = False
201
221
  break
@@ -205,53 +225,88 @@ class SSHConnector:
205
225
  running[0] = False
206
226
  break
207
227
 
208
- # Start output reader thread
228
+ # Structure for reading console input
229
+ class KEY_EVENT_RECORD(ctypes.Structure):
230
+ _fields_ = [
231
+ ("bKeyDown", wintypes.BOOL),
232
+ ("wRepeatCount", wintypes.WORD),
233
+ ("wVirtualKeyCode", wintypes.WORD),
234
+ ("wVirtualScanCode", wintypes.WORD),
235
+ ("uChar", ctypes.c_wchar),
236
+ ("dwControlKeyState", wintypes.DWORD),
237
+ ]
238
+
239
+ class INPUT_RECORD_UNION(ctypes.Union):
240
+ _fields_ = [("KeyEvent", KEY_EVENT_RECORD)]
241
+
242
+ class INPUT_RECORD(ctypes.Structure):
243
+ _fields_ = [
244
+ ("EventType", wintypes.WORD),
245
+ ("Event", INPUT_RECORD_UNION),
246
+ ]
247
+
248
+ KEY_EVENT = 0x0001
249
+
250
+ # Virtual key codes for special keys
251
+ VK_MAP = {
252
+ 0x26: '\x1b[A', # Up
253
+ 0x28: '\x1b[B', # Down
254
+ 0x27: '\x1b[C', # Right
255
+ 0x25: '\x1b[D', # Left
256
+ 0x24: '\x1b[H', # Home
257
+ 0x23: '\x1b[F', # End
258
+ 0x2D: '\x1b[2~', # Insert
259
+ 0x2E: '\x1b[3~', # Delete
260
+ 0x21: '\x1b[5~', # Page Up
261
+ 0x22: '\x1b[6~', # Page Down
262
+ }
263
+
209
264
  output_thread = threading.Thread(target=read_output, daemon=True)
210
265
  output_thread.start()
211
266
 
212
267
  try:
268
+ input_record = INPUT_RECORD()
269
+ events_read = wintypes.DWORD()
270
+
213
271
  while running[0] and not channel.closed:
214
- # Check for keyboard input (non-blocking)
215
- if msvcrt.kbhit():
216
- # Use getch for raw byte input (better for control chars)
217
- char = msvcrt.getwch()
272
+ # Check if input is available
273
+ events_available = wintypes.DWORD()
274
+ kernel32.GetNumberOfConsoleInputEvents(stdin_handle, ctypes.byref(events_available))
275
+
276
+ if events_available.value > 0:
277
+ # Read input event
278
+ kernel32.ReadConsoleInputW(
279
+ stdin_handle,
280
+ ctypes.byref(input_record),
281
+ 1,
282
+ ctypes.byref(events_read)
283
+ )
218
284
 
219
- if char == '\r':
220
- # Enter key - send carriage return
221
- channel.send('\r')
222
- elif char == '\x00' or char == '\xe0':
223
- # Special keys (arrows, function keys, etc.)
224
- char2 = msvcrt.getwch()
225
- # Map arrow keys to ANSI escape sequences
226
- key_map = {
227
- 'H': '\x1b[A', # Up
228
- 'P': '\x1b[B', # Down
229
- 'M': '\x1b[C', # Right
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
237
- }
238
- if char2 in key_map:
239
- channel.send(key_map[char2])
240
- elif char == '\x08':
241
- # Backspace
242
- channel.send('\x7f')
243
- else:
244
- # Send character as-is (includes Ctrl+C as \x03, Ctrl+D as \x04, etc.)
245
- channel.send(char)
285
+ if input_record.EventType == KEY_EVENT:
286
+ key_event = input_record.Event.KeyEvent
287
+
288
+ if key_event.bKeyDown:
289
+ vk = key_event.wVirtualKeyCode
290
+ char = key_event.uChar
291
+
292
+ # Check for special keys
293
+ if vk in VK_MAP:
294
+ channel.send(VK_MAP[vk])
295
+ elif char == '\r':
296
+ channel.send('\r')
297
+ elif char == '\x08': # Backspace
298
+ channel.send('\x7f')
299
+ elif char:
300
+ # Send character including control chars (Ctrl+C = \x03)
301
+ channel.send(char)
246
302
  else:
247
- # Small delay to prevent CPU spinning
248
303
  time.sleep(0.01)
249
304
 
250
305
  finally:
251
306
  running[0] = False
252
- # Wait for output thread to finish
253
307
  output_thread.join(timeout=1.0)
254
- # Restore original signal handler
308
+ # Restore original console mode
309
+ kernel32.SetConsoleMode(stdin_handle, original_mode.value)
255
310
  signal.signal(signal.SIGINT, original_sigint)
256
311
 
257
312
  def _unix_interactive_shell(self, channel) -> None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qssh
3
- Version: 0.2.3
3
+ Version: 0.2.4
4
4
  Summary: Quick SSH session manager - save your VM credentials and connect with a single command
5
5
  Author: joan-code6
6
6
  License-Expression: MIT
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes