summaryrefslogtreecommitdiff
path: root/stacks/terminal/max25_terminal.c
diff options
context:
space:
mode:
authorinfo@mode42.com <info@mode42.com>2026-08-07 18:25:13 +0000
committerinfo@mode42.com <info@mode42.com>2026-08-07 18:25:13 +0000
commit04d965d67a7264a1c7c211494aebda1953df7603 (patch)
tree0ebd700a6e219f84a26a656f4bee8778bc75da7b /stacks/terminal/max25_terminal.c
Initial push
Diffstat (limited to 'stacks/terminal/max25_terminal.c')
-rw-r--r--stacks/terminal/max25_terminal.c767
1 files changed, 767 insertions, 0 deletions
diff --git a/stacks/terminal/max25_terminal.c b/stacks/terminal/max25_terminal.c
new file mode 100644
index 0000000..8b4eb52
--- /dev/null
+++ b/stacks/terminal/max25_terminal.c
@@ -0,0 +1,767 @@
+#define _POSIX_C_SOURCE 200809L
+
+#include "max25_proto.h"
+#include "max25_ui.h"
+
+#include <errno.h>
+#include <getopt.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+
+#ifdef MAX25_HAVE_NCURSES
+#include <ncurses.h>
+#endif
+
+typedef struct {
+ char host[256];
+ unsigned port;
+ char unix_path[256];
+ char tcp_password[128];
+ char default_device[32];
+ int tcp_only;
+ int ax25_ui;
+ int verbose;
+ int probe;
+} terminal_config_t;
+
+typedef struct {
+ int waiting_reply;
+ char last_err[MAX25_LINE_MAX];
+} session_io_t;
+
+static struct termios g_saved_tty;
+static int g_tty_raw;
+
+static void config_defaults(terminal_config_t *cfg)
+{
+ const char *env;
+
+ memset(cfg, 0, sizeof(*cfg));
+ strncpy(cfg->host, "127.0.0.1", sizeof(cfg->host) - 1);
+ cfg->port = MAX25_DEFAULT_PORT;
+ strncpy(cfg->unix_path, MAX25_DEFAULT_UNIX, sizeof(cfg->unix_path) - 1);
+ cfg->ax25_ui = 1;
+
+ env = getenv("MAX25_HOST");
+ if (env != NULL && env[0] != '\0') {
+ strncpy(cfg->host, env, sizeof(cfg->host) - 1);
+ }
+ env = getenv("MAX25_PORT");
+ if (env != NULL && env[0] != '\0') {
+ cfg->port = (unsigned)strtoul(env, NULL, 10);
+ }
+ env = getenv("MAX25_UNIX");
+ if (env != NULL) {
+ strncpy(cfg->unix_path, env, sizeof(cfg->unix_path) - 1);
+ }
+ env = getenv("MAX25_TCP_PASSWORD");
+ if (env != NULL) {
+ strncpy(cfg->tcp_password, env, sizeof(cfg->tcp_password) - 1);
+ }
+}
+
+static void usage(const char *prog)
+{
+ fprintf(stderr,
+ "MAX25 Terminal — CLI operator client (max25d via TCP/M25/1)\n\n"
+ "Usage: %s [options] [host] [port]\n\n"
+ "Options:\n"
+ " -H, --host HOST max25d TCP host (default 127.0.0.1)\n"
+ " -p, --port PORT max25d TCP port (default %u)\n"
+ " -T, --tcp-only TCP only — do not try Unix socket\n"
+ " -U, --unix PATH Unix socket (default %s, before TCP)\n"
+ " --ax25-ui AX.25 UI mode (default on)\n"
+ " --no-ax25-ui Plain SEND lines\n"
+ " -P, --password PASS TCP auth password (plain-text M25/1 AUTH)\n"
+ " -d, --device ID SET DEVICE after connect (configured device id)\n"
+ " -v, --verbose Protocol log on stderr\n"
+ " --probe Connect, print STATUS, exit (CI)\n"
+ " -h, --help Show help\n\n"
+ "Remote example:\n"
+ " %s -T -H linux-host.local -p 7325\n\n"
+ "Environment: MAX25_HOST, MAX25_PORT, MAX25_UNIX, MAX25_TCP_PASSWORD\n"
+ "Symlink: max25-client -> max25-terminal\n\n",
+ prog, MAX25_DEFAULT_PORT, MAX25_DEFAULT_UNIX, prog);
+}
+
+static int tty_enable_raw(void)
+{
+ struct termios tty;
+
+ if (!isatty(STDIN_FILENO)) {
+ return 0;
+ }
+ if (tcgetattr(STDIN_FILENO, &g_saved_tty) != 0) {
+ return -1;
+ }
+ tty = g_saved_tty;
+ tty.c_lflag &= (tcflag_t)~(ICANON | ECHO);
+ tty.c_cc[VMIN] = 0;
+ tty.c_cc[VTIME] = 1;
+ if (tcsetattr(STDIN_FILENO, TCSANOW, &tty) != 0) {
+ return -1;
+ }
+ g_tty_raw = 1;
+ return 0;
+}
+
+static void tty_restore(void)
+{
+ if (g_tty_raw) {
+ tcsetattr(STDIN_FILENO, TCSANOW, &g_saved_tty);
+ g_tty_raw = 0;
+ }
+}
+
+static int begin_cmd(session_io_t *io)
+{
+ if (io == NULL) {
+ return -1;
+ }
+ io->waiting_reply = 1;
+ io->last_err[0] = '\0';
+ return 0;
+}
+
+static int send_cmd_async(max25_client_t *client, session_io_t *io,
+ const char *cmd)
+{
+ if (max25_client_write(client, cmd) != 0) {
+ return -1;
+ }
+ return begin_cmd(io);
+}
+
+static int dispatch_daemon_line(const char *line, session_io_t *io,
+ max25_ui_t *ui, max25_status_t *status,
+ terminal_config_t *cfg, int *need_redraw)
+{
+ max25_line_kind_t kind = max25_client_classify_line(line);
+
+ if (need_redraw != NULL) {
+ *need_redraw = 0;
+ }
+
+ if (cfg->verbose) {
+ fprintf(stderr, "max25-terminal: << %s\n", line);
+ }
+
+ if (kind == MAX25_LINE_RX) {
+ max25_ui_append_rx(ui, line + 3);
+ if (need_redraw != NULL) {
+ *need_redraw = 1;
+ }
+ return 0;
+ }
+
+ /* CONNECT/DISCONNECT emit EVENT before OK — update header even while waiting. */
+ if (kind == MAX25_LINE_EVENT) {
+ if (max25_client_apply_event(line, status)) {
+ max25_ui_append_rx(ui, line);
+ if (need_redraw != NULL) {
+ *need_redraw = 1;
+ }
+ } else if (cfg->verbose) {
+ max25_ui_append_rx(ui, line);
+ if (need_redraw != NULL) {
+ *need_redraw = 1;
+ }
+ }
+ if (io->waiting_reply) {
+ return 0;
+ }
+ return 0;
+ }
+
+ if (io->waiting_reply) {
+ if (kind == MAX25_LINE_STATUS && status != NULL) {
+ char summary[MAX25_LINE_MAX];
+
+ max25_client_parse_status(line, status);
+ /* Show STATUS in RX pane (F10→3 and other GET STATUS callers). */
+ snprintf(summary, sizeof(summary),
+ "STATUS device=%s stack=%s connected=%s callerid=%s callid=%s",
+ status->device[0] != '\0' ? status->device : "-",
+ status->stack[0] != '\0' ? status->stack : "-",
+ status->connected ? "yes" : "no",
+ status->callerid[0] != '\0' ? status->callerid : "-",
+ status->callid[0] != '\0' ? status->callid : "-");
+ max25_ui_append_rx(ui, summary);
+ if (need_redraw != NULL) {
+ *need_redraw = 1;
+ }
+ return 0;
+ }
+ if (kind == MAX25_LINE_DEVICE) {
+ max25_ui_append_rx(ui, line);
+ if (need_redraw != NULL) {
+ *need_redraw = 1;
+ }
+ return 0;
+ }
+ if (kind == MAX25_LINE_OK) {
+ io->waiting_reply = 0;
+ if (need_redraw != NULL) {
+ *need_redraw = 1;
+ }
+ return 0;
+ }
+ if (kind == MAX25_LINE_ERR) {
+ strncpy(io->last_err, line, sizeof(io->last_err) - 1);
+ io->last_err[sizeof(io->last_err) - 1] = '\0';
+ io->waiting_reply = 0;
+ max25_ui_append_rx(ui, line);
+ if (need_redraw != NULL) {
+ *need_redraw = 1;
+ }
+ return -1;
+ }
+ return 0;
+ }
+
+ if (kind == MAX25_LINE_ERR) {
+ max25_ui_append_rx(ui, line);
+ if (need_redraw != NULL) {
+ *need_redraw = 1;
+ }
+ }
+ return 0;
+}
+
+
+static int send_callsign(max25_client_t *client, session_io_t *io,
+ const char *field, const char *value)
+{
+ char cmd[MAX25_LINE_MAX];
+
+ if (!max25_valid_callsign(value)) {
+ fprintf(stderr, "max25-terminal: invalid %s\n", field);
+ return -1;
+ }
+ snprintf(cmd, sizeof(cmd), "SET %s %s", field, value);
+ return send_cmd_async(client, io, cmd);
+}
+
+static int send_line_async(max25_client_t *client, session_io_t *io,
+ const char *line)
+{
+ char cmd[MAX25_LINE_MAX + 8];
+ size_t max_payload = sizeof(cmd) - 6;
+
+ if (strlen(line) > max_payload) {
+ return -1;
+ }
+ snprintf(cmd, sizeof(cmd), "SEND %s", line);
+ return send_cmd_async(client, io, cmd);
+}
+
+static int refresh_status(max25_client_t *client, max25_status_t *status)
+{
+ char reply[MAX25_LINE_MAX];
+
+ if (max25_client_command(client, "GET STATUS", reply, sizeof(reply)) != 0) {
+ return -1;
+ }
+ return max25_client_parse_status(reply, status);
+}
+
+static int set_device_sync(max25_client_t *client, max25_status_t *status,
+ const char *device_id)
+{
+ char cmd[MAX25_LINE_MAX + 16];
+ char reply[MAX25_LINE_MAX];
+
+ if (device_id == NULL || device_id[0] == '\0') {
+ return -1;
+ }
+ snprintf(cmd, sizeof(cmd), "SET DEVICE %s", device_id);
+ if (max25_client_command(client, cmd, reply, sizeof(reply)) != 0) {
+ return -1;
+ }
+ return refresh_status(client, status);
+}
+
+static int fetch_devices(max25_client_t *client, max25_ui_t *ui)
+{
+ char line[MAX25_LINE_MAX];
+
+ if (max25_client_write(client, "GET DEVICES") != 0) {
+ return -1;
+ }
+ max25_ui_append_rx(ui, "-- max25d devices --");
+ for (;;) {
+ if (max25_client_read_line(client, line, sizeof(line)) != 0) {
+ return -1;
+ }
+ if (strcmp(line, "OK") == 0) {
+ break;
+ }
+ if (strncmp(line, "DEVICE ", 7) == 0) {
+ max25_ui_append_rx(ui, line);
+ continue;
+ }
+ if (strncmp(line, "ERR ", 4) == 0) {
+ max25_ui_append_rx(ui, line);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static int menu_action_needs_prompt(max25_menu_action_t action)
+{
+ return action == MAX25_MENU_CALLERID || action == MAX25_MENU_CALLID ||
+ action == MAX25_MENU_SEND || action == MAX25_MENU_DEVICE;
+}
+
+/* Returns 1 to quit session, 0 otherwise. Hides menu before action. */
+static int apply_menu_pick(max25_menu_action_t action, max25_client_t *client,
+ session_io_t *io, max25_ui_t *ui,
+ max25_status_t *status, const char *input_line)
+{
+ char buf[MAX25_LINE_MAX];
+ char cmd[64];
+
+ if (action == MAX25_MENU_NONE) {
+ return 0;
+ }
+ if (action == MAX25_MENU_QUIT) {
+ return 1;
+ }
+
+ /* Leave overlay so prompts / STATUS RX / header redraw are visible. */
+ max25_ui_hide_menu(ui);
+ max25_ui_draw_screen(ui, status, input_line != NULL ? input_line : "");
+
+ switch (action) {
+ case MAX25_MENU_CALLERID:
+ if (max25_ui_prompt(ui, "CALLERID", buf, sizeof(buf)) > 0) {
+ send_callsign(client, io, "CALLERID", buf);
+ }
+ break;
+ case MAX25_MENU_CALLID:
+ if (max25_ui_prompt(ui, "CALLID", buf, sizeof(buf)) > 0) {
+ send_callsign(client, io, "CALLID", buf);
+ }
+ break;
+ case MAX25_MENU_STATUS:
+ send_cmd_async(client, io, "GET STATUS");
+ break;
+ case MAX25_MENU_SEND:
+ if (max25_ui_prompt(ui, "Send", buf, sizeof(buf)) > 0) {
+ send_line_async(client, io, buf);
+ }
+ break;
+ case MAX25_MENU_MONITOR:
+ snprintf(cmd, sizeof(cmd), "MONITOR %s",
+ status->monitor ? "off" : "on");
+ status->monitor = !status->monitor;
+ send_cmd_async(client, io, cmd);
+ max25_ui_append_rx(ui, status->monitor ? "MONITOR on" : "MONITOR off");
+ max25_ui_draw_screen(ui, status, input_line != NULL ? input_line : "");
+ break;
+ case MAX25_MENU_CONNECT:
+ send_cmd_async(client, io,
+ status->connected ? "DISCONNECT" : "CONNECT");
+ break;
+ case MAX25_MENU_DEVICE:
+ (void)fetch_devices(client, ui);
+ if (max25_ui_prompt(ui, "DEVICE id", buf, sizeof(buf)) > 0) {
+ if (set_device_sync(client, status, buf) != 0) {
+ max25_ui_append_rx(ui, "ERR SET DEVICE failed");
+ }
+ max25_ui_draw_screen(ui, status,
+ input_line != NULL ? input_line : "");
+ }
+ break;
+ case MAX25_MENU_DEVICES:
+ (void)fetch_devices(client, ui);
+ max25_ui_draw_screen(ui, status, input_line != NULL ? input_line : "");
+ break;
+ default:
+ break;
+ }
+
+ if (!menu_action_needs_prompt(action) && action != MAX25_MENU_MONITOR) {
+ max25_ui_draw_screen(ui, status, input_line != NULL ? input_line : "");
+ }
+ return 0;
+}
+
+#ifdef MAX25_HAVE_NCURSES
+static int read_key_ncurses(void)
+{
+ int ch = getch();
+
+ if (ch == KEY_F(10)) {
+ return 999;
+ }
+ return ch;
+}
+#endif
+
+static int read_plain_key(void)
+{
+ unsigned char buf[8];
+ ssize_t n;
+
+ n = read(STDIN_FILENO, buf, sizeof(buf));
+ if (n <= 0) {
+ if (n < 0 && errno == EAGAIN) {
+ return -1;
+ }
+ return 0;
+ }
+
+ if (buf[0] == 0x1b && n >= 3 && buf[1] == '[') {
+ if (n >= 4 && buf[2] == '2' && buf[3] == '1' && (n < 5 || buf[4] == '~')) {
+ return 999;
+ }
+ }
+
+ return (int)buf[0];
+}
+
+static int run_session(max25_client_t *client, max25_ui_t *ui,
+ terminal_config_t *cfg, max25_status_t *status)
+{
+ struct pollfd pfds[2];
+ session_io_t io;
+ char line[MAX25_LINE_MAX];
+ char input[MAX25_UI_INPUT_MAX];
+ size_t input_len = 0;
+ int running = 1;
+ int use_ncurses = max25_ui_has_ncurses(ui);
+ int use_plain_raw = 0;
+
+ memset(&io, 0, sizeof(io));
+ input[0] = '\0';
+
+ if (!use_ncurses) {
+ max25_ui_apply_palette();
+ }
+ max25_ui_reset_rx(ui);
+ max25_ui_draw_screen(ui, status, input);
+
+ /* Arm TX session before accepting Enter/F10 SEND (daemon also auto-arms
+ * on SEND; sync CONNECT here so header connected=yes before first key). */
+ if (!status->connected) {
+ char creply[MAX25_LINE_MAX];
+
+ if (max25_client_command(client, "CONNECT", creply, sizeof(creply)) == 0) {
+ status->connected = 1;
+ max25_ui_append_rx(ui, "EVENT connected");
+ } else {
+ max25_ui_append_rx(ui, creply[0] ? creply : "ERR CONNECT failed");
+ }
+ max25_ui_draw_screen(ui, status, input);
+ }
+
+ (void)fetch_devices(client, ui);
+ max25_ui_draw_screen(ui, status, input);
+
+ if (!use_ncurses && isatty(STDIN_FILENO)) {
+ use_plain_raw = (tty_enable_raw() == 0);
+ }
+
+ while (running) {
+ if (!io.waiting_reply) {
+ max25_ui_draw_screen(ui, status, input);
+ }
+
+ pfds[0].fd = STDIN_FILENO;
+ pfds[0].events = POLLIN;
+ pfds[0].revents = 0;
+ pfds[1].fd = max25_client_fd(client);
+ pfds[1].events = POLLIN;
+ pfds[1].revents = 0;
+
+ if (poll(pfds, 2, 200) < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ break;
+ }
+
+ if ((pfds[1].revents & (POLLERR | POLLHUP | POLLNVAL)) != 0) {
+ max25_ui_append_rx(ui, "max25d connection lost");
+ break;
+ }
+
+ if ((pfds[1].revents & POLLIN) != 0) {
+ int need_redraw = 0;
+
+ if (max25_client_read_line(client, line, sizeof(line)) != 0) {
+ max25_ui_append_rx(ui, "max25d connection closed");
+ break;
+ }
+ dispatch_daemon_line(line, &io, ui, status, cfg, &need_redraw);
+ if (io.last_err[0] != '\0' && cfg->verbose) {
+ fprintf(stderr, "max25-terminal: %s\n", io.last_err);
+ }
+ if (need_redraw && !max25_ui_menu_visible(ui)) {
+ max25_ui_draw_screen(ui, status, input);
+ }
+ continue;
+ }
+
+ if ((pfds[0].revents & POLLIN) == 0) {
+ continue;
+ }
+
+#ifdef MAX25_HAVE_NCURSES
+ if (use_ncurses) {
+ int ch = read_key_ncurses();
+ if (ch == -1) {
+ continue;
+ }
+ if (ch == 999) {
+ if (max25_ui_menu_visible(ui)) {
+ max25_ui_hide_menu(ui);
+ max25_ui_draw_screen(ui, status, input);
+ } else {
+ max25_ui_show_menu(ui, status);
+ }
+ continue;
+ }
+ if (max25_ui_menu_visible(ui)) {
+ if (ch == 27) {
+ max25_ui_hide_menu(ui);
+ max25_ui_draw_screen(ui, status, input);
+ continue;
+ }
+ if (apply_menu_pick(max25_ui_menu_pick(ui, ch), client, &io, ui,
+ status, input) != 0) {
+ running = 0;
+ }
+ continue;
+ }
+ if (ch >= 32 && ch < 127) {
+ if (input_len + 1 < sizeof(input)) {
+ input[input_len++] = (char)ch;
+ input[input_len] = '\0';
+ }
+ } else if (ch == '\n' || ch == KEY_ENTER || ch == '\r') {
+ if (input_len > 0) {
+ send_line_async(client, &io, input);
+ input_len = 0;
+ input[0] = '\0';
+ }
+ } else if (ch == KEY_BACKSPACE || ch == 127 || ch == '\b') {
+ if (input_len > 0) {
+ input[--input_len] = '\0';
+ }
+ }
+ continue;
+ }
+#endif
+
+ if (use_plain_raw) {
+ int ch = read_plain_key();
+ if (ch <= 0) {
+ continue;
+ }
+ if (ch == 999) {
+ if (max25_ui_menu_visible(ui)) {
+ max25_ui_hide_menu(ui);
+ max25_ui_draw_screen(ui, status, input);
+ } else {
+ max25_ui_show_menu(ui, status);
+ }
+ continue;
+ }
+ if (max25_ui_menu_visible(ui)) {
+ if (ch == 27) {
+ max25_ui_hide_menu(ui);
+ max25_ui_draw_screen(ui, status, input);
+ continue;
+ }
+ if (apply_menu_pick(max25_ui_menu_pick(ui, ch), client, &io, ui,
+ status, input) != 0) {
+ running = 0;
+ }
+ continue;
+ }
+ if (ch >= 32 && ch < 127) {
+ if (input_len + 1 < sizeof(input)) {
+ input[input_len++] = (char)ch;
+ input[input_len] = '\0';
+ }
+ } else if (ch == '\n' || ch == '\r') {
+ if (input_len > 0) {
+ send_line_async(client, &io, input);
+ input_len = 0;
+ input[0] = '\0';
+ }
+ } else if (ch == 127 || ch == '\b') {
+ if (input_len > 0) {
+ input[--input_len] = '\0';
+ }
+ }
+ continue;
+ }
+
+ if (fgets(input, sizeof(input), stdin) == NULL) {
+ running = 0;
+ } else {
+ input[strcspn(input, "\r\n")] = '\0';
+ if (max25_ui_menu_visible(ui)) {
+ if (apply_menu_pick(max25_ui_menu_pick(ui, input[0]), client,
+ &io, ui, status, "") != 0) {
+ running = 0;
+ }
+ } else if (input[0] != '\0') {
+ send_line_async(client, &io, input);
+ }
+ input[0] = '\0';
+ input_len = 0;
+ }
+ }
+
+ tty_restore();
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ terminal_config_t cfg;
+ max25_client_t *client = NULL;
+ max25_ui_t *ui = NULL;
+ max25_status_t status;
+ char reply[MAX25_LINE_MAX];
+ int opt;
+ int argi;
+ static const struct option long_opts[] = {
+ {"host", required_argument, NULL, 'H'},
+ {"port", required_argument, NULL, 'p'},
+ {"unix", required_argument, NULL, 'U'},
+ {"tcp-only", no_argument, NULL, 'T'},
+ {"ax25-ui", no_argument, NULL, 1000},
+ {"no-ax25-ui", no_argument, NULL, 1001},
+ {"password", required_argument, NULL, 'P'},
+ {"device", required_argument, NULL, 'd'},
+ {"verbose", no_argument, NULL, 'v'},
+ {"probe", no_argument, NULL, 1002},
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+
+ config_defaults(&cfg);
+
+ while ((opt = getopt_long(argc, argv, "H:p:TU:P:d:v:h", long_opts, NULL)) != -1) {
+ switch (opt) {
+ case 'H':
+ strncpy(cfg.host, optarg, sizeof(cfg.host) - 1);
+ break;
+ case 'p':
+ cfg.port = (unsigned)strtoul(optarg, NULL, 10);
+ break;
+ case 'T':
+ cfg.tcp_only = 1;
+ break;
+ case 'U':
+ strncpy(cfg.unix_path, optarg, sizeof(cfg.unix_path) - 1);
+ break;
+ case 'P':
+ strncpy(cfg.tcp_password, optarg, sizeof(cfg.tcp_password) - 1);
+ break;
+ case 'd':
+ strncpy(cfg.default_device, optarg, sizeof(cfg.default_device) - 1);
+ break;
+ case 'v':
+ cfg.verbose = 1;
+ break;
+ case 1002:
+ cfg.probe = 1;
+ cfg.tcp_only = 1;
+ break;
+ case 'h':
+ usage(argv[0]);
+ return EXIT_SUCCESS;
+ case 1000:
+ cfg.ax25_ui = 1;
+ break;
+ case 1001:
+ cfg.ax25_ui = 0;
+ break;
+ default:
+ usage(argv[0]);
+ return EXIT_FAILURE;
+ }
+ }
+
+ argi = optind;
+ if (argi < argc) {
+ strncpy(cfg.host, argv[argi++], sizeof(cfg.host) - 1);
+ }
+ if (argi < argc) {
+ cfg.port = (unsigned)strtoul(argv[argi++], NULL, 10);
+ }
+
+ if (max25_client_connect(cfg.host, cfg.port,
+ cfg.tcp_only ? "" : cfg.unix_path,
+ cfg.tcp_only, cfg.tcp_password,
+ &client, &status) != 0) {
+ fprintf(stderr, "max25-terminal: connect failed");
+ if (cfg.tcp_only) {
+ fprintf(stderr, " (tcp %s:%u)", cfg.host, cfg.port);
+ } else {
+ fprintf(stderr, " (unix %s / tcp %s:%u)",
+ cfg.unix_path, cfg.host, cfg.port);
+ }
+ fputc('\n', stderr);
+ return EXIT_FAILURE;
+ }
+
+ if (cfg.probe) {
+ printf("STATUS hardware=%s device=%s mode=%s callerid=%s callid=%s "
+ "ax25_ui=%s connected=%s stack=%s\n",
+ status.hardware, status.device, status.mode,
+ status.callerid, status.callid,
+ status.ax25_ui ? "on" : "off",
+ status.connected ? "yes" : "no",
+ status.stack);
+ max25_client_write(client, "DISCONNECT");
+ max25_client_free(client);
+ return EXIT_SUCCESS;
+ }
+
+ if (max25_ui_init(&ui) != 0) {
+ max25_client_free(client);
+ return EXIT_FAILURE;
+ }
+
+ if (cfg.default_device[0] != '\0') {
+ if (set_device_sync(client, &status, cfg.default_device) != 0) {
+ fprintf(stderr, "max25-terminal: SET DEVICE %s failed\n",
+ cfg.default_device);
+ max25_client_free(client);
+ return EXIT_FAILURE;
+ }
+ }
+
+ if (cfg.ax25_ui) {
+ max25_client_command(client, "SET AX25_UI on", reply, sizeof(reply));
+ status.ax25_ui = 1;
+ } else {
+ max25_client_command(client, "SET AX25_UI off", reply, sizeof(reply));
+ status.ax25_ui = 0;
+ }
+
+ if (cfg.verbose) {
+ fprintf(stderr, "max25-terminal: connected %s:%u tcp_only=%s ax25-ui=%s\n",
+ cfg.host, cfg.port, cfg.tcp_only ? "yes" : "no",
+ status.ax25_ui ? "on" : "off");
+ }
+
+ run_session(client, ui, &cfg, &status);
+
+ max25_client_write(client, "DISCONNECT");
+ max25_ui_shutdown(ui);
+ max25_client_free(client);
+ return EXIT_SUCCESS;
+}
git clone -b <branch> https://cgit.mode42.com/<repo>.git
git clone -b <branch> git://cgit.mode42.com/<repo>.git

info@mode42.com