Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later

TOOLS = host-run host-multiview host-shell host-path host-reveal host-notify host-edit host-clip host-info host-download host-env
TEST_BINS = tests/test_host_common.out tests/test_host_command_builders.out tests/test_host_edit_command.out tests/test_host_download_command.out
TEST_BINS = tests/test_host_common.out tests/test_host_command_builders.out tests/test_host_edit_command.out tests/test_host_download_command.out tests/test_host_terminal_filter.out
TEST_SCRIPTS = tests/test_package_layout.sh tests/test_ahi_driver_source.sh
TESTS = $(TEST_BINS) $(TEST_SCRIPTS)
COMMON_HEADERS = src/host_common.h src/host_path.h src/host_capture.h src/host_base64.h src/host_clip_command.h src/host_download_command.h src/host_edit_command.h src/host_env_command.h src/host_info_command.h src/host_notify_command.h src/host_powershell.h src/host_reveal_command.h src/host_shell_command.h src/uae_pragmas.h
Expand Down Expand Up @@ -74,7 +74,7 @@ host-run: src/host-run.c $(COMMON_HEADERS)
host-multiview: src/host-multiview.c $(COMMON_HEADERS)
$(CC) $(CFLAGS) $(VERFLAGS) $(INCLUDES) src/host-multiview.c -o $@

host-shell: src/host-shell.c $(COMMON_HEADERS)
host-shell: src/host-shell.c $(COMMON_HEADERS) src/host_terminal_filter.h
$(CC) $(CFLAGS) $(VERFLAGS) $(INCLUDES) src/host-shell.c -o $@

host-path: src/host-path.c $(COMMON_HEADERS)
Expand Down Expand Up @@ -113,6 +113,9 @@ tests/test_host_edit_command.out: tests/test_host_edit_command.c src/host_edit_c
tests/test_host_download_command.out: tests/test_host_download_command.c src/host_download_command.h src/host_base64.h src/host_common.h
$(HOST_CC) $(HOST_NATIVE_FLAGS) $(HOST_CFLAGS) tests/test_host_download_command.c -o $@

tests/test_host_terminal_filter.out: tests/test_host_terminal_filter.c src/host_terminal_filter.h
$(HOST_CC) $(HOST_NATIVE_FLAGS) $(HOST_CFLAGS) tests/test_host_terminal_filter.c -o $@

debug: CFLAGS += -DDEBUG -g
debug: clean all

Expand Down
43 changes: 15 additions & 28 deletions src/host-shell.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "host_capture.h"
#include "host_common.h"
#include "host_shell_command.h"
#include "host_terminal_filter.h"

#define OUTBUFSIZE 4095

Expand Down Expand Up @@ -128,8 +129,8 @@ int main(int argc, char *argv[])
BPTR in = 0;
BPTR out = 0;
long handle = 0;
BOOL esc_pending = FALSE;
BOOL raw_mode = FALSE;
struct host_terminal_filter terminal_filter = { HOST_TERMINAL_TEXT, 0 };
long actual;
ULONG status;
int status_supported = 0;
Expand Down Expand Up @@ -222,30 +223,13 @@ int main(int argc, char *argv[])
actual = HostShell_Read(handle, (UBYTE *)buffer, sizeof(buffer) - 2);
if (actual > 0)
{
int outptr = 0;
for (int i = 0; i < actual; i++) {
unsigned char c = (unsigned char)buffer[i];
if (esc_pending) {
if (c == 0x5B) { // '['
outbuf[outptr++] = 0x9B; // CSI
} else {
outbuf[outptr++] = 0x1B; // Original ESC
outbuf[outptr++] = c;
}
esc_pending = FALSE;
} else {
if (c == 0x1B) {
esc_pending = TRUE;
} else {
outbuf[outptr++] = c;
}
}

// Safety check for outbuf overflow (should rarely happen given the math)
if (outptr >= OUTBUFSIZE) {
Write(out, outbuf, outptr);
outptr = 0;
}
int outptr = host_terminal_filter_process(&terminal_filter,
(const unsigned char *)buffer, actual,
(unsigned char *)outbuf, sizeof(outbuf));
if (outptr < 0) {
printf("Failed to translate host terminal output.\n");
return_code = HOST_RETURN_ERROR;
goto cleanup;
}
if (outptr > 0) {
Write(out, outbuf, outptr);
Expand Down Expand Up @@ -318,9 +302,12 @@ int main(int argc, char *argv[])
}

cleanup:
if (esc_pending && out != 0) {
outbuf[0] = 0x1B;
Write(out, outbuf, 1);
if (out != 0) {
int outptr = host_terminal_filter_finish(&terminal_filter,
(unsigned char *)outbuf, sizeof(outbuf));
if (outptr > 0) {
Write(out, outbuf, outptr);
}
}
if (handle != 0) {
HostShell_Close(handle);
Expand Down
159 changes: 159 additions & 0 deletions src/host_terminal_filter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*
* SPDX-FileCopyrightText: 2020-2026 Dimitris Panokostas
* SPDX-License-Identifier: GPL-3.0-or-later
*/

#ifndef HOST_TERMINAL_FILTER_H
#define HOST_TERMINAL_FILTER_H

#define HOST_TERMINAL_TEXT 0
#define HOST_TERMINAL_ESCAPE 1
#define HOST_TERMINAL_OSC 2
#define HOST_TERMINAL_OSC_ESCAPE 3

struct host_terminal_filter
{
int state;
int utf8_remaining;
};

static inline int host_terminal_filter_utf8_continuations(unsigned char value)
{
if (value >= 0xC2 && value <= 0xDF) {
return 1;
}
if (value >= 0xE0 && value <= 0xEF) {
return 2;
}
if (value >= 0xF0 && value <= 0xF4) {
return 3;
}
return 0;
}

static inline int host_terminal_filter_put(unsigned char *output, int output_size,
int *output_len, unsigned char value)
{
if (*output_len >= output_size) {
return 0;
}

output[(*output_len)++] = value;
return 1;
}

/*
* Convert host ANSI CSI sequences to the Amiga console's single-byte CSI and
* discard OSC sequences, which the Amiga console does not support. The state
* is retained so escape sequences may span HostShell_Read() calls.
*
* The output can be at most input_len + 1 bytes when a pending ESC from the
* previous call turns out to be literal.
*/
static inline int host_terminal_filter_process(struct host_terminal_filter *filter,
const unsigned char *input, int input_len,
unsigned char *output, int output_size)
{
int output_len = 0;

if (filter == NULL || input == NULL || input_len < 0 ||
output == NULL || output_size < 0) {
return -1;
}

for (int i = 0; i < input_len; i++) {
unsigned char c = input[i];

switch (filter->state) {
case HOST_TERMINAL_TEXT:
if (filter->utf8_remaining > 0 && c >= 0x80 && c <= 0xBF) {
filter->utf8_remaining--;
if (!host_terminal_filter_put(output, output_size, &output_len, c)) {
return -1;
}
} else {
filter->utf8_remaining = 0;
if (c == 0x1B) {
filter->state = HOST_TERMINAL_ESCAPE;
} else if (c == 0x9D) {
filter->state = HOST_TERMINAL_OSC;
} else {
filter->utf8_remaining = host_terminal_filter_utf8_continuations(c);
if (!host_terminal_filter_put(output, output_size, &output_len, c)) {
return -1;
}
}
}
break;

case HOST_TERMINAL_ESCAPE:
if (c == '[') {
if (!host_terminal_filter_put(output, output_size, &output_len, 0x9B)) {
return -1;
}
filter->state = HOST_TERMINAL_TEXT;
} else if (c == ']') {
filter->state = HOST_TERMINAL_OSC;
filter->utf8_remaining = 0;
} else {
if (!host_terminal_filter_put(output, output_size, &output_len, 0x1B) ||
!host_terminal_filter_put(output, output_size, &output_len, c)) {
return -1;
}
filter->state = HOST_TERMINAL_TEXT;
filter->utf8_remaining = host_terminal_filter_utf8_continuations(c);
}
break;

case HOST_TERMINAL_OSC:
if (filter->utf8_remaining > 0 && c >= 0x80 && c <= 0xBF) {
filter->utf8_remaining--;
} else if (c == 0x07 || c == 0x9C) {
filter->state = HOST_TERMINAL_TEXT;
filter->utf8_remaining = 0;
} else if (c == 0x1B) {
filter->state = HOST_TERMINAL_OSC_ESCAPE;
Comment thread
midwan marked this conversation as resolved.
filter->utf8_remaining = 0;
} else {
filter->utf8_remaining = host_terminal_filter_utf8_continuations(c);
}
break;

case HOST_TERMINAL_OSC_ESCAPE:
if (c == '\\' || c == 0x07 || c == 0x9C) {
filter->state = HOST_TERMINAL_TEXT;
filter->utf8_remaining = 0;
} else if (c != 0x1B) {
filter->state = HOST_TERMINAL_OSC;
filter->utf8_remaining = host_terminal_filter_utf8_continuations(c);
}
break;

default:
return -1;
}
}

return output_len;
}

static inline int host_terminal_filter_finish(struct host_terminal_filter *filter,
unsigned char *output, int output_size)
{
int output_len = 0;

if (filter == NULL || output == NULL || output_size < 0) {
return -1;
}

if (filter->state == HOST_TERMINAL_ESCAPE &&
!host_terminal_filter_put(output, output_size, &output_len, 0x1B)) {
return -1;
}

filter->state = HOST_TERMINAL_TEXT;
filter->utf8_remaining = 0;
return output_len;
}

#endif
Loading