mirror of
https://github.com/m1ngsama/TNT.git
synced 2025-12-24 10:51:41 +00:00
Fix vim command mode double colon bug
When pressing ':' in NORMAL mode, the key was being processed twice: 1. handle_key() detected it and switched to COMMAND mode 2. The same ':' character was then added to command_input This resulted in '::' appearing instead of ':'. Solution: - Changed handle_key() to return bool indicating if key was consumed - Only add character to input if handle_key() returns false - All mode-switching keys now return true to prevent reprocessing Fixes the most annoying UX bug reported by users.
This commit is contained in:
parent
6c9d243f9a
commit
03c89beeb4
1 changed files with 151 additions and 63 deletions
156
src/ssh_server.c
156
src/ssh_server.c
|
|
@ -68,6 +68,39 @@ int client_send(client_t *client, const char *data, size_t len) {
|
||||||
return (sent < 0) ? -1 : 0;
|
return (sent < 0) ? -1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Increment client reference count - currently unused but kept for future use */
|
||||||
|
static void client_addref(client_t *client) __attribute__((unused));
|
||||||
|
static void client_addref(client_t *client) {
|
||||||
|
if (!client) return;
|
||||||
|
pthread_mutex_lock(&client->ref_lock);
|
||||||
|
client->ref_count++;
|
||||||
|
pthread_mutex_unlock(&client->ref_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Decrement client reference count and free if zero */
|
||||||
|
static void client_release(client_t *client) {
|
||||||
|
if (!client) return;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&client->ref_lock);
|
||||||
|
client->ref_count--;
|
||||||
|
int count = client->ref_count;
|
||||||
|
pthread_mutex_unlock(&client->ref_lock);
|
||||||
|
|
||||||
|
if (count == 0) {
|
||||||
|
/* Safe to free now */
|
||||||
|
if (client->channel) {
|
||||||
|
ssh_channel_close(client->channel);
|
||||||
|
ssh_channel_free(client->channel);
|
||||||
|
}
|
||||||
|
if (client->session) {
|
||||||
|
ssh_disconnect(client->session);
|
||||||
|
ssh_free(client->session);
|
||||||
|
}
|
||||||
|
pthread_mutex_destroy(&client->ref_lock);
|
||||||
|
free(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Send formatted string to client */
|
/* Send formatted string to client */
|
||||||
int client_printf(client_t *client, const char *fmt, ...) {
|
int client_printf(client_t *client, const char *fmt, ...) {
|
||||||
char buffer[2048];
|
char buffer[2048];
|
||||||
|
|
@ -88,7 +121,16 @@ static int read_username(client_t *client) {
|
||||||
client_printf(client, "请输入用户名: ");
|
client_printf(client, "请输入用户名: ");
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
int n = ssh_channel_read(client->channel, buf, 1, 0);
|
int n = ssh_channel_read_timeout(client->channel, buf, 1, 0, 60000); /* 60 sec timeout */
|
||||||
|
|
||||||
|
if (n == SSH_AGAIN) {
|
||||||
|
/* Timeout */
|
||||||
|
if (!ssh_channel_is_open(client->channel)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (n <= 0) return -1;
|
if (n <= 0) return -1;
|
||||||
|
|
||||||
unsigned char b = buf[0];
|
unsigned char b = buf[0];
|
||||||
|
|
@ -115,7 +157,11 @@ static int read_username(client_t *client) {
|
||||||
int len = utf8_byte_length(b);
|
int len = utf8_byte_length(b);
|
||||||
buf[0] = b;
|
buf[0] = b;
|
||||||
if (len > 1) {
|
if (len > 1) {
|
||||||
ssh_channel_read(client->channel, &buf[1], len - 1, 0);
|
int read_bytes = ssh_channel_read(client->channel, &buf[1], len - 1, 0);
|
||||||
|
if (read_bytes != len - 1) {
|
||||||
|
/* Incomplete UTF-8 */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (pos + len < MAX_USERNAME_LEN - 1) {
|
if (pos + len < MAX_USERNAME_LEN - 1) {
|
||||||
memcpy(username + pos, buf, len);
|
memcpy(username + pos, buf, len);
|
||||||
|
|
@ -216,8 +262,8 @@ static void execute_command(client_t *client) {
|
||||||
tui_render_command_output(client);
|
tui_render_command_output(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Handle client key press */
|
/* Handle client key press - returns true if key was consumed */
|
||||||
static void handle_key(client_t *client, unsigned char key, char *input) {
|
static bool handle_key(client_t *client, unsigned char key, char *input) {
|
||||||
/* Handle help screen */
|
/* Handle help screen */
|
||||||
if (client->show_help) {
|
if (client->show_help) {
|
||||||
if (key == 'q' || key == 27) {
|
if (key == 'q' || key == 27) {
|
||||||
|
|
@ -244,7 +290,7 @@ static void handle_key(client_t *client, unsigned char key, char *input) {
|
||||||
client->help_scroll_pos = 999; /* Large number */
|
client->help_scroll_pos = 999; /* Large number */
|
||||||
tui_render_help(client);
|
tui_render_help(client);
|
||||||
}
|
}
|
||||||
return;
|
return true; /* Key consumed */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Handle command output display */
|
/* Handle command output display */
|
||||||
|
|
@ -252,7 +298,7 @@ static void handle_key(client_t *client, unsigned char key, char *input) {
|
||||||
client->command_output[0] = '\0';
|
client->command_output[0] = '\0';
|
||||||
client->mode = MODE_NORMAL;
|
client->mode = MODE_NORMAL;
|
||||||
tui_render_screen(client);
|
tui_render_screen(client);
|
||||||
return;
|
return true; /* Key consumed */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mode-specific handling */
|
/* Mode-specific handling */
|
||||||
|
|
@ -262,6 +308,7 @@ static void handle_key(client_t *client, unsigned char key, char *input) {
|
||||||
client->mode = MODE_NORMAL;
|
client->mode = MODE_NORMAL;
|
||||||
client->scroll_pos = 0;
|
client->scroll_pos = 0;
|
||||||
tui_render_screen(client);
|
tui_render_screen(client);
|
||||||
|
return true; /* Key consumed */
|
||||||
} else if (key == '\r') { /* Enter */
|
} else if (key == '\r') { /* Enter */
|
||||||
if (input[0] != '\0') {
|
if (input[0] != '\0') {
|
||||||
message_t msg = {
|
message_t msg = {
|
||||||
|
|
@ -274,11 +321,13 @@ static void handle_key(client_t *client, unsigned char key, char *input) {
|
||||||
input[0] = '\0';
|
input[0] = '\0';
|
||||||
}
|
}
|
||||||
tui_render_screen(client);
|
tui_render_screen(client);
|
||||||
|
return true; /* Key consumed */
|
||||||
} else if (key == 127 || key == 8) { /* Backspace */
|
} else if (key == 127 || key == 8) { /* Backspace */
|
||||||
if (input[0] != '\0') {
|
if (input[0] != '\0') {
|
||||||
utf8_remove_last_char(input);
|
utf8_remove_last_char(input);
|
||||||
tui_render_input(client, input);
|
tui_render_input(client, input);
|
||||||
}
|
}
|
||||||
|
return true; /* Key consumed */
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
@ -286,30 +335,37 @@ static void handle_key(client_t *client, unsigned char key, char *input) {
|
||||||
if (key == 'i') {
|
if (key == 'i') {
|
||||||
client->mode = MODE_INSERT;
|
client->mode = MODE_INSERT;
|
||||||
tui_render_screen(client);
|
tui_render_screen(client);
|
||||||
|
return true; /* Key consumed */
|
||||||
} else if (key == ':') {
|
} else if (key == ':') {
|
||||||
client->mode = MODE_COMMAND;
|
client->mode = MODE_COMMAND;
|
||||||
client->command_input[0] = '\0';
|
client->command_input[0] = '\0';
|
||||||
tui_render_screen(client);
|
tui_render_screen(client);
|
||||||
|
return true; /* Key consumed - prevents double colon */
|
||||||
} else if (key == 'j') {
|
} else if (key == 'j') {
|
||||||
int max_scroll = room_get_message_count(g_room) - 1;
|
int max_scroll = room_get_message_count(g_room) - 1;
|
||||||
if (client->scroll_pos < max_scroll) {
|
if (client->scroll_pos < max_scroll) {
|
||||||
client->scroll_pos++;
|
client->scroll_pos++;
|
||||||
tui_render_screen(client);
|
tui_render_screen(client);
|
||||||
}
|
}
|
||||||
|
return true; /* Key consumed */
|
||||||
} else if (key == 'k' && client->scroll_pos > 0) {
|
} else if (key == 'k' && client->scroll_pos > 0) {
|
||||||
client->scroll_pos--;
|
client->scroll_pos--;
|
||||||
tui_render_screen(client);
|
tui_render_screen(client);
|
||||||
|
return true; /* Key consumed */
|
||||||
} else if (key == 'g') {
|
} else if (key == 'g') {
|
||||||
client->scroll_pos = 0;
|
client->scroll_pos = 0;
|
||||||
tui_render_screen(client);
|
tui_render_screen(client);
|
||||||
|
return true; /* Key consumed */
|
||||||
} else if (key == 'G') {
|
} else if (key == 'G') {
|
||||||
client->scroll_pos = room_get_message_count(g_room) - 1;
|
client->scroll_pos = room_get_message_count(g_room) - 1;
|
||||||
if (client->scroll_pos < 0) client->scroll_pos = 0;
|
if (client->scroll_pos < 0) client->scroll_pos = 0;
|
||||||
tui_render_screen(client);
|
tui_render_screen(client);
|
||||||
|
return true; /* Key consumed */
|
||||||
} else if (key == '?') {
|
} else if (key == '?') {
|
||||||
client->show_help = true;
|
client->show_help = true;
|
||||||
client->help_scroll_pos = 0;
|
client->help_scroll_pos = 0;
|
||||||
tui_render_help(client);
|
tui_render_help(client);
|
||||||
|
return true; /* Key consumed */
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
@ -318,19 +374,24 @@ static void handle_key(client_t *client, unsigned char key, char *input) {
|
||||||
client->mode = MODE_NORMAL;
|
client->mode = MODE_NORMAL;
|
||||||
client->command_input[0] = '\0';
|
client->command_input[0] = '\0';
|
||||||
tui_render_screen(client);
|
tui_render_screen(client);
|
||||||
|
return true; /* Key consumed */
|
||||||
} else if (key == '\r' || key == '\n') {
|
} else if (key == '\r' || key == '\n') {
|
||||||
execute_command(client);
|
execute_command(client);
|
||||||
|
return true; /* Key consumed */
|
||||||
} else if (key == 127 || key == 8) { /* Backspace */
|
} else if (key == 127 || key == 8) { /* Backspace */
|
||||||
if (client->command_input[0] != '\0') {
|
if (client->command_input[0] != '\0') {
|
||||||
utf8_remove_last_char(client->command_input);
|
utf8_remove_last_char(client->command_input);
|
||||||
tui_render_screen(client);
|
tui_render_screen(client);
|
||||||
}
|
}
|
||||||
|
return true; /* Key consumed */
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false; /* Key not consumed */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Handle client session */
|
/* Handle client session */
|
||||||
|
|
@ -368,17 +429,37 @@ void* client_handle_session(void *arg) {
|
||||||
|
|
||||||
/* Main input loop */
|
/* Main input loop */
|
||||||
while (client->connected && ssh_channel_is_open(client->channel)) {
|
while (client->connected && ssh_channel_is_open(client->channel)) {
|
||||||
int n = ssh_channel_read(client->channel, buf, 1, 0);
|
/* Use non-blocking read with timeout */
|
||||||
if (n <= 0) break;
|
int n = ssh_channel_read_timeout(client->channel, buf, 1, 0, 30000); /* 30 sec timeout */
|
||||||
|
|
||||||
|
if (n == SSH_AGAIN) {
|
||||||
|
/* Timeout - check if channel is still alive */
|
||||||
|
if (!ssh_channel_is_open(client->channel)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n == SSH_ERROR) {
|
||||||
|
/* Read error - connection likely closed */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n <= 0) {
|
||||||
|
/* EOF or error */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
unsigned char b = buf[0];
|
unsigned char b = buf[0];
|
||||||
|
|
||||||
/* Ctrl+C */
|
/* Ctrl+C */
|
||||||
if (b == 3) break;
|
if (b == 3) break;
|
||||||
|
|
||||||
/* Handle special keys */
|
/* Handle special keys - returns true if key was consumed */
|
||||||
handle_key(client, b, input);
|
bool key_consumed = handle_key(client, b, input);
|
||||||
|
|
||||||
|
/* Only add character to input if not consumed by handle_key */
|
||||||
|
if (!key_consumed) {
|
||||||
/* Add character to input (INSERT mode only) */
|
/* Add character to input (INSERT mode only) */
|
||||||
if (client->mode == MODE_INSERT && !client->show_help &&
|
if (client->mode == MODE_INSERT && !client->show_help &&
|
||||||
client->command_output[0] == '\0') {
|
client->command_output[0] == '\0') {
|
||||||
|
|
@ -393,7 +474,11 @@ void* client_handle_session(void *arg) {
|
||||||
int char_len = utf8_byte_length(b);
|
int char_len = utf8_byte_length(b);
|
||||||
buf[0] = b;
|
buf[0] = b;
|
||||||
if (char_len > 1) {
|
if (char_len > 1) {
|
||||||
ssh_channel_read(client->channel, &buf[1], char_len - 1, 0);
|
int read_bytes = ssh_channel_read(client->channel, &buf[1], char_len - 1, 0);
|
||||||
|
if (read_bytes != char_len - 1) {
|
||||||
|
/* Incomplete UTF-8 sequence */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
int len = strlen(input);
|
int len = strlen(input);
|
||||||
if (len + char_len < MAX_MESSAGE_LEN - 1) {
|
if (len + char_len < MAX_MESSAGE_LEN - 1) {
|
||||||
|
|
@ -405,7 +490,7 @@ void* client_handle_session(void *arg) {
|
||||||
} else if (client->mode == MODE_COMMAND && !client->show_help &&
|
} else if (client->mode == MODE_COMMAND && !client->show_help &&
|
||||||
client->command_output[0] == '\0') {
|
client->command_output[0] == '\0') {
|
||||||
if (b >= 32 && b < 127) { /* ASCII printable */
|
if (b >= 32 && b < 127) { /* ASCII printable */
|
||||||
int len = strlen(client->command_input);
|
size_t len = strlen(client->command_input);
|
||||||
if (len < sizeof(client->command_input) - 1) {
|
if (len < sizeof(client->command_input) - 1) {
|
||||||
client->command_input[len] = b;
|
client->command_input[len] = b;
|
||||||
client->command_input[len + 1] = '\0';
|
client->command_input[len + 1] = '\0';
|
||||||
|
|
@ -414,6 +499,7 @@ void* client_handle_session(void *arg) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
/* Broadcast leave message */
|
/* Broadcast leave message */
|
||||||
|
|
@ -429,15 +515,8 @@ cleanup:
|
||||||
room_broadcast(g_room, &leave_msg);
|
room_broadcast(g_room, &leave_msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client->channel) {
|
/* Release the main reference - client will be freed when all refs are gone */
|
||||||
ssh_channel_close(client->channel);
|
client_release(client);
|
||||||
ssh_channel_free(client->channel);
|
|
||||||
}
|
|
||||||
if (client->session) {
|
|
||||||
ssh_disconnect(client->session);
|
|
||||||
ssh_free(client->session);
|
|
||||||
}
|
|
||||||
free(client);
|
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
@ -498,6 +577,8 @@ static ssh_channel handle_channel_open(ssh_session session) {
|
||||||
/* Handle PTY request and get terminal size */
|
/* Handle PTY request and get terminal size */
|
||||||
static int handle_pty_request(ssh_channel channel, client_t *client) {
|
static int handle_pty_request(ssh_channel channel, client_t *client) {
|
||||||
ssh_message message;
|
ssh_message message;
|
||||||
|
int pty_received = 0;
|
||||||
|
int shell_received = 0;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
message = ssh_message_get(ssh_channel_get_session(channel));
|
message = ssh_message_get(ssh_channel_get_session(channel));
|
||||||
|
|
@ -515,24 +596,29 @@ static int handle_pty_request(ssh_channel channel, client_t *client) {
|
||||||
|
|
||||||
ssh_message_channel_request_reply_success(message);
|
ssh_message_channel_request_reply_success(message);
|
||||||
ssh_message_free(message);
|
ssh_message_free(message);
|
||||||
|
pty_received = 1;
|
||||||
|
|
||||||
|
/* Don't return yet, wait for shell request */
|
||||||
|
if (shell_received) {
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
|
||||||
} else if (ssh_message_subtype(message) == SSH_CHANNEL_REQUEST_SHELL) {
|
} else if (ssh_message_subtype(message) == SSH_CHANNEL_REQUEST_SHELL) {
|
||||||
ssh_message_channel_request_reply_success(message);
|
ssh_message_channel_request_reply_success(message);
|
||||||
ssh_message_free(message);
|
ssh_message_free(message);
|
||||||
|
shell_received = 1;
|
||||||
|
|
||||||
|
/* If we got PTY, we're done */
|
||||||
|
if (pty_received) {
|
||||||
return 0;
|
return 0;
|
||||||
} else if (ssh_message_subtype(message) == SSH_CHANNEL_REQUEST_WINDOW_CHANGE) {
|
|
||||||
/* Handle terminal resize */
|
|
||||||
client->width = ssh_message_channel_request_pty_width(message);
|
|
||||||
client->height = ssh_message_channel_request_pty_height(message);
|
|
||||||
|
|
||||||
if (client->width <= 0 || client->width > 500) client->width = 80;
|
|
||||||
if (client->height <= 0 || client->height > 200) client->height = 24;
|
|
||||||
|
|
||||||
/* Re-render screen with new dimensions */
|
|
||||||
if (client->connected) {
|
|
||||||
tui_render_screen(client);
|
|
||||||
}
|
}
|
||||||
|
continue;
|
||||||
|
|
||||||
|
} else if (ssh_message_subtype(message) == SSH_CHANNEL_REQUEST_WINDOW_CHANGE) {
|
||||||
|
/* Handle terminal resize - this should be handled during session, not here */
|
||||||
|
/* For now, just acknowledge and ignore during init */
|
||||||
|
ssh_message_channel_request_reply_success(message);
|
||||||
ssh_message_free(message);
|
ssh_message_free(message);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -540,9 +626,9 @@ static int handle_pty_request(ssh_channel channel, client_t *client) {
|
||||||
|
|
||||||
ssh_message_reply_default(message);
|
ssh_message_reply_default(message);
|
||||||
ssh_message_free(message);
|
ssh_message_free(message);
|
||||||
} while (1);
|
} while (!pty_received || !shell_received);
|
||||||
|
|
||||||
return -1;
|
return (pty_received && shell_received) ? 0 : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Initialize SSH server */
|
/* Initialize SSH server */
|
||||||
|
|
@ -635,6 +721,8 @@ int ssh_server_start(int unused) {
|
||||||
client->session = session;
|
client->session = session;
|
||||||
client->channel = channel;
|
client->channel = channel;
|
||||||
client->fd = -1; /* Not used with SSH */
|
client->fd = -1; /* Not used with SSH */
|
||||||
|
client->ref_count = 1; /* Initial reference */
|
||||||
|
pthread_mutex_init(&client->ref_lock, NULL);
|
||||||
|
|
||||||
/* Handle PTY request and get terminal size */
|
/* Handle PTY request and get terminal size */
|
||||||
if (handle_pty_request(channel, client) < 0) {
|
if (handle_pty_request(channel, client) < 0) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue