On Nov 9, 2019, at 1:17 AM, Augustus Tan <
augustu...@gmail.com> wrote:
Hi, I'm kind of new to AsyncSSH, I wonder if there is anyway to detect hotkeys (ctrl-z, ctrl-d)?
So far, I'm able to detect it if I edit the source code at channel.py
class SSHServerChannel(SSHChannel):
"""SSH server channel"""
.
.
.
.
def _accept_data(self, data, datatype=None):
print(data)
I've tried to subclass this SSHServerChannel, but I'm not sure how to pass it over to SSHServerSession self._chan.
Any idea how I can detect those hotkeys?
The SSHChannel class is not meant to be subclassed. Its implementation is internal to AsyncSSH, and _accept_data() in particular is a private function, so you run the risk of your code breaking in future AsyncSSH releases if you attempt to override its implementation.
If you are running an SSH server which allows pseudo-TTYs to be created and the client is requesting one of those, AsyncSSH will enable line editing by default on stdin. In that case, you can use the register_key() method on the SSH server channel object to set a handler to call when certain keys are pressed. However, that’s mainly meant to let you define your own custom key sequences to modify the input line, so I’m not sure that’ll give you the flexibility to do what you want.
There’s actually already a handler registered in this case for Ctrl-D which would trigger a soft “EOF” to be reported on the channel when you are reading it (where read returns an empty string), so supporting Ctrl-D wouldn’t necessarily require you to register that as a hot key. There’s also already a handler registered for Ctrl-C which would cause the BreakReceived exception to be raised on reads. You might be able to register your own handler for Ctrl-Z using register_key, but it would hard to leverage any of the high-level stream or process API functions if you did this. You’d instead be forced back to using the low-level callback APIs. For instance:
class MySSHServerSession(asyncssh.SSHServerSession):
def shell_requested(self):
return True
def connection_made(self, chan):
self._chan = chan
def session_started(self):
self._chan.register_key('\x1a', self.ctrlz_received)
def data_received(self, data, datatype):
print('Got data:', repr(data))
def soft_eof_received(self):
print('Got EOF')
def break_received(self, msec):
print('Got break')
def ctrlz_received(self, line, pos):
print('Got Ctrl-Z')
return line, pos
class MySSHServer(asyncssh.SSHServer):
def session_requested(self):
return MySSHServerSession()
async def start_server():
await asyncssh.create_server(MySSHServer, '', 8022,
server_host_keys=['ssh_host_key'],
authorized_client_keys='ssh_user_ca')
I could potentially extend the line editor register_key() functionality to allow signals to be raised on the channel, and that would let you use the higher-level API to raise a SignalReceived exception when a certain key was hit, breaking you out of whatever read you were blocked on. This would require a small change to the editor code, but with it you’d end up being able to write something like:
def handle_ctrlz(line, pos):
return 'CTRLZ', -1
async def handle_client(process):
process.channel.register_key('\x1a', handle_ctrlz)
while True:
try:
line = await process.stdin.readline()
if line:
print('Got input:', repr(line))
else:
print('Got EOF')
except asyncssh.BreakReceived:
print('Got break')
except asyncssh.SignalReceived as exc:
print('Got signal:', exc.signal)
process.exit(0)
async def start_server():
await asyncssh.listen('', 8022, server_host_keys=['ssh_host_key'],
authorized_client_keys='ssh_user_ca',
process_factory=handle_client)
Would this be useful to you?
--