Skip to content
Snippets Groups Projects
term.c 116 KiB
Newer Older
deuce's avatar
deuce committed
/* Copyright (C), 2007 by Stephen Hurd */

/* $Id: term.c,v 1.387 2020/06/27 00:04:50 deuce Exp $ */
#include <assert.h>
deuce's avatar
deuce committed
#include <ciolib.h>
deuce's avatar
deuce committed
#include <cterm.h>
Deucе's avatar
Deucе committed
#include <genwrap.h>
Deucе's avatar
Deucе committed
#include <math.h>
Deucе's avatar
Deucе committed
#include <stdbool.h>
#include <string.h>
#include <vidmodes.h>
#include "conn.h"
Deucе's avatar
Deucе committed
#include "dirwrap.h"
#include "filepick.h"
Deucе's avatar
Deucе committed
#include "filewrap.h"
#include "gen_defs.h"
Deucе's avatar
Deucе committed
#include "saucedefs.h"
#include "sexyz.h"
#include "strwrap.h"
Deucе's avatar
Deucе committed
#include "syncterm.h"
Deucе's avatar
Deucе committed
#include "term.h"
#include "threadwrap.h"
#include "uifcinit.h"
#include "window.h"
#include "xmodem.h"
#include "xpbeep.h"
#include "xpendian.h"
#include "xpprintf.h"
Deucе's avatar
Deucе committed
#include "zmodem.h"
deuce's avatar
deuce committed
#ifndef WITHOUT_OOII
Deucе's avatar
Deucе committed
 #include "ooii.h"
deuce's avatar
deuce committed
#endif
#include "base64.h"
#include "md5.h"
deuce's avatar
deuce committed

Deucе's avatar
Deucе committed
#define ANSI_REPLY_BUFSIZE 2048
static char ansi_replybuf[2048];
deuce's avatar
deuce committed

Deucе's avatar
Deucе committed
 #define MIN(a, b) ((a) < (b) ? (a) : (b))
struct terminal   term;
struct cterminal *cterm;
Deucе's avatar
Deucе committed
#define TRANSFER_WIN_WIDTH 66
#define TRANSFER_WIN_HEIGHT 18
static struct vmem_cell winbuf[(TRANSFER_WIN_WIDTH + 2) * (TRANSFER_WIN_HEIGHT + 1) * 2]; /* Save buffer for transfer
                                                                                           * window */
static struct text_info trans_ti;
static struct text_info log_ti;
static struct ciolib_pixels *pixmap_buffer[2];
static struct ciolib_mask *mask_buffer;
static uint8_t pnm_gamma[256] = {
	0,  13, 22, 28, 34, 38, 42, 46, 50, 53, 56, 59, 61, 64, 66, 69,
	71, 73, 75, 77, 79, 81, 83, 85, 86, 88, 90, 92, 93, 95, 96, 98, 
	99, 101, 102, 104, 105, 106, 108, 109, 110, 112, 113, 114, 115, 
	117, 118, 119, 120, 121, 122, 124, 125, 126, 127, 128, 129, 130, 
	131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 
	144, 145, 146, 147, 148, 148, 149, 150, 151, 152, 153, 154, 155, 
	155, 156, 157, 158, 159, 159, 160, 161, 162, 163, 163, 164, 165, 
	166, 167, 167, 168, 169, 170, 170, 171, 172, 173, 173, 174, 175, 
	175, 176, 177, 178, 178, 179, 180, 180, 181, 182, 182, 183, 184, 
	185, 185, 186, 187, 187, 188, 189, 189, 190, 190, 191, 192, 192, 
	193, 194, 194, 195, 196, 196, 197, 197, 198, 199, 199, 200, 200, 
	201, 202, 202, 203, 203, 204, 205, 205, 206, 206, 207, 208, 208, 
	209, 209, 210, 210, 211, 212, 212, 213, 213, 214, 214, 215, 215, 
	216, 216, 217, 218, 218, 219, 219, 220, 220, 221, 221, 222, 222, 
	223, 223, 224, 224, 225, 226, 226, 227, 227, 228, 228, 229, 229, 
	230, 230, 231, 231, 232, 232, 233, 233, 234, 234, 235, 235, 236, 
	236, 237, 237, 238, 238, 238, 239, 239, 240, 240, 241, 241, 242, 
	242, 243, 243, 244, 244, 245, 245, 246, 246, 246, 247, 247, 248, 
	248, 249, 249, 250, 250, 251, 251, 251, 252, 252, 253, 253, 254, 
	254, 255, 255
};
uint8_t pnm_gamma_max = 255;
Deucе's avatar
Deucе committed
void
get_cterm_size(int *cols, int *rows, int ns)
Deucе's avatar
Deucе committed
	if (cterm != NULL) {
		*cols = cterm->width;
		*rows = cterm->height;
	}
		get_term_win_size(cols, rows, NULL, NULL, &ns);
	MM_X10 = 9,
	MM_NORMAL_TRACKING = 1000,
	MM_HIGHLIGHT_TRACKING = 1001,
	MM_BUTTON_EVENT_TRACKING = 1002,
	MM_ANY_EVENT_TRACKING = 1003
};

struct mouse_state {
Deucе's avatar
Deucе committed
	uint32_t         flags;

#define MS_FLAGS_SGR (1 << 0)
#define MS_SGR_SET (1006)
Deucе's avatar
Deucе committed
void
setup_mouse_events(struct mouse_state *ms)
{
	ciomouse_setevents(0);
Deucе's avatar
Deucе committed
		switch (ms->mode) {
			case MM_RIP:
				ciomouse_addevent(CIOLIB_BUTTON_1_PRESS);
				ciomouse_addevent(CIOLIB_BUTTON_1_RELEASE);
				ciomouse_addevent(CIOLIB_BUTTON_2_PRESS);
				ciomouse_addevent(CIOLIB_BUTTON_2_RELEASE);
				ciomouse_addevent(CIOLIB_BUTTON_3_PRESS);
				ciomouse_addevent(CIOLIB_BUTTON_3_RELEASE);
				ciomouse_addevent(CIOLIB_BUTTON_4_PRESS);
				mousepointer(CIOLIB_MOUSEPTR_ARROW);
				return;
			case MM_X10:
				ciomouse_addevent(CIOLIB_BUTTON_1_PRESS);
				ciomouse_addevent(CIOLIB_BUTTON_1_CLICK);
				ciomouse_addevent(CIOLIB_BUTTON_1_DRAG_START);
				ciomouse_addevent(CIOLIB_BUTTON_1_DRAG_MOVE);
				ciomouse_addevent(CIOLIB_BUTTON_1_DRAG_END);
				ciomouse_addevent(CIOLIB_BUTTON_2_CLICK);
				ciomouse_addevent(CIOLIB_BUTTON_3_CLICK);
				ciomouse_addevent(CIOLIB_BUTTON_4_PRESS); // For scrollback...
				mousepointer(CIOLIB_MOUSEPTR_ARROW);
				return;
			case MM_NORMAL_TRACKING:
				ciomouse_addevent(CIOLIB_BUTTON_1_PRESS);
				ciomouse_addevent(CIOLIB_BUTTON_1_RELEASE);
				ciomouse_addevent(CIOLIB_BUTTON_2_PRESS);
				ciomouse_addevent(CIOLIB_BUTTON_2_RELEASE);
				ciomouse_addevent(CIOLIB_BUTTON_3_PRESS);
				ciomouse_addevent(CIOLIB_BUTTON_3_RELEASE);
				ciomouse_addevent(CIOLIB_BUTTON_4_PRESS);
				ciomouse_addevent(CIOLIB_BUTTON_5_PRESS);
				mousepointer(CIOLIB_MOUSEPTR_ARROW);
				return;
			case MM_BUTTON_EVENT_TRACKING:
				ciomouse_addevent(CIOLIB_BUTTON_1_PRESS);
				ciomouse_addevent(CIOLIB_BUTTON_1_RELEASE);
				ciomouse_addevent(CIOLIB_BUTTON_2_PRESS);
				ciomouse_addevent(CIOLIB_BUTTON_2_RELEASE);
				ciomouse_addevent(CIOLIB_BUTTON_3_PRESS);
				ciomouse_addevent(CIOLIB_BUTTON_3_RELEASE);
				ciomouse_addevent(CIOLIB_BUTTON_4_PRESS);
				ciomouse_addevent(CIOLIB_BUTTON_5_PRESS);
				ciomouse_addevent(CIOLIB_MOUSE_MOVE);
				mousepointer(CIOLIB_MOUSEPTR_ARROW);
				return;
			case MM_ANY_EVENT_TRACKING:
				ciomouse_addevent(CIOLIB_BUTTON_1_PRESS);
				ciomouse_addevent(CIOLIB_BUTTON_1_RELEASE);
				ciomouse_addevent(CIOLIB_BUTTON_2_PRESS);
				ciomouse_addevent(CIOLIB_BUTTON_2_RELEASE);
				ciomouse_addevent(CIOLIB_BUTTON_3_PRESS);
				ciomouse_addevent(CIOLIB_BUTTON_3_RELEASE);
				ciomouse_addevent(CIOLIB_BUTTON_4_PRESS);
				ciomouse_addevent(CIOLIB_BUTTON_5_PRESS);
				ciomouse_addevent(CIOLIB_MOUSE_MOVE);
				mousepointer(CIOLIB_MOUSEPTR_ARROW);
	ciomouse_addevent(CIOLIB_BUTTON_1_DRAG_START);
	ciomouse_addevent(CIOLIB_BUTTON_1_DRAG_MOVE);
	ciomouse_addevent(CIOLIB_BUTTON_1_DRAG_END);
	ciomouse_addevent(CIOLIB_BUTTON_2_CLICK);
	ciomouse_addevent(CIOLIB_BUTTON_3_CLICK);
	ciomouse_addevent(CIOLIB_BUTTON_4_PRESS);
	mousepointer(CIOLIB_MOUSEPTR_BAR);
#if defined(__BORLANDC__)
Deucе's avatar
Deucе committed
 #pragma argsused
Deucе's avatar
Deucе committed
void
mousedrag(struct vmem_cell *scrollback)
	int                   key;
	struct mouse_event    mevent;
	struct vmem_cell     *screen;
	unsigned char        *tscreen;
	struct vmem_cell     *sbuffer;
	int                   sbufsize;
	int                   pos, startpos, endpos, lines;
	int                   outpos;
	char                 *copybuf = NULL;
	char                 *newcopybuf;
	int                   lastchar;
	struct ciolib_screen *savscrn;
Deucе's avatar
Deucе committed

	sbufsize = term.width * sizeof(*screen) * term.height;
	screen = malloc(sbufsize);
	sbuffer = malloc(sbufsize);
	tscreen = malloc(term.width * 2 * term.height);
	vmem_gettext(term.x - 1, term.y - 1, term.x + term.width - 2, term.y + term.height - 2, screen);
	gettext(term.x - 1, term.y - 1, term.x + term.width - 2, term.y + term.height - 2, tscreen);
	savscrn = savescreen();
	set_modepalette(palettes[COLOUR_PALETTE]);
Deucе's avatar
Deucе committed
	while (1) {
		key = getch();
		if ((key == 0) || (key == 0xe0))
			key |= getch() << 8;
		switch (key) {
			case CIO_KEY_MOUSE:
				getmouse(&mevent);
Deucе's avatar
Deucе committed
				startpos = ((mevent.starty - 1) * term.width) + (mevent.startx - 1);
				endpos = ((mevent.endy - 1) * term.width) + (mevent.endx - 1);
				if (startpos >= term.width * term.height)
					startpos = term.width * term.height - 1;
				if (endpos >= term.width * term.height)
					endpos = term.width * term.height - 1;
				if (endpos < startpos) {
					pos = endpos;
					endpos = startpos;
					startpos = pos;
Deucе's avatar
Deucе committed
				switch (mevent.event) {
Deucе's avatar
Deucе committed
						memcpy(sbuffer, screen, sbufsize);
						for (pos = startpos; pos <= endpos; pos++) {
							if ((sbuffer[pos].legacy_attr & 0x70) != 0x10)
								sbuffer[pos].legacy_attr =
								    (sbuffer[pos].legacy_attr & 0x8F) | 0x10;
Deucе's avatar
Deucе committed
								sbuffer[pos].legacy_attr =
								    (sbuffer[pos].legacy_attr & 0x8F) | 0x60;
							if (((sbuffer[pos].legacy_attr & 0x70) >> 4)
							    == (sbuffer[pos].legacy_attr & 0x0F))
								sbuffer[pos].legacy_attr |= 0x08;
							attr2palette(sbuffer[pos].legacy_attr,
							    &sbuffer[pos].fg,
							    &sbuffer[pos].bg);
						vmem_puttext(term.x - 1,
						    term.y - 1,
						    term.x + term.width - 2,
						    term.y + term.height - 2,
						    sbuffer);
Deucе's avatar
Deucе committed
						lines = abs(mevent.endy - mevent.starty) + 1;
						newcopybuf = realloc(copybuf, (endpos - startpos + 4 + lines * 2) * 4);
						if (newcopybuf)
							copybuf = newcopybuf;
						else
							goto cleanup;
Deucе's avatar
Deucе committed
						outpos = 0;
						lastchar = 0;
						for (pos = startpos; pos <= endpos; pos++) {
							size_t   outlen;
							uint8_t *utf8str;
Deucе's avatar
Deucе committed

							utf8str =
							    cp_to_utf8(conio_fontdata[screen[pos].font].cp,
							        (char *)&screen[pos].ch,
							        1,
							        &outlen);
deuce's avatar
deuce committed
							if (utf8str == NULL)
								continue;
							memcpy(copybuf + outpos, utf8str, outlen);
							outpos += outlen;
Deucе's avatar
Deucе committed
							if ((screen[pos].ch != ' ') && screen[pos].ch)
								lastchar = outpos;
							if ((pos + 1) % term.width == 0) {
								outpos = lastchar;
#ifdef _WIN32
								copybuf[outpos++] = '\r';
#endif
								copybuf[outpos++] = '\n';
								lastchar = outpos;
Deucе's avatar
Deucе committed
						copybuf[outpos] = 0;
						copytext(copybuf, strlen(copybuf));
						vmem_puttext(term.x - 1,
						    term.y - 1,
						    term.x + term.width - 2,
						    term.y + term.height - 2,
						    screen);
				vmem_puttext(term.x - 1,
				    term.y - 1,
				    term.x + term.width - 2,
				    term.y + term.height - 2,
				    screen);

cleanup:
	free(screen);
	free(sbuffer);
	free(tscreen);
Deucе's avatar
Deucе committed
	if (copybuf)
	restorescreen(savscrn);
	freescreen(savscrn);
Deucе's avatar
Deucе committed
void
update_status(struct bbslist *bbs, int speed, int ooii_mode)
deuce's avatar
deuce committed
{
	char nbuf[LIST_NAME_MAX + 10 + 11 + 1]; /*
                                                 * Room for "Name (Logging) (115300)" and terminator
                                                 * SAFE and Logging should me be possible.
                                                 */
Deucе's avatar
Deucе committed
	int               oldscroll;
	int               olddmc = hold_update;
	struct  text_info txtinfo;
	time_t            now;
	static time_t     lastupd = 0;
	static int        oldspeed = 0;
	int               timeon;
	char              sep;
	int               oldfont_norm;
	int               oldfont_bright;
Deucе's avatar
Deucе committed
	oldfont_norm = getfont(1);
	oldfont_bright = getfont(2);
	setfont(0, false, 1);
	setfont(0, false, 2);
Deucе's avatar
Deucе committed
	switch (getfont(1)) {
		case 0:
		case 17:
		case 18:
		case 19:
		case 25:
		case 26:
		case 27:
		case 28:
		case 29:
		case 31:
			sep = 0xb3;
			break;
		default:
			sep = '|';
Deucе's avatar
Deucе committed
	now = time(NULL);
	if ((now == lastupd) && (speed == oldspeed)) {
		setfont(oldfont_norm, 0, 1);
		setfont(oldfont_bright, 0, 2);
deuce's avatar
deuce committed
		return;
Deucе's avatar
Deucе committed
	lastupd = now;
	oldspeed = speed;
	if (now > (bbs->connected + 359999))
		timeon = 350000;
	else if (now < bbs->connected)
		timeon = 0;
	else
Deucе's avatar
Deucе committed
		timeon = (int)(now - bbs->connected);
	gettextinfo(&txtinfo);
Deucе's avatar
Deucе committed
	oldscroll = _wscroll;
	hold_update = true;
	textattr(YELLOW | (BLUE << 4));

        /* Move to status line thinger */
	window(term.x - 1, term.y + term.height - 1, term.x + term.width - 2, term.y + term.height - 1);
	gotoxy(1, 1);
	_wscroll = 0;
	strcpy(nbuf, bbs->name);
Deucе's avatar
Deucе committed
	if (safe_mode)
		strcat(nbuf, " (SAFE)");
Deucе's avatar
Deucе committed
	if (cterm->log)
		strcat(nbuf, " (Logging)");
Deucе's avatar
Deucе committed
	if (speed)
		sprintf(strchr(nbuf, 0), " (%d)", speed);
	if (cterm->doorway_mode)
deuce's avatar
deuce committed
		strcat(nbuf, " (DrWy)");
Deucе's avatar
Deucе committed
	switch (ooii_mode) {
		case 1:
			strcat(nbuf, " (OOTerm)");
			break;
		case 2:
			strcat(nbuf, " (OOTerm1)");
			break;
		case 3:
			strcat(nbuf, " (OOTerm2)");
			break;
deuce's avatar
deuce committed
	}
	ciolib_setcolour(11, 4);
Deucе's avatar
Deucе committed
	switch (cio_api.mode) {
		case CIOLIB_MODE_CURSES:
		case CIOLIB_MODE_CURSES_IBM:
		case CIOLIB_MODE_ANSI:
			if (timeon > 359999) {
				cprintf(" %-29.29s %c %-6.6s %c Connected: Too Long %c CTRL-S for menu ",
				    nbuf,
				    sep,
				    conn_types[bbs->conn_type],
				    sep,
				    sep);
				cprintf(" %-29.29s %c %-6.6s %c Connected: %02d:%02d:%02d %c CTRL-S for menu ",
				    nbuf,
				    sep,
				    conn_types[bbs->conn_type],
				    sep,
				    timeon / 3600,
				    (timeon / 60) % 60,
				    timeon % 60,
				    sep);
			if (timeon > 359999) {
				cprintf(" %-30.30s %c %-6.6s %c Connected: Too Long %c "ALT_KEY_NAME3CH "-Z for menu ",
				    nbuf,
				    sep,
				    conn_types[bbs->conn_type],
				    sep,
				    sep);
				cprintf(
					" %-30.30s %c %-6.6s %c Connected: %02d:%02d:%02d %c "ALT_KEY_NAME3CH "-Z for menu ",
					nbuf,
					sep,
					conn_types[bbs->conn_type],
					sep,
					timeon / 3600,
					(timeon / 60) % 60,
					timeon % 60,
					sep);
			break; /*    1+29     +3    +6    +3    +11        +3+3+2        +3    +6    +4  +5 */
Deucе's avatar
Deucе committed
	if (wherex() >= 80)
Deucе's avatar
Deucе committed
	_wscroll = oldscroll;
	setfont(oldfont_norm, 0, 1);
	setfont(oldfont_bright, 0, 2);
deuce's avatar
deuce committed
	textattr(txtinfo.attribute);
Deucе's avatar
Deucе committed
	window(txtinfo.winleft, txtinfo.wintop, txtinfo.winright, txtinfo.winbottom);
	gotoxy(txtinfo.curx, txtinfo.cury);
	hold_update = olddmc;
#if defined(_WIN32) && defined(_DEBUG) && defined(DUMP)
Deucе's avatar
Deucе committed
void
dump(BYTE *buf, int len)
Deucе's avatar
Deucе committed
	char   str[128];
	int    i, j;
	size_t slen = 0;

	slen = sprintf(str, "RX: ");
	for (i = 0; i < len; i += j) {
		for (j = 0; i + j < len && j < 32; j++)
			slen += sprintf(str + slen, "%02X ", buf[i + j]);
Deucе's avatar
Deucе committed
		slen = sprintf(str, "RX: ");
Deucе's avatar
Deucе committed

#endif /* if defined(_WIN32) && defined(_DEBUG) && defined(DUMP) */
int log_level = LOG_INFO;
	zmodem_t       *zm;
	struct bbslist *bbs;
Deucе's avatar
Deucе committed
enum {
	ZMODEM_MODE_SEND,
	ZMODEM_MODE_RECV
} zmodem_mode;
Deucе's avatar
Deucе committed
static BOOL
zmodem_check_abort(void *vp)
	struct zmodem_cbdata *zcb = (struct zmodem_cbdata *)vp;
	zmodem_t             *zm = zcb->zm;
	static time_t         last_check = 0;
	time_t                now = time(NULL);
	int                   key;
deuce's avatar
deuce committed
	if (zm == NULL)
		return true;
deuce's avatar
deuce committed
	if (quitting) {
Deucе's avatar
Deucе committed
		zm->cancelled = true;
		zm->local_abort = true;
		return true;
Deucе's avatar
Deucе committed
	if (last_check != now) {
		last_check = now;
		while (kbhit()) {
			switch ((key = getch())) {
deuce's avatar
deuce committed
				case ESC:
				case CTRL_C:
				case CTRL_X:
Deucе's avatar
Deucе committed
					zm->cancelled = true;
					zm->local_abort = true;
deuce's avatar
deuce committed
					break;
				case 0:
				case 0xe0:
					key |= (getch() << 8);
Deucе's avatar
Deucе committed
					if (key == CIO_KEY_MOUSE)
deuce's avatar
deuce committed
						getmouse(NULL);
Deucе's avatar
Deucе committed
					if (key == CIO_KEY_QUIT) {
						if (check_exit(false)) {
Deucе's avatar
Deucе committed
							zm->cancelled = true;
							zm->local_abort = true;
deuce's avatar
deuce committed
					}
					break;
Deucе's avatar
Deucе committed
	return zm->cancelled;
extern FILE *log_fp;
extern char *log_levels[];
#if defined(__BORLANDC__)
Deucе's avatar
Deucе committed
 #pragma argsused
Deucе's avatar
Deucе committed
static int
lputs(void *cbdata, int level, const char *str)
{
	char msg[512];
Deucе's avatar
Deucе committed
	int  chars;
	int  oldhold = hold_update;
#if defined(_WIN32) && defined(_DEBUG) && false
Deucе's avatar
Deucе committed
	sprintf(msg, "SyncTerm: %s\n", str);
	OutputDebugString(msg);
Deucе's avatar
Deucе committed
	if ((log_fp != NULL) && (level <= log_level)) {
		time_t t = time(NULL);
Deucе's avatar
Deucе committed
		fprintf(log_fp, "%.15s %s\n", ctime(&t) + 4, str);
Deucе's avatar
Deucе committed
	if (level > LOG_INFO)
Deucе's avatar
Deucе committed
        /* Assumes the receive window has been drawn! */
	window(log_ti.winleft, log_ti.wintop, log_ti.winright, log_ti.winbottom);
	gotoxy(log_ti.curx, log_ti.cury);
	textbackground(BLUE);
Deucе's avatar
Deucе committed
	switch (level) {
#if 0 // Not possible because of above level > LOG_INFO check
Deucе's avatar
Deucе committed
			SAFEPRINTF(msg, "%s\r\n", str);
deuce's avatar
deuce committed
#endif
deuce's avatar
deuce committed
		case LOG_INFO:
Deucе's avatar
Deucе committed
			SAFEPRINTF(msg, "%s\r\n", str);
Deucе's avatar
Deucе committed
			SAFEPRINTF(msg, "%s\r\n", str);
Deucе's avatar
Deucе committed
			SAFEPRINTF(msg, "Warning: %s\r\n", str);
Deucе's avatar
Deucе committed
			SAFEPRINTF(msg, "!ERROR: %s\r\n", str);
Deucе's avatar
Deucе committed
	hold_update = false;
	chars = cputs(msg);
	hold_update = oldhold;
	gettextinfo(&log_ti);
deuce's avatar
deuce committed
	return chars;
Deucе's avatar
Deucе committed
static int
lprintf(int level, const char *fmt, ...)
Deucе's avatar
Deucе committed
	char    sbuf[1024];
Deucе's avatar
Deucе committed
	va_start(argptr, fmt);
	vsnprintf(sbuf, sizeof(sbuf), fmt, argptr);
	sbuf[sizeof(sbuf) - 1] = 0;
	va_end(argptr);
	return lputs(NULL, level, sbuf);
#if defined(__BORLANDC__)
Deucе's avatar
Deucе committed
 #pragma argsused
Deucе's avatar
Deucе committed
void
zmodem_progress(void *cbdata, int64_t current_pos)
	char                  orig[128];
	unsigned              cps;
	int                   l;
	time_t                t;
	time_t                now;
	static time_t         last_progress = 0;
	int                   old_hold = hold_update;
	struct zmodem_cbdata *zcb = (struct zmodem_cbdata *)cbdata;
	zmodem_t             *zm = zcb->zm;
	bool                  growing = false;
Deucе's avatar
Deucе committed

	now = time(NULL);
	if (current_pos > zm->current_file_size)
		growing = true;
	if ((now != last_progress) || ((current_pos >= zm->current_file_size) && (growing == false))) {
		hold_update = true;
		window(((trans_ti.screenwidth - TRANSFER_WIN_WIDTH) / 2) + 2,
		    ((trans_ti.screenheight - TRANSFER_WIN_HEIGHT) / 2) + 1,
		    ((trans_ti.screenwidth - TRANSFER_WIN_WIDTH) / 2) + TRANSFER_WIN_WIDTH - 2,
		    ((trans_ti.screenheight - TRANSFER_WIN_HEIGHT) / 2) + 5);
Deucе's avatar
Deucе committed
		gotoxy(1, 1);
		textattr(LIGHTCYAN | (BLUE << 4));
		t = now - zm->transfer_start_time;
		if (t <= 0)
			t = 1;
		if (zm->transfer_start_pos > current_pos)
			zm->transfer_start_pos = 0;
		if ((cps = (unsigned)((current_pos - zm->transfer_start_pos) / t)) == 0)
			cps = 1;                                 /* cps so far */
		l = (zm->current_file_size - current_pos) / cps; /* remaining transfer est time */
		if (l < 0)
			l = 0;
		cprintf("File (%u of %u): %-.*s",
		    zm->current_file_num, zm->total_files, TRANSFER_WIN_WIDTH - 20, zm->current_file_name);
		clreol();
		cputs("\r\n");
Deucе's avatar
Deucе committed
		if (zm->transfer_start_pos)
			sprintf(orig, "From: %" PRId64 "  ", zm->transfer_start_pos);
Deucе's avatar
Deucе committed
			orig[0] = 0;
		cprintf("%sByte: %" PRId64 " of %" PRId64 " (%" PRId64 " KB)",
		    orig, current_pos, zm->current_file_size, zm->current_file_size / 1024);
		clreol();
		cputs("\r\n");
		cprintf("Time: %lu:%02lu  ETA: %lu:%02lu  Block: %u/CRC-%u  %u cps"
Deucе's avatar
Deucе committed
		    ,
		    (unsigned long)(t / 60L)
		    ,
		    (unsigned long)(t % 60L)
		    ,
		    (unsigned long)(l / 60L)
		    ,
		    (unsigned long)(l % 60L)
		    ,
		    zm->block_size
		    ,
		    zmodem_mode == ZMODEM_MODE_RECV ? (zm->receive_32bit_data ? 32 : 16)
		                                                              : (zm->can_fcs_32 && !zm->want_fcs_16) ? 32 : 16
		    ,
		    cps);
		cputs("\r\n");
Deucе's avatar
Deucе committed
		if (zm->current_file_size == 0) {
			cprintf("%*s%3d%%\r\n", TRANSFER_WIN_WIDTH / 2 - 5, "", 100);
Deucе's avatar
Deucе committed
		else {
			cprintf("%*s%3d%%\r\n", TRANSFER_WIN_WIDTH / 2 - 5, "",
			    (long)(((float)current_pos / (float)zm->current_file_size) * 100.0));
Deucе's avatar
Deucе committed
			l = (long)(60 * ((float)current_pos / (float)zm->current_file_size));
		cprintf("[%*.*s%*s]", l, l,
Deucе's avatar
Deucе committed
		    "\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1"
		    "\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1"
		    "\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1"
		    "\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1"
		    "\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1"
		    "\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1",
		    (int)(60 - l), "");
Deucе's avatar
Deucе committed
		last_progress = now;
		hold_update = false;
		gotoxy(wherex(), wherey());
		hold_update = old_hold;
#if defined(__BORLANDC__)
Deucе's avatar
Deucе committed
 #pragma argsused
Deucе's avatar
Deucе committed
unsigned char transfer_buffer[BUFFER_SIZE / 2];
unsigned      transfer_buf_len = 0;
Deucе's avatar
Deucе committed
static void
flush_send(void *unused)
Deucе's avatar
Deucе committed
	int sent;
Deucе's avatar
Deucе committed
	sent = conn_send(transfer_buffer, transfer_buf_len, 120 * 1000);
	if (sent < transfer_buf_len) {
		memmove(transfer_buffer, transfer_buffer + sent, transfer_buf_len - sent);
		transfer_buf_len -= sent;
	}
Deucе's avatar
Deucе committed
	else {
		transfer_buf_len = 0;
	}
Deucе's avatar
Deucе committed
static int
send_byte(void *unused, uchar ch, unsigned timeout /* seconds */)
Deucе's avatar
Deucе committed
	transfer_buffer[transfer_buf_len++] = ch;
	if (transfer_buf_len == sizeof(transfer_buffer))
		flush_send(unused);
Deucе's avatar
Deucе committed
	return !(transfer_buf_len < sizeof(transfer_buffer));
#if defined(__BORLANDC__)
Deucе's avatar
Deucе committed
 #pragma argsused
Deucе's avatar
Deucе committed
BYTE     recv_byte_buffer[BUFFER_SIZE];
unsigned recv_byte_buffer_len = 0;
unsigned recv_byte_buffer_pos = 0;
Deucе's avatar
Deucе committed
static void
recv_bytes(unsigned timeout /* Milliseconds */)
Deucе's avatar
Deucе committed
	if (recv_byte_buffer_len == 0) {
		recv_byte_buffer_len = parse_rip(recv_byte_buffer, 0, sizeof(recv_byte_buffer));
		if (recv_byte_buffer_len == 0) {
Deucе's avatar
Deucе committed
			recv_byte_buffer_len = conn_recv_upto(recv_byte_buffer, sizeof(recv_byte_buffer) - 3, timeout);
			if (recv_byte_buffer_len)
Deucе's avatar
Deucе committed
				recv_byte_buffer_len =
				    parse_rip(recv_byte_buffer, recv_byte_buffer_len, sizeof(recv_byte_buffer));
Deucе's avatar
Deucе committed
static int
recv_byte(void *unused, unsigned timeout /* seconds */)
Deucе's avatar
Deucе committed
	recv_bytes(timeout * 1000);
Deucе's avatar
Deucе committed
	if (recv_byte_buffer_len > 0) {
		ch = recv_byte_buffer[recv_byte_buffer_pos++];
		if (recv_byte_buffer_pos == recv_byte_buffer_len)
			recv_byte_buffer_len = recv_byte_buffer_pos = 0;
Deucе's avatar
Deucе committed
 #pragma argsused
Deucе's avatar
Deucе committed
BOOL
data_waiting(void *unused, unsigned timeout /* seconds */)
Deucе's avatar
Deucе committed
	bool ret;
deuce's avatar
deuce committed

Deucе's avatar
Deucе committed
	if (recv_byte_buffer_len)
		return true;
deuce's avatar
deuce committed
	pthread_mutex_lock(&(conn_inbuf.mutex));
Deucе's avatar
Deucе committed
	ret = conn_buf_wait_bytes(&conn_inbuf, 1, timeout * 1000) != 0;
deuce's avatar
deuce committed
	pthread_mutex_unlock(&(conn_inbuf.mutex));
	return ret;
Deucе's avatar
Deucе committed
size_t
count_data_waiting(void)
Deucе's avatar
Deucе committed
void
draw_transfer_window(char *title)
Deucе's avatar
Deucе committed
	char outline[TRANSFER_WIN_WIDTH * 2];
	char shadow[TRANSFER_WIN_WIDTH * 2]; /* Assumes that width*2 > height * 2 */
	int  i, top, left, old_hold;
	old_hold = hold_update;
Deucе's avatar
Deucе committed
	hold_update = true;
	gettextinfo(&trans_ti);
Deucе's avatar
Deucе committed
	top = (trans_ti.screenheight - TRANSFER_WIN_HEIGHT) / 2;
	left = (trans_ti.screenwidth - TRANSFER_WIN_WIDTH) / 2;
	window(1, 1, trans_ti.screenwidth, trans_ti.screenheight);

	vmem_gettext(left, top, left + TRANSFER_WIN_WIDTH + 1, top + TRANSFER_WIN_HEIGHT, winbuf);
Deucе's avatar
Deucе committed
	memset(outline, YELLOW | (BLUE << 4), sizeof(outline));
	for (i = 2; i < sizeof(outline) - 2; i += 2) {
		outline[i] = (char)0xcd; /* Double horizontal line */
Deucе's avatar
Deucе committed
	outline[0] = (char)0xc9;
	outline[sizeof(outline) - 2] = (char)0xbb;
	puttext(left, top, left + TRANSFER_WIN_WIDTH - 1, top, outline);
Deucе's avatar
Deucе committed
        /* Title */
	gotoxy(left + 4, top);
	textattr(YELLOW | (BLUE << 4));
	cprintf("\xb5 %*s \xc6", strlen(title), "");
	gotoxy(left + 6, top);
	textattr(WHITE | (BLUE << 4));
	cprintf("%s", title);
Deucе's avatar
Deucе committed
	for (i = 2; i < sizeof(outline) - 2; i += 2) {
		outline[i] = (char)0xc4;           /* Single horizontal line */
Deucе's avatar
Deucе committed
	outline[0] = (char)0xc7;                   /* 0xcc */
	outline[sizeof(outline) - 2] = (char)0xb6; /* 0xb6 */
	puttext(left, top + 6, left + TRANSFER_WIN_WIDTH - 1, top + 6, outline);
Deucе's avatar
Deucе committed
	for (i = 2; i < sizeof(outline) - 2; i += 2) {
		outline[i] = (char)0xcd; /* Double horizontal line */
Deucе's avatar
Deucе committed
	outline[0] = (char)0xc8;
	outline[sizeof(outline) - 2] = (char)0xbc;
	puttext(left,
	    top + TRANSFER_WIN_HEIGHT - 1,
	    left + TRANSFER_WIN_WIDTH - 1,
	    top + TRANSFER_WIN_HEIGHT - 1,
	    outline);
Deucе's avatar
Deucе committed
	outline[0] = (char)0xba;
	outline[sizeof(outline) - 2] = (char)0xba;
	for (i = 2; i < sizeof(outline) - 2; i += 2)
		outline[i] = ' ';
Deucе's avatar
Deucе committed
	for (i = 1; i < 6; i++)
		puttext(left, top + i, left + TRANSFER_WIN_WIDTH - 1, top + i, outline);
Deucе's avatar
Deucе committed
/*
 *      for(i=3;i < sizeof(outline) - 2; i+=2) {
 *              outline[i] = LIGHTGRAY | (BLACK << 8);
 *      }
 */
	for (i = 7; i < TRANSFER_WIN_HEIGHT - 1; i++)
		puttext(left, top + i, left + TRANSFER_WIN_WIDTH - 1, top + i, outline);
Deucе's avatar
Deucе committed
        /* Title */
	gotoxy(left + TRANSFER_WIN_WIDTH - 20, top + i);
Deucе's avatar
Deucе committed
	textattr(YELLOW | (BLUE << 4));
Deucе's avatar
Deucе committed
	textattr(WHITE | (BLUE << 4));
	gotoxy(left + TRANSFER_WIN_WIDTH - 18, top + i);
	cprintf("ESC to Abort");

Deucе's avatar
Deucе committed
        /* Shadow */
	if (uifc.bclr == BLUE) {
		gettext(left + TRANSFER_WIN_WIDTH,
		    top + 1,
		    left + TRANSFER_WIN_WIDTH + 1,
		    top + (TRANSFER_WIN_HEIGHT - 1),
		    shadow);
Deucе's avatar
Deucе committed
		for (i = 1; i < sizeof(shadow); i += 2)
			shadow[i] = DARKGRAY;
		puttext(left + TRANSFER_WIN_WIDTH,
		    top + 1,
		    left + TRANSFER_WIN_WIDTH + 1,
		    top + (TRANSFER_WIN_HEIGHT - 1),
		    shadow);
		gettext(left + 2,
		    top + TRANSFER_WIN_HEIGHT,
		    left + TRANSFER_WIN_WIDTH + 1,
		    top + TRANSFER_WIN_HEIGHT,
		    shadow);
Deucе's avatar
Deucе committed
		for (i = 1; i < sizeof(shadow); i += 2)
			shadow[i] = DARKGRAY;
		puttext(left + 2,
		    top + TRANSFER_WIN_HEIGHT,
		    left + TRANSFER_WIN_WIDTH + 1,
		    top + TRANSFER_WIN_HEIGHT,
		    shadow);
Deucе's avatar
Deucе committed
	window(left + 2, top + 7, left + TRANSFER_WIN_WIDTH - 3, top + TRANSFER_WIN_HEIGHT - 2);
	hold_update = false;
Deucе's avatar
Deucе committed
	gotoxy(1, 1);
	hold_update = old_hold;
	gettextinfo(&log_ti);
	_setcursortype(_NOCURSOR);
Deucе's avatar
Deucе committed
void
erase_transfer_window(void)
{
	vmem_puttext(((trans_ti.screenwidth - TRANSFER_WIN_WIDTH) / 2),
	    ((trans_ti.screenheight - TRANSFER_WIN_HEIGHT) / 2),
	    ((trans_ti.screenwidth - TRANSFER_WIN_WIDTH) / 2) + TRANSFER_WIN_WIDTH + 1,
	    ((trans_ti.screenheight - TRANSFER_WIN_HEIGHT) / 2) + TRANSFER_WIN_HEIGHT,
	    winbuf);
	window(trans_ti.winleft, trans_ti.wintop, trans_ti.winright, trans_ti.winbottom);
	gotoxy(trans_ti.curx, trans_ti.cury);
	textattr(trans_ti.attribute);
	_setcursortype(_NORMALCURSOR);
void ascii_upload(FILE *fp);
void raw_upload(FILE *fp);
Deucе's avatar
Deucе committed
void
begin_upload(struct bbslist *bbs, bool autozm, int lastch)
	char                  str[MAX_PATH * 2 + 1];
	char                  path[MAX_PATH + 1];
	int                   result;
	int                   i;
	FILE                 *fp;
	struct file_pick      fpick;
	char                 *opts[7] = {
		"ZMODEM",
		"YMODEM",
		"XMODEM-1K",
		"XMODEM-128",
		"ASCII",
		"Raw",
		""
Deucе's avatar
Deucе committed
	};
	struct  text_info     txtinfo;
	struct ciolib_screen *savscrn;
Deucе's avatar
Deucе committed

	if (safe_mode)
Deucе's avatar
Deucе committed
	gettextinfo(&txtinfo);
	savscrn = savescreen();
	setfont(0, false, 1);
	setfont(0, false, 2);
	setfont(0, false, 3);
	setfont(0, false, 4);
	init_uifc(false, false);
Deucе's avatar
Deucе committed
	if (!isdir(bbs->uldir)) {
		SAFEPRINTF(str, "Invalid upload directory: %s", bbs->uldir);
		uifcmsg(str, "An invalid `UploadPath` was specified in the `syncterm.lst` file");
		uifcbail();
		restorescreen(savscrn);
		freescreen(savscrn);
		gotoxy(txtinfo.curx, txtinfo.cury);
		return;
	}
Deucе's avatar
Deucе committed
	result = filepick(&uifc, "Upload", &fpick, bbs->uldir, NULL, UIFC_FP_ALLOWENTRY);
Deucе's avatar
Deucе committed
	if ((result == -1) || (fpick.files < 1)) {
		check_exit(false);
		filepick_free(&fpick);
		restorescreen(savscrn);
		freescreen(savscrn);
		gotoxy(txtinfo.curx, txtinfo.cury);
Deucе's avatar
Deucе committed
	SAFECOPY(path, fpick.selected[0]);
	filepick_free(&fpick);
	restorescreen(savscrn);
Deucе's avatar
Deucе committed
	if ((fp = fopen(path, "rb")) == NULL) {
		SAFEPRINTF2(str, "Error %d opening %s for read", errno, path);
		uifcmsg("Error opening file", str);
		restorescreen(savscrn);
		freescreen(savscrn);
		gotoxy(txtinfo.curx, txtinfo.cury);
Deucе's avatar
Deucе committed
	setvbuf(fp, NULL, _IOFBF, 0x10000);
	suspend_rip(true);
Deucе's avatar
Deucе committed
	if (autozm) {
Deucе's avatar
Deucе committed
	}
Deucе's avatar
Deucе committed
		i = 0;
		uifc.helpbuf = "Select Protocol";
		switch (uifc.list(WIN_MID | WIN_SAV, 0, 0, 0, &i, NULL, "Protocol", opts)) {
Deucе's avatar
Deucе committed
				xmodem_upload(bbs, fp, path, YMODEM | SEND, lastch);
deuce's avatar
deuce committed
				break;
			case 2:
Deucе's avatar
Deucе committed
				xmodem_upload(bbs, fp, path, XMODEM | SEND, lastch);
deuce's avatar
deuce committed
				break;
			case 3:
Deucе's avatar
Deucе committed
				xmodem_upload(bbs, fp, path, XMODEM | SEND | XMODEM_128B, lastch);
deuce's avatar
deuce committed
			case 4:
				ascii_upload(fp);
				break;
			case 5:
deuce's avatar
deuce committed
				raw_upload(fp);
				break;
			default:
				fclose(fp);
				break;
	suspend_rip(false);
	restorescreen(savscrn);
	freescreen(savscrn);
	gotoxy(txtinfo.curx, txtinfo.cury);
Deucе's avatar
Deucе committed
void
begin_download(struct bbslist *bbs)
deuce's avatar
deuce committed
{
	char                  path[MAX_PATH + 1];
	int                   i;
	char                 *opts[6] = {
		"ZMODEM",
		"YMODEM-g",
		"YMODEM",
		"XMODEM-CRC",
		"XMODEM-CHKSUM",
		""
Deucе's avatar
Deucе committed
	};
	struct  text_info     txtinfo;
	int                   old_hold = hold_update;
	struct ciolib_screen *savscrn;
Deucе's avatar
Deucе committed

	if (safe_mode)
deuce's avatar
deuce committed
		return;

Deucе's avatar
Deucе committed
	gettextinfo(&txtinfo);
	savscrn = savescreen();
	setfont(0, false, 1);
	setfont(0, false, 2);
	setfont(0, false, 3);
	setfont(0, false, 4);
deuce's avatar
deuce committed

	init_uifc(false, false);
deuce's avatar
deuce committed

Deucе's avatar
Deucе committed
	i = 0;
	uifc.helpbuf = "Select Protocol";
	hold_update = false;
	suspend_rip(true);
Deucе's avatar
Deucе committed
	switch (uifc.list(WIN_MID | WIN_SAV, 0, 0, 0, &i, NULL, "Protocol", opts)) {
			check_exit(false);
deuce's avatar
deuce committed
		case 0:
			zmodem_download(bbs);
			break;
		case 1:
Deucе's avatar
Deucе committed
			xmodem_download(bbs, YMODEM | CRC | GMODE | RECV, NULL);
deuce's avatar
deuce committed
			break;
		case 2:
Deucе's avatar
Deucе committed
			xmodem_download(bbs, YMODEM | CRC | RECV, NULL);
deuce's avatar
deuce committed
			break;
		case 3:
Deucе's avatar
Deucе committed
			if (uifc.input(WIN_MID | WIN_SAV, 0, 0, "Filename", path, sizeof(path), 0) != -1)
				xmodem_download(bbs, XMODEM | CRC | RECV, path);
Deucе's avatar
Deucе committed
			if (uifc.input(WIN_MID | WIN_SAV, 0, 0, "Filename", path, sizeof(path), 0) != -1)
				xmodem_download(bbs, XMODEM | RECV, path);
deuce's avatar
deuce committed
	}
	suspend_rip(false);
Deucе's avatar
Deucе committed
	hold_update = old_hold;
deuce's avatar
deuce committed
	uifcbail();
	restorescreen(savscrn);
	freescreen(savscrn);
deuce's avatar
deuce committed
	gotoxy(txtinfo.curx, txtinfo.cury);
}

Deucе's avatar
Deucе committed
 #pragma argsused
Deucе's avatar
Deucе committed
static BOOL
is_connected(void *unused)
Deucе's avatar
Deucе committed
	if (recv_byte_buffer_len)
		return true;
Deucе's avatar
Deucе committed
	return conn_connected();
Deucе's avatar
Deucе committed
void
deuce's avatar
deuce committed
{
Deucе's avatar
Deucе committed
	char buf[1024];
	int  r;
	int  inch;
	char ch[2];

	ch[1] = 0;
	for (;;) {
		r = fread(buf, 1, sizeof(buf), fp);
		if (r)
			conn_send(buf, r, 0);

                /* Note, during RAW uploads, do NOT send ANSI responses and don't
                 * allow speed changes. */
		while ((inch = recv_byte(NULL, 0)) >= 0) {
			ch[0] = inch;
			cterm_write(cterm, ch, 1, NULL, 0, NULL);
deuce's avatar
deuce committed
		}
Deucе's avatar
Deucе committed
		if (r == 0)
deuce's avatar
deuce committed
			break;
	}
	fclose(fp);
}
Deucе's avatar
Deucе committed
void
ascii_upload(FILE *fp)
	char  linebuf[1024 + 2]; /* One extra for terminator, one extra for added CR */
	char *p;
	char  ch[2];
	int   inch;
	bool  lastwascr = false;
Deucе's avatar
Deucе committed

	ch[1] = 0;
	while (!feof(fp)) {
		if (fgets(linebuf, 1025, fp) != NULL) {
			if ((p = strrchr(linebuf, '\n')) != NULL) {
				if (((p == linebuf) && !lastwascr) || ((p > linebuf) && (*(p - 1) != '\n'))) {
					*p = '\r';
Deucе's avatar
Deucе committed
					*p = '\n';
Deucе's avatar
Deucе committed
					*p = 0;
Deucе's avatar
Deucе committed
			lastwascr = false;
deuce's avatar
deuce committed
			if (p != NULL)
Deucе's avatar
Deucе committed
				p = strchr(p, 0);
			if ((p != NULL) && (p > linebuf)) {
				if (*(p - 1) == '\r')
					lastwascr = true;
Deucе's avatar
Deucе committed
			conn_send(linebuf, strlen(linebuf), 0);
Deucе's avatar
Deucе committed

                /* Note, during ASCII uploads, do NOT send ANSI responses and don't
                 * allow speed changes. */
		while ((inch = recv_byte(NULL, 0)) >= 0) {
			ch[0] = inch;
			cterm_write(cterm, ch, 1, NULL, 0, NULL);
Deucе's avatar
Deucе committed
static void
transfer_complete(bool success, bool was_binary)
{
	int timeout = success ? settings.xfer_success_keypress_timeout : settings.xfer_failure_keypress_timeout;

	if (!was_binary)
		conn_binary_mode_off();
Deucе's avatar
Deucе committed
	if (log_fp != NULL)
Deucе's avatar
Deucе committed

        /* TODO: Make this pretty (countdown timer) and don't delay a second between keyboard polls */
	lprintf(LOG_NOTICE, "Hit any key or wait %u seconds to continue...", timeout);
	while (timeout > 0) {
Deucе's avatar
Deucе committed
			if ((getch() == (CIO_KEY_QUIT & 0xff)) && ((getch() << 8) == (CIO_KEY_QUIT & 0xff00)))
				check_exit(false);
Deucе's avatar
Deucе committed
void
zmodem_upload(struct bbslist *bbs, FILE *fp, char *path)
Deucе's avatar
Deucе committed
	bool                 success;
	zmodem_t             zm;
	int64_t              fsize;
	struct zmodem_cbdata cbdata;
Deucе's avatar
Deucе committed
	bool                 was_binary = conn_api.binary_mode;
	draw_transfer_window("ZMODEM Upload");
Deucе's avatar
Deucе committed
	zmodem_mode = ZMODEM_MODE_SEND;
Deucе's avatar
Deucе committed
	cbdata.zm = &zm;
	cbdata.bbs = bbs;
	if (!was_binary)
		conn_binary_mode_on();
Deucе's avatar
Deucе committed
	transfer_buf_len = 0;
	zmodem_init(&zm,

            /* cbdata */ &cbdata,
	    lputs, zmodem_progress,
	    send_byte, recv_byte,
	    is_connected,
	    zmodem_check_abort,
	    data_waiting,
	    flush_send);
Deucе's avatar
Deucе committed
	zm.log_level = &log_level;
Deucе's avatar
Deucе committed
	zm.current_file_num = zm.total_files = 1; /* ToDo: support multi-file/batch uploads */
Deucе's avatar
Deucе committed
	fsize = filelength(fileno(fp));
	lprintf(LOG_INFO, "Sending %s (%" PRId64 " KB) via ZMODEM",
	    path, fsize / 1024);

	if ((success = zmodem_send_file(&zm, path, fp,
            /* ZRQINIT? */ true, /* start_time */ NULL, /* sent_bytes */ NULL)) == true)
		zmodem_get_zfin(&zm);

	fclose(fp);

	transfer_complete(success, was_binary);
Deucе's avatar
Deucе committed
BOOL
zmodem_duplicate_callback(void *cbdata, void *zm_void)
	struct  text_info     txtinfo;
	struct ciolib_screen *savscrn;
	bool                  ret = false;
	int                   i;
	char                 *opts[4] = {
		"Overwrite",
		"Choose New Name",
		"Cancel Download",
		NULL
Deucе's avatar
Deucе committed
	};
	struct zmodem_cbdata *cb = (struct zmodem_cbdata *)cbdata;
	zmodem_t             *zm = (zmodem_t *)zm_void;
	char                  fpath[MAX_PATH * 2 + 2];
	bool                  loop = true;
	int                   old_hold = hold_update;
Deucе's avatar
Deucе committed

	gettextinfo(&txtinfo);
	savscrn = savescreen();
	setfont(0, false, 1);
	setfont(0, false, 2);
	setfont(0, false, 3);
	setfont(0, false, 4);
	window(1, 1, txtinfo.screenwidth, txtinfo.screenheight);
	init_uifc(false, false);
Deucе's avatar
Deucе committed
	hold_update = false;
Deucе's avatar
Deucе committed
	while (loop) {
		loop = false;
		i = 0;
		uifc.helpbuf = "Duplicate file... choose action\n";
		switch (uifc.list(WIN_MID | WIN_SAV, 0, 0, 0, &i, NULL, "Duplicate File Name", opts)) {
				if (check_exit(false)) {
Deucе's avatar
Deucе committed
					ret = false;
Deucе's avatar
Deucе committed
				loop = true;
Deucе's avatar
Deucе committed
			case 0: /* Overwrite */
				SAFEPRINTF2(fpath, "%s/%s", cb->bbs->dldir, zm->current_file_name);
				unlink(fpath);
Deucе's avatar
Deucе committed
				ret = true;
Deucе's avatar
Deucе committed
			case 1: /* Choose new name */
				uifc.changes = 0;
				uifc.helpbuf = "Duplicate Filename... enter new name";
				if (uifc.input(WIN_MID | WIN_SAV, 0, 0, "New Filename: ", zm->current_file_name,
				    sizeof(zm->current_file_name) - 1, K_EDIT) == -1) {
Deucе's avatar
Deucе committed
					loop = true;
Deucе's avatar
Deucе committed
					if (uifc.changes)
						ret = true;
Deucе's avatar
Deucе committed
						loop = true;
	restorescreen(savscrn);
	freescreen(savscrn);
	gotoxy(txtinfo.curx, txtinfo.cury);
Deucе's avatar
Deucе committed
	hold_update = old_hold;
	return ret;
Deucе's avatar
Deucе committed
void
zmodem_download(struct bbslist *bbs)
Deucе's avatar
Deucе committed
	zmodem_t             zm;
	int                  files_received;
	uint64_t             bytes_received;
Deucе's avatar
Deucе committed
	bool                 was_binary = conn_api.binary_mode;
Deucе's avatar
Deucе committed
	if (safe_mode)
	draw_transfer_window("ZMODEM Download");
Deucе's avatar
Deucе committed
	zmodem_mode = ZMODEM_MODE_RECV;
	if (!was_binary)
		conn_binary_mode_on();
Deucе's avatar
Deucе committed
	cbdata.zm = &zm;
	cbdata.bbs = bbs;
	transfer_buf_len = 0;
	zmodem_init(&zm,

            /* cbdata */ &cbdata,
	    lputs, zmodem_progress,
	    send_byte, recv_byte,
	    is_connected,
	    zmodem_check_abort,
	    data_waiting,
	    flush_send);
Deucе's avatar
Deucе committed
	zm.log_level = &log_level;
Deucе's avatar
Deucе committed
	zm.duplicate_filename = zmodem_duplicate_callback;
Deucе's avatar
Deucе committed
	files_received = zmodem_recv_files(&zm, bbs->dldir, &bytes_received);
Deucе's avatar
Deucе committed
	if (files_received > 1)
		lprintf(LOG_INFO, "Received %u files (%" PRId64 " bytes) successfully", files_received, bytes_received);
	transfer_complete(files_received, was_binary);
Deucе's avatar
Deucе committed

/* End of Zmodem Stuff */
/* X/Y-MODEM stuff */

Deucе's avatar
Deucе committed
uchar block[1024]; /* Block buffer                                      */
ulong block_num;   /* Block number                                      */
Deucе's avatar
Deucе committed
static BOOL
xmodem_check_abort(void *vp)
	xmodem_t     *xm = (xmodem_t *)vp;
Deucе's avatar
Deucе committed
	static time_t last_check = 0;
	time_t        now = time(NULL);
	int           key;
	if (xm == NULL)
		return false;
Deucе's avatar
Deucе committed
		xm->cancelled = true;
		return true;
Deucе's avatar
Deucе committed
	if (last_check != now) {
		last_check = now;
		while (kbhit()) {
			switch ((key = getch())) {
				case ESC:
				case CTRL_C:
				case CTRL_X:
Deucе's avatar
Deucе committed
					xm->cancelled = true;
					key |= (getch() << 8);
Deucе's avatar
Deucе committed
					if (key == CIO_KEY_MOUSE)
						getmouse(NULL);
Deucе's avatar
Deucе committed
					if (key == CIO_KEY_QUIT) {
						if (check_exit(false))
Deucе's avatar
Deucе committed
							xm->cancelled = true;
Deucе's avatar
Deucе committed
	return xm->cancelled;
/****************************************************************************/

/* Returns the number of blocks required to send len bytes                                      */

/****************************************************************************/
Deucе's avatar
Deucе committed
uint64_t
num_blocks(unsigned curr_block, uint64_t offset, uint64_t len, unsigned block_size)
deuce's avatar
deuce committed
{
	uint64_t blocks;
deuce's avatar
deuce committed

Deucе's avatar
Deucе committed
	len -= offset;
	blocks = len / block_size;
	if (len % block_size)
deuce's avatar
deuce committed
		blocks++;
Deucе's avatar
Deucе committed
	return curr_block + blocks;
deuce's avatar
deuce committed
}

#if defined(__BORLANDC__)
Deucе's avatar
Deucе committed
 #pragma argsused
#endif
Deucе's avatar
Deucе committed
void
xmodem_progress(void *cbdata, unsigned block_num, int64_t offset, int64_t fsize, time_t start)
Deucе's avatar
Deucе committed
	uint64_t      total_blocks;
	unsigned      cps;
	int           i;
	uint64_t      l;
	time_t        t;
	time_t        now;
deuce's avatar
deuce committed
	static time_t last_progress;
Deucе's avatar
Deucе committed
	int           old_hold = hold_update;
	xmodem_t     *xm = (xmodem_t *)cbdata;
deuce's avatar
deuce committed

Deucе's avatar
Deucе committed
	now = time(NULL);
	if ((now - last_progress > 0) || (offset >= fsize)) {
		hold_update = true;
		window(((trans_ti.screenwidth - TRANSFER_WIN_WIDTH) / 2) + 2,
		    ((trans_ti.screenheight - TRANSFER_WIN_HEIGHT) / 2) + 1,
		    ((trans_ti.screenwidth - TRANSFER_WIN_WIDTH) / 2) + TRANSFER_WIN_WIDTH - 2,
		    ((trans_ti.screenheight - TRANSFER_WIN_HEIGHT) / 2) + 5);
Deucе's avatar
Deucе committed
		gotoxy(1, 1);
		textattr(LIGHTCYAN | (BLUE << 4));
		t = now - start;
		if (t <= 0)
			t = 1;
		if ((cps = (unsigned)(offset / t)) == 0)
			cps = 1;           /* cps so far */
		l = (time_t)(fsize / cps); /* total transfer est time */
		if (t >= l)
Deucе's avatar
Deucе committed
			l = 0;
		else
			l -= t;                    /* now, it's est time left */
Deucе's avatar
Deucе committed
		if ((*(xm->mode)) & SEND) {
			total_blocks = num_blocks(block_num, offset, fsize, xm->block_size);
			cprintf("Block (%lu%s): %u/%" PRId64 "  Byte: %" PRId64,
			    xm->block_size % 1024L ? xm->block_size : xm->block_size / 1024L,
			    xm->block_size % 1024L ? "" : "K",
			    block_num,
			    total_blocks,
			    offset);
deuce's avatar
deuce committed
			clreol();
			cputs("\r\n");
			cprintf("Time: %lu:%02lu/%" PRIu64 ":%02" PRIu64 "  %u cps",
			    (ulong)(t / 60L),
			    (ulong)(t % 60L),
			    (ulong)(l / 60L),
			    (ulong)(l % 60L),
			    cps);
deuce's avatar
deuce committed
			clreol();
			cputs("\r\n");
			cprintf("%*s%3d%%\r\n", TRANSFER_WIN_WIDTH / 2 - 5, "",
			    fsize ? (long)(((float)offset / (float)fsize) * 100.0) : 100);
Deucе's avatar
Deucе committed
			i = fsize ? (((float)offset / (float)fsize) * 60.0) : 60;
			if (i < 0)
				i = 0;
			else if (i > 60)
				i = 60;
			cprintf("[%*.*s%*s]", i, i,
Deucе's avatar
Deucе committed
			    "\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1"
			    "\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1"
			    "\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1"
			    "\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1"
			    "\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1"
			    "\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1",
			    60 - i, "");
Deucе's avatar
Deucе committed
		}
		else if ((*(xm->mode)) & YMODEM) {
			cprintf("Block (%lu%s): %lu  Byte: %" PRId64,
			    xm->block_size % 1024L ? xm->block_size : xm->block_size / 1024L,
			    xm->block_size % 1024L ? "" : "K",
			    block_num,
			    offset);
deuce's avatar
deuce committed
			clreol();
			cputs("\r\n");
			cprintf("Time: %lu:%02lu/%lu:%02lu  %u cps",
			    (ulong)(t / 60L),
			    (ulong)(t % 60L),
			    (ulong)(l / 60L),
			    (ulong)(l % 60L),
			    cps);
deuce's avatar
deuce committed
			clreol();
			cputs("\r\n");
			cprintf("%*s%3d%%\r\n", TRANSFER_WIN_WIDTH / 2 - 5, "",
			    fsize ? (long)(((float)offset / (float)fsize) * 100.0) : 100);
Deucе's avatar
Deucе committed
			i = fsize ? (long)(((float)offset / (float)fsize) * 60.0) : 60;
			if (i < 0)
				i = 0;
			else if (i > 60)
				i = 60;
			cprintf("[%*.*s%*s]", i, i,
Deucе's avatar
Deucе committed
			    "\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1"
			    "\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1"
			    "\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1"
			    "\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1"
			    "\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1"
			    "\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1",
			    60 - i, "");
Deucе's avatar
Deucе committed
		}
		else { /* XModem receive */
			cprintf("Block (%lu%s): %lu  Byte: %" PRId64,
			    xm->block_size % 1024L ? xm->block_size : xm->block_size / 1024L,
			    xm->block_size % 1024L ? "" : "K",
			    block_num,
			    offset);
deuce's avatar
deuce committed
			clreol();
			cputs("\r\n");
			cprintf("Time: %lu:%02lu  %u cps",
			    (ulong)(t / 60L),
			    (ulong)(t % 60L),
			    cps);
deuce's avatar
deuce committed
			clreol();
		}
Deucе's avatar
Deucе committed
		last_progress = now;
		hold_update = false;
deuce's avatar
deuce committed
		gotoxy(wherex(), wherey());
		hold_update = old_hold;
	}
Deucе's avatar
Deucе committed
static int
recv_g(void *cbdata, unsigned timeout)
	xmodem_t *xm = (xmodem_t *)cbdata;
Deucе's avatar
Deucе committed
	xm->recv_byte = recv_byte;
	return 'G';
Deucе's avatar
Deucе committed
static int
recv_c(void *cbdata, unsigned timeout)
	xmodem_t *xm = (xmodem_t *)cbdata;
Deucе's avatar
Deucе committed
	xm->recv_byte = recv_byte;
	return 'C';
Deucе's avatar
Deucе committed
static int
recv_nak(void *cbdata, unsigned timeout)
	xmodem_t *xm = (xmodem_t *)cbdata;
Deucе's avatar
Deucе committed
	xm->recv_byte = recv_byte;
	return NAK;
Deucе's avatar
Deucе committed
void
xmodem_upload(struct bbslist *bbs, FILE *fp, char *path, long mode, int lastch)
Deucе's avatar
Deucе committed
	bool     success;
	xmodem_t xm;
	int64_t  fsize;
	bool     was_binary = conn_api.binary_mode;
	if (!was_binary)
		conn_binary_mode_on();
	xmodem_init(&xm,

            /* cbdata */ &xm,
	    &mode,
	    lputs,
	    xmodem_progress,
	    send_byte,
	    recv_byte,
	    is_connected,
	    xmodem_check_abort,
	    flush_send);
Deucе's avatar
Deucе committed
	xm.log_level = &log_level;
	if (!data_waiting(&xm, 0)) {
		switch (lastch) {
Deucе's avatar
Deucе committed
				xm.recv_byte = recv_g;
Deucе's avatar
Deucе committed
				xm.recv_byte = recv_c;
Deucе's avatar
Deucе committed
				xm.recv_byte = recv_nak;
Deucе's avatar
Deucе committed
	if (mode & XMODEM_128B)
		xm.block_size = 128;
Deucе's avatar
Deucе committed
	xm.total_files = 1; /* ToDo: support multi-file/batch uploads */
Deucе's avatar
Deucе committed
	fsize = filelength(fileno(fp));
Deucе's avatar
Deucе committed
	if (mode & XMODEM) {
		if (mode & GMODE)
deuce's avatar
deuce committed
			draw_transfer_window("XMODEM-g Upload");
		else
			draw_transfer_window("XMODEM Upload");
		lprintf(LOG_INFO, "Sending %s (%" PRId64 " KB) via XMODEM%s",
		    path, fsize / 1024, (mode & GMODE) ? "-g" : "");
Deucе's avatar
Deucе committed
	else if (mode & YMODEM) {
		if (mode & GMODE)
			draw_transfer_window("YMODEM-g Upload");
deuce's avatar
deuce committed
		else
			draw_transfer_window("YMODEM Upload");
		lprintf(LOG_INFO, "Sending %s (%" PRId64 " KB) via YMODEM%s",
		    path, fsize / 1024, (mode & GMODE) ? "-g" : "");
		if (!was_binary)
			conn_binary_mode_off();
	if ((success = xmodem_send_file(&xm, path, fp,

            /* start_time */ NULL, /* sent_bytes */ NULL)) == true) {
Deucе's avatar
Deucе committed
		if (mode & YMODEM) {
			if (xmodem_get_mode(&xm)) {
				lprintf(LOG_INFO, "Sending YMODEM termination block");
Deucе's avatar
Deucе committed
				memset(block, 0, 128); /* send short block for terminator */
				xmodem_put_block(&xm, block, 128 /* block_size */, 0 /* block_num */);
Deucе's avatar
Deucе committed
				if (xmodem_get_ack(&xm, /* tries: */ 6, /* block_num: */ 0) != ACK)
					lprintf(LOG_WARNING, "Failed to receive ACK after terminating block");
	transfer_complete(success, was_binary);
Deucе's avatar
Deucе committed
bool
xmodem_duplicate(xmodem_t *xm, struct bbslist *bbs, char *path, size_t pathsize, char *fname)
	struct  text_info     txtinfo;
	struct ciolib_screen *savscrn;
	bool                  ret = false;
	int                   i;
	char                 *opts[4] = {
		"Overwrite",
		"Choose New Name",
		"Cancel Download",
		NULL
Deucе's avatar
Deucе committed
	};
	char                  newfname[MAX_PATH + 1];
	bool                  loop = true;
	int                   old_hold = hold_update;
Deucе's avatar
Deucе committed

	gettextinfo(&txtinfo);
	savscrn = savescreen();
	setfont(0, false, 1);
	setfont(0, false, 2);
	setfont(0, false, 3);
	setfont(0, false, 4);
	window(1, 1, txtinfo.screenwidth, txtinfo.screenheight);

	init_uifc(false, false);
Deucе's avatar
Deucе committed
	hold_update = false;
	while (loop) {
		loop = false;
		i = 0;
		uifc.helpbuf = "Duplicate file... choose action\n";
		switch (uifc.list(WIN_MID | WIN_SAV, 0, 0, 0, &i, NULL, "Duplicate File Name", opts)) {
				if (check_exit(false)) {
Deucе's avatar
Deucе committed
					ret = false;
Deucе's avatar
Deucе committed
				loop = true;
Deucе's avatar
Deucе committed
			case 0: /* Overwrite */
				unlink(path);
Deucе's avatar
Deucе committed
				ret = true;
Deucе's avatar
Deucе committed
			case 1: /* Choose new name */
				uifc.changes = 0;
				uifc.helpbuf = "Duplicate Filename... enter new name";
				SAFECOPY(newfname, getfname(fname));
				if (uifc.input(WIN_MID | WIN_SAV, 0, 0, "New Filename: ", newfname,
				    sizeof(newfname) - 1, K_EDIT) == -1) {
Deucе's avatar
Deucе committed
					loop = true;
Deucе's avatar
Deucе committed
					if (uifc.changes) {
						sprintf(path, "%s/%s", bbs->dldir, newfname);
						ret = true;
					}
					else {
						loop = true;
	restorescreen(savscrn);
	freescreen(savscrn);
Deucе's avatar
Deucе committed
	hold_update = old_hold;
	return ret;
Deucе's avatar
Deucе committed
void
xmodem_download(struct bbslist *bbs, long mode, char *path)
Deucе's avatar
Deucе committed
	xmodem_t xm;

        /* The better to -Wunused you with my dear! */
	char     str[MAX_PATH * 2 + 2];
	char     fname[MAX_PATH + 1];
	int      i = 0;
	int      fnum = 0;
	uint     errors;
	uint     total_files = 0;
	uint     cps;
	uint     wr;
	bool     success = false;
	long     fmode;
	long     serial_num = -1;
	ulong    tmpftime;
	int64_t  file_bytes = 0, file_bytes_left = 0;
	int64_t  total_bytes = 0;
	FILE    *fp = NULL;
	time_t   t, startfile, ftime = 0;
	int      old_hold = hold_update;
	bool     extra_pass = false;
	bool     was_binary = conn_api.binary_mode;

	if (safe_mode)
Deucе's avatar
Deucе committed
	if (mode & XMODEM) {
		if (mode & GMODE)
deuce's avatar
deuce committed
			draw_transfer_window("XMODEM-g Download");
		else
			draw_transfer_window("XMODEM Download");
Deucе's avatar
Deucе committed
	}
	else if (mode & YMODEM) {
		if (mode & GMODE)
			draw_transfer_window("YMODEM-g Download");
			draw_transfer_window("YMODEM Download");
Deucе's avatar
Deucе committed
	else {
		return;
Deucе's avatar
Deucе committed
	}
	if (!was_binary)
		conn_binary_mode_on();
	xmodem_init(&xm,

            /* cbdata */ &xm,
	    &mode,
	    lputs,
	    xmodem_progress,
	    send_byte,
	    recv_byte,
	    is_connected,
	    xmodem_check_abort,
	    flush_send);
Deucе's avatar
Deucе committed
	xm.log_level = &log_level;
	while (is_connected(NULL)) {
		if (mode & XMODEM) {
			if (isfullpath(path))
				SAFECOPY(str, path);
			else
				SAFEPRINTF2(str, "%s/%s", bbs->dldir, path);
			file_bytes = file_bytes_left = 0x7fffffff;
deuce's avatar
deuce committed
		}
		else {
Deucе's avatar
Deucе committed
			lprintf(LOG_INFO, "Fetching YMODEM header block");
			for (errors = 0; errors <= xm.max_errors && !xm.cancelled; errors++) {
deuce's avatar
deuce committed
				xmodem_put_nak(&xm, /* expected_block: */ 0);
Deucе's avatar
Deucе committed
				i = xmodem_get_block(&xm, block, /* expected_block: */ 0);
				if (i == SUCCESS) {
					if (!(mode & GMODE)) {
						send_byte(&xm, ACK, 10);
deuce's avatar
deuce committed
					break;
				}
Deucе's avatar
Deucе committed
				if (extra_pass) {
Deucе's avatar
Deucе committed
                                        // This is a hack for sz  0.12.21rc
					lprintf(LOG_INFO,
					    "No YMODEM header block after transfer, assuming end of batch");
Deucе's avatar
Deucе committed
					goto end;
				}
Deucе's avatar
Deucе committed
				if ((i == NOINP) && (mode & GMODE)) { /* Timeout */
					lprintf(LOG_WARNING, "Falling back to %s",
					    (mode & CRC) ? "CRC-16" : "Checksum");
deuce's avatar
deuce committed
				}
Deucе's avatar
Deucе committed
				if (i == NOT_YMODEM) {
					lprintf(LOG_WARNING, "Falling back to XMODEM%s", (mode & GMODE) ? "-g" : "");
					mode &= ~(YMODEM);
Deucе's avatar
Deucе committed
					mode |= XMODEM | CRC;
deuce's avatar
deuce committed
					erase_transfer_window();
Deucе's avatar
Deucе committed
					hold_update = 0;
					if (uifc.input(WIN_MID | WIN_SAV, 0, 0, "XMODEM Filename", fname, sizeof(fname),
					    0) == -1) {
deuce's avatar
deuce committed
						xmodem_cancel(&xm);
						goto end;
					}
Deucе's avatar
Deucе committed
					hold_update = old_hold;
					if (mode & GMODE)
deuce's avatar
deuce committed
						draw_transfer_window("XMODEM Download");
					else
						draw_transfer_window("XMODEM-g Download");
Deucе's avatar
Deucе committed
					lprintf(LOG_WARNING, "Falling back to XMODEM%s", (mode & GMODE) ? "-g" : "");
					if (isfullpath(fname))
						SAFECOPY(str, fname);
					else
						SAFEPRINTF2(str, "%s/%s", bbs->dldir, fname);
					file_bytes = file_bytes_left = 0x7fffffff;
deuce's avatar
deuce committed
					break;
Deucе's avatar
Deucе committed
				if ((errors + 1 > xm.max_errors / 3) && mode & CRC && !(mode & GMODE)) {
					lprintf(LOG_NOTICE, "Falling back to 8-bit Checksum mode");
					mode &= ~CRC;
Deucе's avatar
Deucе committed
			if ((errors > xm.max_errors) || xm.cancelled) {
				xmodem_cancel(&xm);
deuce's avatar
deuce committed
				goto end;
Deucе's avatar
Deucе committed
			if (i != NOT_YMODEM) {
				if (!block[0]) {
					lprintf(LOG_INFO, "Received YMODEM termination block");
deuce's avatar
deuce committed
				}
				extra_pass = false;
Deucе's avatar
Deucе committed
				file_bytes = total_bytes = 0;
				total_files = 0;
				i = sscanf(((char *)block) + strlen((char *)block) + 1,
				        "%" PRId64 " %lo %lo %lo %d %" PRId64
Deucе's avatar
Deucе committed
				        ,
				        &file_bytes  /* file size (decimal) */
Deucе's avatar
Deucе committed
				        ,
				        &tmpftime    /* file time (octal unix format) */
Deucе's avatar
Deucе committed
				        ,
				        &fmode       /* file mode (not used) */
Deucе's avatar
Deucе committed
				        ,
				        &serial_num  /* program serial number */
Deucе's avatar
Deucе committed
				        ,
				        &total_files /* remaining files to be sent */
Deucе's avatar
Deucе committed
				        ,
				        &total_bytes /* remaining bytes to be sent */
Deucе's avatar
Deucе committed
				        );
				ftime = tmpftime;
				lprintf(LOG_DEBUG, "YMODEM header (%u fields): %s", i,
				    block + strlen((char *)block) + 1);
				SAFECOPY(fname, ((char *)block));
Deucе's avatar
Deucе committed

				if (!file_bytes)
					file_bytes = 0x7fffffff;
				file_bytes_left = file_bytes;
				if (!total_files)
					total_files = 1;
				if (total_bytes < file_bytes)
					total_bytes = file_bytes;

				lprintf(LOG_DEBUG, "Incoming filename: %.64s ", getfname(fname));

				SAFEPRINTF2(str, "%s/%s", bbs->dldir, getfname(fname));
				lprintf(LOG_INFO, "File size: %" PRId64 " bytes", file_bytes);
				if (total_files > 1) {
					lprintf(LOG_INFO,
					    "Remaining: %" PRId64 " bytes in %u files",
					    total_bytes,
					    total_files);
deuce's avatar
deuce committed

Deucе's avatar
Deucе committed
		lprintf(LOG_DEBUG, "Receiving: %.64s ", str);
Deucе's avatar
Deucе committed
		while (fexistcase(str) && !(mode & OVERWRITE)) {
			lprintf(LOG_WARNING, "%s already exists", str);
			if (!xmodem_duplicate(&xm, bbs, str, sizeof(str), getfname(fname))) {
Deucе's avatar
Deucе committed
		if ((fp = fopen(str, "wb")) == NULL) {
			lprintf(LOG_ERR, "Error %d creating %s", errno, str);
			xmodem_cancel(&xm);
Deucе's avatar
Deucе committed
		if (mode & XMODEM) {
			lprintf(LOG_INFO, "Receiving %s via %s %s",
			    str,
			    mode & GMODE ? "XMODEM-g" : "XMODEM",
			    mode & CRC ? "CRC-16" : "Checksum");
Deucе's avatar
Deucе committed
		}
		else {
			lprintf(LOG_INFO, "Receiving %s (%" PRId64 " KB) via %s %s",
			    str,
			    file_bytes / 1024,
			    mode & GMODE ? "YMODEM-g" : "YMODEM",
			    mode & CRC ? "CRC-16" : "Checksum");
Deucе's avatar
Deucе committed
		}

		startfile = time(NULL);
		success = false;

		errors = 0;
		block_num = 1;
		if (i != NOT_YMODEM)
			xmodem_put_nak(&xm, block_num);
Deucе's avatar
Deucе committed
		while (is_connected(NULL)) {
			xmodem_progress(&xm, block_num, ftello(fp), file_bytes, startfile);
			if (xm.is_cancelled(&xm)) {
				lprintf(LOG_WARNING, "Cancelled locally");
				xmodem_cancel(&xm);
Deucе's avatar
Deucе committed
			if (i == NOT_YMODEM)
				i = SUCCESS;
Deucе's avatar
Deucе committed
				i = xmodem_get_block(&xm, block, block_num);
deuce's avatar
deuce committed

Deucе's avatar
Deucе committed
			if (i != SUCCESS) {
				if (i == EOT) { /* end of transfer */
					success = true;
deuce's avatar
deuce committed
					xmodem_put_ack(&xm);
					break;
				}
Deucе's avatar
Deucе committed
				if (i == CAN) { /* Cancel */
					xm.cancelled = true;
deuce's avatar
deuce committed
					break;
				}
Deucе's avatar
Deucе committed
				if (mode & GMODE) {
					lprintf(LOG_ERR, "Too many errors (%u)", ++errors);
Deucе's avatar
Deucе committed
				if (++errors > xm.max_errors) {
					lprintf(LOG_ERR, "Too many errors (%u)", errors);
deuce's avatar
deuce committed
					xmodem_cancel(&xm);
					break;
				}
Deucе's avatar
Deucе committed
				if ((i != NOT_XMODEM)
				    && (block_num == 1) && (errors > (xm.max_errors / 3)) && mode & CRC
				    && !(mode & GMODE)) {
					lprintf(LOG_NOTICE, "Falling back to 8-bit Checksum mode (error=%d)", i);
					mode &= ~CRC;
deuce's avatar
deuce committed
				xmodem_put_nak(&xm, block_num);
				continue;
			}
Deucе's avatar
Deucе committed
			if (!(mode & GMODE)) {
				send_byte(&xm, ACK, 10);
deuce's avatar
deuce committed
				flush_send(&xm);
			}
Deucе's avatar
Deucе committed
			if (file_bytes_left <= 0L) { /* No more bytes to receive */
				lprintf(LOG_WARNING,
				    "Sender attempted to send more bytes than were specified in header");
deuce's avatar
deuce committed
			}
Deucе's avatar
Deucе committed
			wr = xm.block_size;
			if (wr > (uint)file_bytes_left)
				wr = (uint)file_bytes_left;
			if (fwrite(block, 1, wr, fp) != wr) {
				lprintf(LOG_ERR, "Error writing %u bytes to file at offset %" PRId64,
				    wr, (int64_t)ftello(fp));
deuce's avatar
deuce committed
				xmodem_cancel(&xm);
deuce's avatar
deuce committed
			}
Deucе's avatar
Deucе committed
			file_bytes_left -= wr;
deuce's avatar
deuce committed
			block_num++;
		}
Deucе's avatar
Deucе committed
                /* Use correct file size */
deuce's avatar
deuce committed
		fflush(fp);
Deucе's avatar
Deucе committed
		lprintf(LOG_DEBUG, "file_bytes=%u", file_bytes);
		lprintf(LOG_DEBUG, "file_bytes_left=%u", file_bytes_left);
		lprintf(LOG_DEBUG, "filelength=%u", filelength(fileno(fp)));
Deucе's avatar
Deucе committed
		if (file_bytes < (ulong)filelength(fileno(fp))) {
			lprintf(LOG_INFO, "Truncating file to %lu bytes", (ulong)file_bytes);
			chsize(fileno(fp), (ulong)file_bytes); /* 4GB limit! */
		}
		else {
deuce's avatar
deuce committed
			file_bytes = filelength(fileno(fp));
Deucе's avatar
Deucе committed
		}
deuce's avatar
deuce committed
		fclose(fp);
deuce's avatar
deuce committed
		fp = NULL;
Deucе's avatar
Deucе committed
		t = time(NULL) - startfile;
		if (!t)
			t = 1;
		if (success)
			lprintf(LOG_INFO, "Successful - Time: %lu:%02lu  CPS: %lu",
			    (ulong)(t / 60), (ulong)(t % 60), (ulong)(file_bytes / t));
deuce's avatar
deuce committed
		else
Deucе's avatar
Deucе committed
			lprintf(LOG_ERR, "File Transfer %s", xm.cancelled ? "Cancelled" : "Failure");
deuce's avatar
deuce committed

Deucе's avatar
Deucе committed
		if (!(mode & XMODEM) && ftime)
			setfdate(str, ftime);
deuce's avatar
deuce committed

Deucе's avatar
Deucе committed
		if (!success && (file_bytes == 0)) { /* remove 0-byte files */
deuce's avatar
deuce committed
			if (remove(str) == -1)
				lprintf(LOG_ERR, "Unable to remove empty file %s\n", str);
		}
Deucе's avatar
Deucе committed
		if (mode & XMODEM) /* maximum of one file */
deuce's avatar
deuce committed
			break;
Deucе's avatar
Deucе committed
		if ((cps = (unsigned)(file_bytes / t)) == 0)
			cps = 1;
Deucе's avatar
Deucе committed
		if (--total_files <= 0)
			extra_pass = true;
Deucе's avatar
Deucе committed
		total_bytes -= file_bytes;
		if ((total_files > 1) && total_bytes) {
			lprintf(LOG_INFO, "Remaining - Time: %lu:%02lu  Files: %u  KBytes: %" PRId64,
			    (total_bytes / cps) / 60,
			    (total_bytes / cps) % 60,
			    total_files,
			    total_bytes / 1024);
Deucе's avatar
Deucе committed
		}
Deucе's avatar
Deucе committed
	if (fp)
deuce's avatar
deuce committed
		fclose(fp);
	transfer_complete(success, was_binary);
/* End of X/Y-MODEM stuff */
Deucе's avatar
Deucе committed
void
music_control(struct bbslist *bbs)
deuce's avatar
deuce committed
{
	struct  text_info     txtinfo;
	struct ciolib_screen *savscrn;
	int                   i;
Deucе's avatar
Deucе committed
	gettextinfo(&txtinfo);
	savscrn = savescreen();
	setfont(0, false, 1);
	setfont(0, false, 2);
	setfont(0, false, 3);
	setfont(0, false, 4);
	init_uifc(false, false);
Deucе's avatar
Deucе committed
	i = cterm->music_enable;
	uifc.helpbuf = music_helpbuf;
	if (uifc.list(WIN_MID | WIN_SAV, 0, 0, 0, &i, NULL, "ANSI Music Setup", music_names) != -1)
		cterm->music_enable = i;
		check_exit(false);
deuce's avatar
deuce committed
	uifcbail();
	restorescreen(savscrn);
	freescreen(savscrn);
deuce's avatar
deuce committed
}
Deucе's avatar
Deucе committed
void
font_control(struct bbslist *bbs, struct cterminal *cterm)
	struct ciolib_screen *savscrn;
	struct  text_info     txtinfo;
	int                   i, j, k;
Deucе's avatar
Deucе committed
	if (safe_mode)
Deucе's avatar
Deucе committed
	gettextinfo(&txtinfo);
	savscrn = savescreen();
	setfont(0, false, 1);
	setfont(0, false, 2);
	setfont(0, false, 3);
	setfont(0, false, 4);
	init_uifc(false, false);
Deucе's avatar
Deucе committed
	switch (cio_api.mode) {
		case CIOLIB_MODE_CONIO:
		case CIOLIB_MODE_CONIO_FULLSCREEN:
deuce's avatar
deuce committed
		case CIOLIB_MODE_CURSES_ASCII:
		case CIOLIB_MODE_CURSES_IBM:
		case CIOLIB_MODE_ANSI:
			uifcmsg("Not supported in this video output mode.",
			    "Font cannot be changed in the current video output mode");
			check_exit(false);
Deucе's avatar
Deucе committed
			i = j = cterm->altfont[0];
			uifc.helpbuf = "`Font Setup`\n\n"
			    "Change the current font.  Font must support the current video mode:\n\n"
			    "`8x8`  Used for screen modes with 35 or more lines and all C64/C128 modes\n"
			    "`8x14` Used for screen modes with 28 and 34 lines\n"
			    "`8x16` Used for screen modes with 30 lines or fewer than 28 lines.";
			k = uifc.list(WIN_MID | WIN_SAV | WIN_INS, 0, 0, 0, &i, &j, "Font Setup", font_names);
			if (k != -1) {
				if (k & MSK_INS) {
					struct file_pick fpick;
Deucе's avatar
Deucе committed

					j = filepick(&uifc, "Load Font From File", &fpick, ".", NULL, 0);
					check_exit(false);
Deucе's avatar
Deucе committed
					if ((j != -1) && (fpick.files >= 1))
						loadfont(fpick.selected[0]);
					filepick_free(&fpick);
				}
Deucе's avatar
Deucе committed
					setfont(i, false, 1);
deuce's avatar
deuce committed
					cterm->altfont[0] = i;
Deucе's avatar
Deucе committed
			else {
				check_exit(false);
Deucе's avatar
Deucе committed
			}
			break;
	restorescreen(savscrn);
	freescreen(savscrn);
Deucе's avatar
Deucе committed
void
capture_control(struct bbslist *bbs)
	struct ciolib_screen *savscrn;
	char                 *cap;
	struct  text_info     txtinfo;
	int                   i, j;
Deucе's avatar
Deucе committed
	if (safe_mode)
Deucе's avatar
Deucе committed
	gettextinfo(&txtinfo);
	savscrn = savescreen();
	setfont(0, false, 1);
	setfont(0, false, 2);
	setfont(0, false, 3);
	setfont(0, false, 4);
	cap = (char *)alloca(cterm->height * cterm->width * 2);
Deucе's avatar
Deucе committed
	gettext(cterm->x, cterm->y, cterm->x + cterm->width - 1, cterm->y + cterm->height - 1, cap);
	init_uifc(false, false);
Deucе's avatar
Deucе committed
	if (!cterm->log) {
		struct file_pick fpick;
Deucе's avatar
Deucе committed
		char            *opts[] = {
			"ASCII",
			"Raw",
			"Binary",
			"Binary with SAUCE",
			""
Deucе's avatar
Deucе committed
		};

		i = 0;
		uifc.helpbuf = "~ Capture Type ~\n\n"
		    "`ASCII`              ASCII only (no ANSI escape sequences)\n"
		    "`Raw`                Preserves ANSI sequences\n"
		    "`Binary`             Saves current screen in IBM-CGA/BinaryText format\n"
		    "`Binary with SAUCE`  Saves current screen in BinaryText format with SAUCE\n"
		    "\n"
		    "Raw is useful for stealing ANSI screens from other systems.\n"
		    "Don't do that though.  :-)";
		if (uifc.list(WIN_MID | WIN_SAV, 0, 0, 0, &i, NULL, "Capture Type", opts) != -1) {
			j = filepick(&uifc, "Capture File", &fpick, bbs->dldir, i >= 2 ? "*.bin" : NULL,
			        UIFC_FP_ALLOWENTRY | UIFC_FP_OVERPROMPT);
			check_exit(false);
Deucе's avatar
Deucе committed
			if ((j != -1) && (fpick.files >= 1)) {
				if (i >= 2) {
					FILE *fp = fopen(fpick.selected[0], "wb");
Deucе's avatar
Deucе committed

					if (fp == NULL) {
Deucе's avatar
Deucе committed

						sprintf(err, "Error %u opening file '%s'", errno, fpick.selected[0]);
						uifc.msg(err);
Deucе's avatar
Deucе committed
					}
					else {
Deucе's avatar
Deucе committed

						uifc.pop("Writing to file");
						fwrite(cap, sizeof(uint8_t), cterm->width * cterm->height * 2, fp);
Deucе's avatar
Deucе committed
						if (i > 2) {
							time_t       t = time(NULL);
							struct tm   *tm;
							struct sauce sauce;

							memset(&sauce, 0, sizeof(sauce));
							memcpy(sauce.id, SAUCE_ID, sizeof(sauce.id));
							memcpy(sauce.ver, SAUCE_VERSION, sizeof(sauce.ver));
							memset(sauce.title, ' ', sizeof(sauce.title));
							memset(sauce.author, ' ', sizeof(sauce.author));
							memset(sauce.group, ' ', sizeof(sauce.group));
Deucе's avatar
Deucе committed
							if (bbs != NULL) {
								memcpy(sauce.title, bbs->name,
								    MIN(strlen(bbs->name), sizeof(sauce.title)));
								memcpy(sauce.author, bbs->user,
								    MIN(strlen(bbs->user), sizeof(sauce.author)));
							if ((tm = localtime(&t)) != NULL) { // The null-terminator
                                                                                            // overwrites the first
                                                                                            // byte of filesize
Rob Swindell's avatar
Rob Swindell committed
								snprintf(sauce.date, sizeof(sauce.date), "%04u%02u%02u",
								    1900 + tm->tm_year, 1 + tm->tm_mon, tm->tm_mday);
Deucе's avatar
Deucе committed
							sauce.filesize = LE_INT32(ftell(fp)); // LE
							sauce.datatype = sauce_datatype_bin;
							sauce.filetype = cterm->width / 2;
Deucе's avatar
Deucе committed
							if (ciolib_getvideoflags()
							    & (CIOLIB_VIDEO_BGBRIGHT | CIOLIB_VIDEO_NOBLINK))
								sauce.tflags |= sauce_ansiflag_nonblink;

							fputc(SAUCE_SEPARATOR, fp);
Deucе's avatar
Deucе committed

                                                        /* No comment block (no comments) */
							fwrite(&sauce.id, sizeof(sauce.id), 1, fp);
							fwrite(&sauce.ver, sizeof(sauce.ver), 1, fp);
							fwrite(&sauce.title, sizeof(sauce.title), 1, fp);
							fwrite(&sauce.author, sizeof(sauce.author), 1, fp);
							fwrite(&sauce.group, sizeof(sauce.group), 1, fp);
							fwrite(&sauce.date, sizeof(sauce.date), 1, fp);
							fwrite(&sauce.filesize, sizeof(sauce.filesize), 1, fp);
							fwrite(&sauce.datatype, sizeof(sauce.datatype), 1, fp);
							fwrite(&sauce.filetype, sizeof(sauce.filetype), 1, fp);
							fwrite(&sauce.tinfo1, sizeof(sauce.tinfo1), 1, fp);
							fwrite(&sauce.tinfo2, sizeof(sauce.tinfo2), 1, fp);
							fwrite(&sauce.tinfo3, sizeof(sauce.tinfo3), 1, fp);
							fwrite(&sauce.tinfo4, sizeof(sauce.tinfo4), 1, fp);
							fwrite(&sauce.comments, sizeof(sauce.comments), 1, fp);
							fwrite(&sauce.tflags, sizeof(sauce.tflags), 1, fp);
							fwrite(&sauce.tinfos, sizeof(sauce.tinfos), 1, fp);
						}
						fclose(fp);
						uifc.pop(NULL);
						sprintf(msg, "Screen saved to '%s'", getfname(fpick.selected[0]));
						uifc.msg(msg);
					}
Deucе's avatar
Deucе committed
				}
				else {
					cterm_openlog(cterm, fpick.selected[0], i ? CTERM_LOG_RAW : CTERM_LOG_ASCII);
				}
			filepick_free(&fpick);
		}
Deucе's avatar
Deucе committed
		else {
			check_exit(false);
Deucе's avatar
Deucе committed
		}
Deucе's avatar
Deucе committed
		if (cterm->log & CTERM_LOG_PAUSED) {
Deucе's avatar
Deucе committed
			};

			i = 0;
			uifc.helpbuf = "`Capture Control`\n\n"
			    "~ Unpause ~ Continues logging\n"
			    "~ Close ~   Closes the log\n\n";
			if (uifc.list(WIN_MID | WIN_SAV, 0, 0, 0, &i, NULL, "Capture Control", opts) != -1) {
				switch (i) {
						check_exit(false);
Deucе's avatar
Deucе committed
						cterm->log = cterm->log & CTERM_LOG_MASK;
Deucе's avatar
Deucе committed
			};

			i = 0;
			uifc.helpbuf = "`Capture Control`\n\n"
			    "~ Pause ~ Suspends logging\n"
			    "~ Close ~ Closes the log\n\n";
			if (uifc.list(WIN_MID | WIN_SAV, 0, 0, 0, &i, NULL, "Capture Control", opts) != -1) {
				switch (i) {
						check_exit(false);
rswindell's avatar
rswindell committed
						cterm->log |= CTERM_LOG_PAUSED;
	restorescreen(savscrn);
	freescreen(savscrn);
Deucе's avatar
Deucе committed
#define OUTBUF_SIZE 2048
Deucе's avatar
Deucе committed
#define WRITE_OUTBUF() \
	if (outbuf_size > 0) { \
		cterm_write(cterm, outbuf, outbuf_size, (char *)ansi_replybuf, sizeof(ansi_replybuf), &speed); \
Deucе's avatar
Deucе committed
		outbuf_size = 0; \
		if (ansi_replybuf[0]) \
		conn_send(ansi_replybuf, strlen((char *)ansi_replybuf), 0); \
Deucе's avatar
Deucе committed
		updated = true; \
Deucе's avatar
Deucе committed
int
get_cache_fn_base(struct bbslist *bbs, char *fn, size_t fnsz)
	get_syncterm_filename(fn, fnsz, SYNCTERM_PATH_CACHE, false);
	backslash(fn);
	strcat(fn, bbs->name);
	backslash(fn);
	if (!isdir(fn))
deuce's avatar
deuce committed
		mkpath(fn);
	if (!isdir(fn))
		return 0;
	return 1;
}
Deucе's avatar
Deucе committed
int
get_cache_fn_subdir(struct bbslist *bbs, char *fn, size_t fnsz, const char *subdir)
{
	int ret;

	ret = get_cache_fn_base(bbs, fn, fnsz);
	if (ret == 0)
		return ret;
	strcat(fn, subdir);
	backslash(fn);
	if (!isdir(fn))
		mkpath(fn);
	if (!isdir(fn))
		return 0;
	return 1;
}
Deucе's avatar
Deucе committed
static int
clean_path(char *fn, size_t fnsz)

	fp = _fullpath(NULL, fn, fnsz);
Deucе's avatar
Deucе committed
	if ((fp == NULL) || strcmp(fp, fn)) {
		FREE_AND_NULL(fp);
		return 0;
	}
	FREE_AND_NULL(fp);
	return 1;
}
// ============ This section taken from pnmgamma.c ============
/* pnmgamma.c - perform gamma correction on a PNM image
**
** Copyright (C) 1991 by Bill Davidson and Jef Poskanzer.
**
** Permission to use, copy, modify, and distribute this software and its
** documentation for any purpose and without fee is hereby granted, provided
** that the above copyright notice appear in all copies and that both that
** copyright notice and this permission notice appear in supporting
** documentation.  This software is provided "as is" without express or
** implied warranty.
*/

static void
buildBt709ToSrgbGamma(const uint8_t maxval) {
    uint8_t const newMaxval = 255;
    double const gammaSrgb = 2.4;
/*----------------------------------------------------------------------------
   Build a gamma table of size maxval+1 for the combination of the
   inverse of ITU Rec BT.709 and the forward SRGB gamma transfer
   functions.  I.e. this converts from Rec 709 to SRGB.

   'gammaSrgb' must be 2.4 for true SRGB.
-----------------------------------------------------------------------------*/
    double const oneOverGamma709  = 0.45;
    double const gamma709         = 1.0 / oneOverGamma709;
    double const oneOverGammaSrgb = 1.0 / gammaSrgb;
    double const normalizer       = 1.0 / maxval;

    if (pnm_gamma_max == maxval)
	return;
    /* This transfer function is linear for sample values 0
       .. maxval*.018 and an exponential for larger sample values.
       The exponential is slightly stretched and translated, though,
       unlike the popular pure exponential gamma transfer function.
    */

    uint8_t const linearCutoff709 = (uint8_t) (maxval * 0.018 + 0.5);
    double const linearCompression709 = 
        0.018 / (1.099 * pow(0.018, oneOverGamma709) - 0.099);

    double const linearCutoffSrgb = 0.0031308;
    double const linearExpansionSrgb = 
        (1.055 * pow(0.0031308, oneOverGammaSrgb) - 0.055) / 0.0031308;

    int i;

    for (i = 0; i <= maxval; ++i) {
        double const normalized = i * normalizer;
            /* Xel sample value normalized to 0..1 */
        double radiance;
        double srgb;

        if (i < linearCutoff709 / linearCompression709)
            radiance = normalized * linearCompression709;
        else
            radiance = pow((normalized + 0.099) / 1.099, gamma709);

        assert(radiance <= 1.0);

        if (radiance < linearCutoffSrgb * normalizer)
            srgb = radiance * linearExpansionSrgb;
        else
            srgb = 1.055 * pow(normalized, oneOverGammaSrgb) - 0.055;

        assert(srgb <= 1.0);

        pnm_gamma[i] = srgb * newMaxval + 0.5;
    }
}


// ====================== End of section ======================

bool
is_pbm_whitespace(char c)
{
	switch(c) {
		case ' ':
		case '\t':
		case '\r':
		case '\n':
			return true;
	}
	return false;
}

bool
read_pbm_char(FILE *f, off_t *lastpos, char *ch)
{
	if (lastpos != NULL) {
		*lastpos = ftello(f);
		if (*lastpos == -1)
			return false;
	}
	if (fread(ch, 1, 1, f) != 1)
		return false;
	return true;
}

bool
skip_pbm_whitespace(FILE *f)
{
	char ch;
	off_t lastpos;
	bool start = true;

	for (;;) {
		if (!read_pbm_char(f, &lastpos, &ch)) {
			return false;
		}
		if (start) {
			if (!is_pbm_whitespace(ch)) {
				return false;
			}
			start = false;
		}
		if (ch == '#') {
			do {
				if (!read_pbm_char(f, &lastpos, &ch)) {
					return false;
				}
			} while (ch != '\r' && ch != '\n');
		}
		if (!is_pbm_whitespace(ch)) {
			if (fseeko(f, lastpos, SEEK_SET) != 0)
				return false;
			return true;
		}
	}
}

uintmax_t
read_pbm_number(FILE *f)
{
	char value[256]; // Should be big enough ;)
	char *endptr;
	int i;
	off_t lastpos;

	for (i = 0; i < sizeof(value) - 1; i++) {
		if (!read_pbm_char(f, &lastpos, &value[i]))
			break;
		if (value[i] < '0' || value[i] > '9') {
			if (i == 0)
				return UINTMAX_MAX;
			value[i] = 0;
			if (fseeko(f, lastpos, SEEK_SET) != 0)
				return UINTMAX_MAX;
			return strtoumax(value, &endptr, 10);
		}
	}
	return UINTMAX_MAX;
}

static bool
read_pbm_text_raster(struct ciolib_mask *ret, size_t sz, FILE *f)
{
	uintmax_t num;
	size_t    i;
	size_t    byte = 0;
	uint8_t   bit = 7;

	memset(ret->bits, 0, (sz + 7) / 8);
	for (i = 0; i < sz; i++) {
		num = read_pbm_number(f);
		if (num > 1)
			return false;
		ret->bits[byte] |= num << bit;
		if (bit == 0)
			bit = 7;
		else
			bit--;
	}
	return true;
}

static bool
read_ppm_any_raster(struct ciolib_pixels *p, size_t sz, uint8_t max, FILE *f, uintmax_t(*readnum)(FILE *))
{
	uintmax_t num;
	size_t    i;
	uint32_t  pdata;

	for (i = 0; i < sz; i++) {
		pdata = 0x80000000;	// RGB value (anything less is palette)

		// Red
		num = readnum(f);
		if (num > 255)
			return false;
		pdata |= (pnm_gamma[num] << 16);

		// Green
		num = readnum(f);
		if (num > 255)
			return false;
		pdata |= (pnm_gamma[num] << 8);

		// Blue
		num = readnum(f);
		if (num > 255)
			return false;
		pdata |= (pnm_gamma[num] << 0);
		p->pixels[i] = pdata;
	}
	return true;
}

static bool
read_ppm_text_raster(struct ciolib_pixels *p, size_t sz, uint8_t max, FILE *f)
{
	return read_ppm_any_raster(p, sz, max, f, read_pbm_number);
}

static uintmax_t
read_pbm_byte(FILE *f)
{
	uint8_t b;

	if (fread(&b, 1, 1, f) != 1)
		return UINTMAX_MAX;
	return b;
}

static bool
read_ppm_raw_raster(struct ciolib_pixels *p, size_t sz, uint8_t max, FILE *f)
{
	return read_ppm_any_raster(p, sz, max, f, read_pbm_byte);
}

static struct ciolib_pixels *
alloc_ciolib_pixels(uint32_t w, uint32_t h)
{
	struct ciolib_pixels *ret;
	size_t pszo;
	size_t psz;

	pszo = w * h;
	if (h != 0 && pszo / h != w)
		return NULL;
	psz = pszo * sizeof(uint32_t);
	if (psz / sizeof(uint32_t) != pszo)
		return NULL;
	ret = malloc(sizeof(*ret));
	if (ret == NULL)
		return ret;
	ret->width = w;
	ret->height = h;
	ret->pixelsb = NULL;
	if (psz > 0) {
		ret->pixels = malloc(psz);
		if (ret->pixels == NULL) {
			free(ret);
			return NULL;
		}
	}
	else {
		ret->pixels = NULL;
	}
	return ret;
}

static struct ciolib_mask *
alloc_ciolib_mask(uint32_t w, uint32_t h)
{
	struct ciolib_mask *ret;
	size_t psz;

	psz = w * h;
	if (h != 0 && psz / h != w)
		return NULL;
	ret = malloc(sizeof(*ret));
	if (ret == NULL)
		return ret;
	ret->width = w;
	ret->height = h;
	if (psz > 0) {
		ret->bits = malloc((psz + 7) / 8);
		if (ret->bits == NULL) {
			free(ret);
			return NULL;
		}
	}
	else {
		ret->bits = NULL;
	}
	return ret;
}

static void *
read_pbm(const char *fn, bool bitmap)
{
	uintmax_t             width;
	uintmax_t             height;
	uintmax_t             maxval = 0;
	uintmax_t             overflow;
	FILE                 *f = fopen(fn, "rb");
	struct ciolib_mask   *mret = NULL;
	struct ciolib_pixels *pret = NULL;
	size_t                raster_size;
	size_t                raster_bit_size;
	char                  magic[2];
	bool                  b;

	if (f == NULL)
		goto fail;
	if (fread(magic, sizeof(magic), 1, f) != 1)
		goto fail;
	if (magic[0] != 'P')
		goto fail;
	switch (magic[1]) {
		case '1':
		case '4':
			if (!bitmap)
				goto fail;
			break;
		case '3':
		case '6':
			if (bitmap)
				goto fail;
			break;
		default:
			goto fail;
	}

	if (!skip_pbm_whitespace(f))
		goto fail;

	assert(UINTMAX_MAX > UINT32_MAX);
	width = read_pbm_number(f);
	if (width > UINT32_MAX)
		goto fail;

	if (!skip_pbm_whitespace(f))
		goto fail;

	height = read_pbm_number(f);
	if (height > UINT32_MAX)
		goto fail;

	// Check for multiplcation overflow
	overflow = width * height;
	if (width != 0 && overflow / height != width)
		goto fail;
	// Check for type truncation
	raster_size = overflow;
	if (raster_size != overflow)
		goto fail;

	if (magic[1] == '3' || magic[1] == '6') {
		if (!skip_pbm_whitespace(f))
			goto fail;

		maxval = read_pbm_number(f);
		if (maxval == UINTMAX_MAX)
			goto fail;

		if (maxval > 255)
			goto fail;
	}

	if (!skip_pbm_whitespace(f))
		goto fail;

	switch (magic[1]) {
		case '1':
		case '4':
			raster_bit_size = (raster_size + 7) / 8;
			mret = alloc_ciolib_mask(width, height);
			if (mret == NULL)
				goto fail;
			if (magic[1] == '1')
				b = read_pbm_text_raster(mret, raster_size, f);
			else
				b = fread(mret->bits, raster_bit_size, 1, f) == 1;
			if (!b)
				goto fail;
			fclose(f);
			return mret;
		case '3':
		case '6':
			pret = alloc_ciolib_pixels(width, height);
			if (pret == NULL)
				goto fail;
			if (magic[1] == '3')
				b = read_ppm_text_raster(pret, raster_size, maxval, f);
			else
				b = read_ppm_raw_raster(pret, raster_size, maxval, f);
			if (!b)
				goto fail;
			fclose(f);
			return pret;
		default:
			goto fail;
	}

fail:
	freemask(mret);
	freepixels(pret);
	if (f)
		fclose(f);
	return NULL;
}

static void *
b64_decode_alloc(const char *strbuf, size_t slen, size_t *outlen)
{
	void  *ret;
	int    ol;
	size_t sz;

	sz = slen * 3 + 3 / 4 + 1;
	ret = malloc(sz);
	if (!ret)
		return NULL;
	ol = b64_decode(ret, sz, strbuf, slen);
	if (ol == -1) {
		free(ret);
		return NULL;
	}
	if (outlen != NULL)
		*outlen = ol;
	return ret;
}

static void
draw_ppm_str_handler(char *str, size_t slen, char *fn, void *apcd)
{
	struct ciolib_mask   *ctmask = NULL;
	char                 *p;
	char                 *p2;
	void                 *mask = NULL;
	char                 *maskfn = NULL;
	char                 *ppmfn = NULL;
	struct ciolib_pixels *ppmp = NULL;
	unsigned long        *val;
	unsigned long         sx = 0; // Source X to start at
	unsigned long         sy = 0; // Source Y to start at
	unsigned long         sw = 0; // Source width to show
	unsigned long         sh = 0; // Source height to show
	unsigned long         dx = 0; // Destination X to start at
	unsigned long         dy = 0; // Destination Y to start at
	unsigned long         mx = 0; // Mask X to start at
	unsigned long         my = 0; // Mask Y to start at
	unsigned long         mw = 0; // Width of the mask
	unsigned long         mh = 0; // Height of the mask
	size_t                mlen = 0;

	for (p = str + 18; p && *p == ';'; p = strchr(p + 1, ';')) {
		val = NULL;
		switch (p[1]) {
			case 'S':
				switch (p[2]) {
					case 'X':
						val = &sx;
						break;
					case 'Y':
						val = &sy;
						break;
					case 'W':
						val = &sw;
						break;
					case 'H':
						val = &sh;
						break;
				}
				break;
			case 'D':
				switch (p[2]) {
					case 'X':
						val = &dx;
						break;
					case 'Y':
						val = &dy;
						break;
				}
				break;
			case 'M':
				if (p[2] == 'X') {
					val = &mx;
					break;
				}
				if (p[2] == 'Y') {
					val = &my;
					break;
				}
				if (p[2] == 'W') {
					val = &mw;
					break;
				}
				if (p[2] == 'H') {
					val = &mh;
					break;
				}
				if (strncmp(p + 2, "FILE=", 5) == 0) {
					p2 = strchr(p + 7, ';');
					if (p2 == NULL)
						goto done;
					if (!mbuf)
						freemask(ctmask);
					mbuf = false;
					ctmask = NULL;
					free(mask);
					mask = strndup(p + 7, p2 - p - 7);
					continue; // Avoid val check
				}
				else if (strncmp(p + 2, "ASK=", 4) == 0) {
					p2 = strchr(p + 6, ';');
					if (p2 == NULL)
						goto done;
					FREE_AND_NULL(mask);
					if (!mbuf)
						freemask(ctmask);
					mbuf = false;
					ctmask = alloc_ciolib_mask(0, 0);
					ctmask->bits = b64_decode_alloc(p + 6, p2 - p + 5, &mlen);
					if (ctmask->bits == NULL)
						goto done;
					continue; // Avoid val check
				}
				else if (strncmp(p + 2, "BUF", 3) == 0) {
					freemask(ctmask);
					ctmask = NULL;
					mbuf = true;
					continue; // Avoid val check
				}
				break;
		}
		if (val == NULL || p[3] != '=')
			break;
		*val = strtoul(p + 4, NULL, 10);
	}

	if (asprintf(&ppmfn, "%s%s", fn, p + 1) == -1)
		goto done;
	ppmp = read_pbm(ppmfn, false);
	if (ppmp == NULL)
		goto done;

	if (sw == 0)
		sw = ppmp->width - sx;
	if (sh == 0)
		sh = ppmp->height - sy;

	if (ctmask != NULL) {
		if (mlen < (sw * sh + 7) / 8)
			goto done;
		if (mw == 0)
			mw = sw;
		if (mh == 0)
			mh = sh;
		if (mlen < (mw * mh + 7) / 8)
			goto done;
		ctmask->width = mw;
		ctmask->height = mh;
	}

	if (mask != NULL) {
Rob Swindell's avatar
Rob Swindell committed
		if (asprintf(&maskfn, "%s%s", fn, (char*)mask) < 0)
			goto done;
	}

	if (maskfn != NULL) {
		freemask(ctmask);
		ctmask = read_pbm(maskfn, true);
		if (ctmask == NULL)
			goto done;
		if (ctmask->width < sw || ctmask->height < sh)
			goto done;
	}

	if (mbuf)
		ctmask = mask_buffer;

	if (ppmp != NULL)
		setpixels(dx, dy, dx + sw - 1, dy + sh - 1, sx, sy, mx, my, ppmp, ctmask);
done:
	free(mask);
	free(maskfn);
	if (!mbuf)
		freemask(ctmask);
	free(ppmfn);
	freepixels(ppmp);
}

static void
load_ppm_str_handler(char *str, size_t slen, char *fn, void *apcd)
{
	char                 *p;
	char                 *ppmfn = NULL;
	struct ciolib_pixels *ppmp = NULL;
	unsigned long         bufnum = 0;
	unsigned long        *val;

	for (p = str + 18; p && *p == ';'; p = strchr(p + 1, ';')) {
		val = NULL;
		switch (p[1]) {
			case 'B':
				val = &bufnum;
				break;
		}
		if (val == NULL || p[2] != '=')
			break;
		*val = strtoul(p + 3, NULL, 10);
	}

	if (bufnum >= sizeof(pixmap_buffer) / sizeof(pixmap_buffer[0]))
		goto done;

	freepixels(pixmap_buffer[bufnum]);
	pixmap_buffer[bufnum] = NULL;

	if (asprintf(&ppmfn, "%s%s", fn, p + 1) == -1)
		goto done;
	ppmp = read_pbm(ppmfn, false);
	if (ppmp == NULL)
		goto done;
	pixmap_buffer[bufnum] = ppmp;
	free(ppmfn);
	return;

done:
	free(ppmfn);
	freepixels(ppmp);
}

static void
load_pbm_str_handler(char *str, size_t slen, char *fn, void *apcd)
{
	char               *p;
	char               *maskfn = NULL;

	p = str + 18;
	if (asprintf(&maskfn, "%s%s", fn, p + 1) == -1)
		goto done;
	freemask(mask_buffer);
	mask_buffer = read_pbm(maskfn, true);

done:
	free(maskfn);
}

static void
copy_pixmap(char *str, size_t slen, char *fn, void *apcd)
{
	unsigned long  x = 0; // Source X
	unsigned long  y = 0; // Source Y
	unsigned long  w = 0; // Source width
	unsigned long  h = 0; // Source height
	unsigned long *val;
	char          *p;
	unsigned long  bufnum = 0;

	for (p = str + 15; p && *p == ';'; p = strchr(p + 1, ';')) {
		val = NULL;
		switch (p[1]) {
			case 'X':
				val = &x;
				break;
			case 'Y':
				val = &y;
				break;
			case 'W':
				val = &w;
				break;
			case 'H':
				val = &h;
				break;
			case 'B':
				val = &bufnum;
				break;
		}
		if (val == NULL || p[2] != '=')
			return;
		*val = strtol(p + 3, NULL, 10);
	}

	if (bufnum >= sizeof(pixmap_buffer) / sizeof(pixmap_buffer[0]))
		return;

	freepixels(pixmap_buffer[bufnum]);
	pixmap_buffer[bufnum] = NULL;
	if (w == 0 || h == 0) {
		struct text_info ti;
		int vmode;
		gettextinfo(&ti);
		vmode = find_vmode(ti.currmode);
		if (vmode == -1)
			return;
		if (w == 0)
			w = vparams[vmode].xres - x;
		if (h == 0)
			h = vparams[vmode].yres - y;
	}
	pixmap_buffer[bufnum] = getpixels(x, y, x + w - 1, y + h - 1, false);
}

static void
paste_pixmap(char *str, size_t slen, char *fn, void *apcd)
{
	unsigned long       sx = 0; // Source X
	unsigned long       sy = 0; // Source Y
	unsigned long       sw = 0; // Source width
	unsigned long       sh = 0; // Source height
	unsigned long       dx = 0; // Destination X
	unsigned long       dy = 0; // Destination Y
	unsigned long       mx = 0; // Destination X
	unsigned long       my = 0; // Destination Y
	unsigned long       mw = 0; // Width of the mask
	unsigned long       mh = 0; // Height of the mask
	unsigned long       bufnum = 0;
	unsigned long      *val;
	void               *mask = NULL;
	struct ciolib_mask *ctmask = NULL;
	char               *maskfn = NULL;
	char               *p;
	char               *p2;
	size_t              mlen = 0;
Deucе's avatar
Deucе committed
	bool                mbuf = false;

	for (p = str + 16; p && *p == ';'; p = strchr(p + 1, ';')) {
		val = NULL;
		switch (p[1]) {
			case 'B':
				val = &bufnum;
				poff = 2;
				break;
			case 'S':
				switch (p[2]) {
					case 'X':
						val = &sx;
						break;
					case 'Y':
						val = &sy;
						break;
					case 'W':
						val = &sw;
						break;
					case 'H':
						val = &sh;
						break;
				}
				break;
			case 'D':
				switch (p[2]) {
					case 'X':
						val = &dx;
						break;
					case 'Y':
						val = &dy;
						break;
				}
				break;
			case 'M':
				if (p[2] == 'X') {
					val = &mx;
					break;
				}
				if (p[2] == 'Y') {
					val = &my;
					break;
				}
				if (p[2] == 'W') {
					val = &mw;
					break;
				}
				if (p[2] == 'H') {
					val = &mh;
					break;
				}
				if (strncmp(p + 2, "FILE=", 5) == 0) {
					p2 = strchr(p + 7, ';');
					if (p2 == NULL)
						p2 = strchr(p + 6, 0);
					if (p2 == NULL)
						goto done;
					if (!mbuf)
						freemask(ctmask);
					ctmask = NULL;
					free(mask);
					mask = strndup(p + 7, p2 - p - 7);
					continue; // Avoid val check
				}
				else if (strncmp(p + 2, "ASK=", 4) == 0) {
					p2 = strchr(p + 6, ';');
					if (p2 == NULL)
						p2 = strchr(p + 6, 0);
					if (p2 == NULL)
						goto done;
					FREE_AND_NULL(mask);
					if (!mbuf)
						freemask(ctmask);
					ctmask = alloc_ciolib_mask(0, 0);
					if (ctmask == NULL)
						goto done;
					ctmask->bits = b64_decode_alloc(p + 6, p2 - p + 5, &mlen);
					if (ctmask->bits == NULL)
						goto done;
					continue; // Avoid val check
				}
				else if (strncmp(p + 2, "BUF", 3) == 0) {
					freemask(ctmask);
					ctmask = NULL;
					mbuf = true;
					continue; // Avoid val check
				}
		if (val == NULL || p[poff] != '=')
			goto done;
		*val = strtoul(p + poff + 1, NULL, 10);
	if (bufnum >= sizeof(pixmap_buffer) / sizeof(pixmap_buffer[0]))
		goto done;

	if (pixmap_buffer[bufnum] == NULL)
		goto done;
	if (sw == 0)
		sw = pixmap_buffer[bufnum]->width - sx;
	if (sh == 0)
		sh = pixmap_buffer[bufnum]->height - sy;

	if (ctmask != NULL) {
		if (mlen < (sw * sh + 7) / 8)
			goto done;
		if (mw == 0)
			mw = sw;
		if (mh == 0)
			mh = sh;
		if (mlen < (mw * mh + 7) / 8)
			goto done;
		ctmask->width = mw;
		ctmask->height = mh;
	}

	if (mask != NULL) {
Rob Swindell's avatar
Rob Swindell committed
		if (asprintf(&maskfn, "%s%s", fn, (char*)mask) < 0)
			goto done;
	}

	if (maskfn != NULL) {
		freemask(ctmask);
		ctmask = read_pbm(maskfn, true);
		free(maskfn);
		if (ctmask == NULL)
			goto done;
	}

	if (mbuf)
		ctmask = mask_buffer;

	setpixels(dx, dy, dx + sw - 1, dy + sh - 1, sx, sy, mx, my, pixmap_buffer[bufnum], ctmask);
done:
	free(mask);
	if (!mbuf)
		freemask(ctmask);
Deucе's avatar
Deucе committed
static void
apc_handler(char *strbuf, size_t slen, void *apcd)
	char            fn[MAX_PATH + 1];
	char            fn_root[MAX_PATH + 1];
	FILE           *f;
	size_t          rc;
	size_t          sz;
	char           *p;
	char           *buf;
	struct bbslist *bbs = apcd;
	glob_t          gl;
	int             i;
	MD5             ctx;
	BYTE            digest[MD5_DIGEST_SIZE];
	unsigned long   slot;
Deucе's avatar
Deucе committed

	if (ansi_replybuf[0])
		conn_send(ansi_replybuf, strlen((char *)ansi_replybuf), 0);
	if (get_cache_fn_base(bbs, fn_root, sizeof(fn_root)) == 0)
		return;
	strcpy(fn, fn_root);
Deucе's avatar
Deucе committed
	if (strncmp(strbuf, "SyncTERM:C;S;", 13) == 0) {
                // Request to save b64 encoded data into the cache directory.
		p = strchr(strbuf + 13, ';');
Deucе's avatar
Deucе committed
		strncat(fn, strbuf + 13, p - strbuf - 13);
		if (!clean_path(fn, sizeof(fn)))
			return;
		buf = b64_decode_alloc(p, slen - (p - strbuf), &rc);
		if (buf == NULL)
		p = strrchr(fn, '/');
		if (p) {
			*p = 0;
			mkpath(fn);
			*p = '/';
		}
		f = fopen(fn, "wb");
		if (f == NULL) {
			free(buf);
			return;
		}
		fwrite(buf, rc, 1, f);
		free(buf);
		fclose(f);
	}
	else if (strncmp(strbuf, "SyncTERM:C;LoadPPM", 18) == 0) {
                // Load PPM into memory buffer
		load_ppm_str_handler(strbuf, slen, fn, apcd);
	}
	else if (strncmp(strbuf, "SyncTERM:C;LoadPBM", 18) == 0) {
                // Load PPM into memory buffer
		load_pbm_str_handler(strbuf, slen, fn, apcd);
	}
	else if (strncmp(strbuf, "SyncTERM:C;L", 12) == 0) {
Deucе's avatar
Deucе committed
                // Cache list
		if ((strbuf[12] != 0) && (strbuf[12] != ';'))
		if (!clean_path(fn, sizeof(fn))) {
			conn_send("\x1b_SyncTERM:C;L\n\x1b\\", 17, 0);
			return;
		}
		if (!isdir(fn)) {
			conn_send("\x1b_SyncTERM:C;L\n\x1b\\", 17, 0);
			return;
		}
		if (slen == 12)
			p = "*";
		else
Deucе's avatar
Deucе committed
			p = strbuf + 13;
		strcat(fn, p);
		conn_send("\x1b_SyncTERM:C;L\n", 15, 0);
		rc = glob(fn, GLOB_MARK, NULL, &gl);
		if (rc != 0) {
			conn_send("\x1b\\", 2, 0);
			return;
		}
Deucе's avatar
Deucе committed
		buf = malloc(1024 * 32);
Deucе's avatar
Deucе committed
		for (i = 0; i < gl.gl_pathc; i++) {
                        /* Skip . and .. along with any fuckery */
			if (!clean_path(gl.gl_pathv[i], MAX_PATH))
				continue;
			p = getfname(gl.gl_pathv[i]);
			conn_send(p, strlen(p), 0);
			conn_send("\t", 1, 0);
			f = fopen(gl.gl_pathv[i], "rb");
			if (f) {
				MD5_open(&ctx);
				while (!feof(f)) {
Deucе's avatar
Deucе committed
					rc = fread(buf, 1, 1024 * 32, f);
					if (rc > 0)
						MD5_calc(digest, buf, rc);
				}
				fclose(f);
				MD5_hex(buf, digest);
				conn_send(buf, strlen(buf), 0);
			}
			conn_send("\n", 1, 0);
		}
		free(buf);
		conn_send("\x1b\\", 2, 0);
		globfree(&gl);
	}
	else if (strncmp(strbuf, "SyncTERM:C;SetFont;", 19) == 0) {
Deucе's avatar
Deucе committed
		slot = strtoul(strbuf + 19, &p, 10);
		if (slot < CONIO_FIRST_FREE_FONT)
			return;
		if (slot > 255)
			return;
		if (*p != ';')
			return;
		p++;
		strcat(fn, p);
		if (!clean_path(fn, sizeof(fn)))
			return;
		if (!fexist(fn))
			return;
		sz = flength(fn);
		f = fopen(fn, "rb");
		if (f) {
			buf = malloc(sz);
			if (buf == NULL) {
				fclose(f);
				return;
			}
			if (fread(buf, sz, 1, f) != 1) {
				fclose(f);
				free(buf);
				return;
			}
Deucе's avatar
Deucе committed
			switch (sz) {
				case 4096:
					FREE_AND_NULL(conio_fontdata[cterm->font_slot].eight_by_sixteen);
Deucе's avatar
Deucе committed
					conio_fontdata[cterm->font_slot].eight_by_sixteen = buf;
					FREE_AND_NULL(conio_fontdata[cterm->font_slot].desc);
Deucе's avatar
Deucе committed
					conio_fontdata[cterm->font_slot].desc = strdup("Cached Font");
					break;
				case 3584:
					FREE_AND_NULL(conio_fontdata[cterm->font_slot].eight_by_fourteen);
Deucе's avatar
Deucе committed
					conio_fontdata[cterm->font_slot].eight_by_fourteen = buf;
					FREE_AND_NULL(conio_fontdata[cterm->font_slot].desc);
Deucе's avatar
Deucе committed
					conio_fontdata[cterm->font_slot].desc = strdup("Cached Font");
					break;
				case 2048:
					FREE_AND_NULL(conio_fontdata[cterm->font_slot].eight_by_eight);
Deucе's avatar
Deucе committed
					conio_fontdata[cterm->font_slot].eight_by_eight = buf;
					FREE_AND_NULL(conio_fontdata[cterm->font_slot].desc);
Deucе's avatar
Deucе committed
					conio_fontdata[cterm->font_slot].desc = strdup("Cached Font");
	else if (strncmp(strbuf, "SyncTERM:C;DrawPPM", 18) == 0) {
                // Request to draw a 255 max PPM file from cache
		//SyncTERM:C;DrawPPM;SX=x;SY=y;SW=w;SH=hDX=x;Dy=y;
		draw_ppm_str_handler(strbuf, slen, fn, apcd);
	}
	else if (strncmp(strbuf, "SyncTERM:P;Copy", 15) == 0) {
		copy_pixmap(strbuf, slen, fn, apcd);
	}
	else if (strncmp(strbuf, "SyncTERM:P;Paste", 16) == 0) {
		paste_pixmap(strbuf, slen, fn, apcd);
	}

	// TODO: Copy PBM mask to memory
Deucе's avatar
Deucе committed
void
mouse_state_change(int type, int action, void *pms)
	struct mouse_state *ms = (struct mouse_state *)pms;
	if (ms->mode == MM_RIP)
		return;
		if (type == ms->mode) {
			ms->mode = MM_OFF;
Deucе's avatar
Deucе committed
		if (type == MS_SGR_SET)
		switch (type) {
			case MM_X10:
			case MM_NORMAL_TRACKING:
			case MM_BUTTON_EVENT_TRACKING:
			case MM_ANY_EVENT_TRACKING:
				ms->mode = type;
				setup_mouse_events(ms);
				break;
			case MS_SGR_SET:
				ms->flags |= MS_FLAGS_SGR;
Deucе's avatar
Deucе committed
int
mouse_state_query(int type, void *pms)
	struct mouse_state *ms = (struct mouse_state *)pms;

	if (type == MS_SGR_SET)
		return ms->flags & MS_FLAGS_SGR;
	return type == ms->mode;
}
/* Win32 doesn't have ffs()... just use this everywhere. */
static int
my_ffs(int mask)
{
	int bit;

	if (mask == 0)
Deucе's avatar
Deucе committed
		return 0;
	for (bit = 1; !(mask & 1); bit++)
		mask = (unsigned int)mask >> 1;
Deucе's avatar
Deucе committed
	return bit;
Deucе's avatar
Deucе committed
static int
fill_mevent(char *buf, size_t bufsz, struct mouse_event *me, struct mouse_state *ms)
Deucе's avatar
Deucе committed
	int  button;
	int  x = me->startx - cterm->x + 1;
	int  y = me->starty - cterm->y + 1;
	int  bit;
	int  ret;
Deucе's avatar
Deucе committed
        // TODO: Get modifier keys too...
	if (me->event == CIOLIB_MOUSE_MOVE) {
		if ((me->kbsm & me->bstate) == 0) {
			if (ms->mode == MM_BUTTON_EVENT_TRACKING)
				return 0;
		}
		bit = my_ffs(me->kbsm & me->bstate);
		button = bit - 1;
		release = false;
	}
	else {
		button = CIOLIB_BUTTON_NUMBER(me->event);
Deucе's avatar
Deucе committed
		release = (me->event == CIOLIB_BUTTON_RELEASE(button));
Deucе's avatar
Deucе committed
	else if (button >= 3)
	if (me->event == CIOLIB_MOUSE_MOVE)
		button += 32;
	if ((ms->flags & MS_FLAGS_SGR) == 0) {
		if (bufsz < 6)
			return 0;
		if (release)
			button = 3;
		x--;
		y--;
		if (x < 0)
			x = 0;
		if (x > 222)
			x = 222;
		if (y < 0)
			y = 0;
		if (y > 222)
			y = 222;
		buf[0] = '\x1b';
		buf[1] = '[';
		buf[2] = 'M';
Deucе's avatar
Deucе committed
		buf[3] = ' ' + button;
		buf[4] = '!' + x;
		buf[5] = '!' + y;
		return 6;
	}
	else {
		ret = snprintf(buf, bufsz, "\x1b[<%d;%d;%d%c", button, x, y, release ? 'm' : 'M');
		if (ret > bufsz)
			return 0;
		return ret;
	}
	uint8_t *p;
	uint8_t *p2;
	int      oldfont;
	p = (unsigned char *)getcliptext();
Deucе's avatar
Deucе committed
	if (p != NULL) {
		p2 = p;
		oldfont = getfont(1);
		setfont(cterm->altfont[0], false, 1);
		p = (unsigned char *)utf8_to_cp(getcodepage(), p, '\x00', strlen((char *)p), NULL);
		setfont(oldfont, false, 1);
		free(p2);
		if (p != NULL) {
			if (cterm->extattr & CTERM_EXTATTR_BRACKETPASTE)
				conn_send("\x1b[200~", 6, 0);
Deucе's avatar
Deucе committed
			for (p2 = p; *p2; p2++) {
				if (*p2 == '\n') {
                                        /* If previous char was not \r, send a \r */
					if ((p2 == p) || (*(p2 - 1) != '\r'))
						conn_send("\r", 1, 0);
				}
				else if (*p2 == '\x1b') {
					// If we're using bracked paste, strip paste end sequence
					// TODO: Do we generally want to disable all ESC chars?
						p2 += 5;
					else
						conn_send(p2, 1, 0);
				}
Deucе's avatar
Deucе committed
				else {
					conn_send(p2, 1, 0);
			if (cterm->extattr & CTERM_EXTATTR_BRACKETPASTE)
				conn_send("\x1b[201~", 6, 0);
Deucе's avatar
Deucе committed
bool
doterm(struct bbslist *bbs)
	unsigned char     ch[2];
	char              mouse_buf[64];
	unsigned char     outbuf[OUTBUF_SIZE];
	size_t            outbuf_size = 0;
	int               key;
	int               i, j;
	struct vmem_cell *vc;
	BYTE              zrqinit[] = {ZDLE, ZHEX, '0', '0', 0};  /* for Zmodem auto-downloads */
	BYTE              zrinit[] = {ZDLE, ZHEX, '0', '1', 0};   /* for Zmodem auto-uploads */
	BYTE              zrqbuf[sizeof(zrqinit)];
	int               inch = NOINP;
	long double       nextchar = 0;
	long double       lastchar = 0;
	long double       thischar = 0;
	int               speed;
	int               oldmc;
	int               updated = false;
	bool              sleep;
	size_t            remain;
	struct text_info  txtinfo;
Deucе's avatar
Deucе committed

#ifndef WITHOUT_OOII
	BYTE              ooii_buf[256];
	BYTE              ooii_init1[] =
	    "\xdb\b \xdb\b \xdb\b[\xdb\b[\xdb\b \xdb\bM\xdb\ba\xdb\bi\xdb\bn\xdb\bt\xdb\be\xdb\bn\xdb\ba\xdb\bn\xdb\bc\xdb\be\xdb\b \xdb\bC\xdb\bo\xdb\bm\xdb\bp\xdb\bl\xdb\be\xdb\bt\xdb\be\xdb\b \xdb\b]\xdb\b]\xdb\b \b\r\n\r\n\r\n\x1b[0;0;36mDo you have the Overkill Ansiterm installed? (y/N)  \xe9 ";            /*
                                                                                                                                                                                                                                                                                                                          *
                                                                                                                                                                                                                                                                                                                          * for
                                                                                                                                                                                                                                                                                                                          *
                                                                                                                                                                                                                                                                                                                          * OOII
                                                                                                                                                                                                                                                                                                                          *
                                                                                                                                                                                                                                                                                                                          * auto-enable
                                                                                                                                                                                                                                                                                                                          */
	BYTE ooii_init2[] =
	    "\xdb\b \xdb\b \xdb\b[\xdb\b[\xdb\b \xdb\bM\xdb\ba\xdb\bi\xdb\bn\xdb\bt\xdb\be\xdb\bn\xdb\ba\xdb\bn\xdb\bc\xdb\be\xdb\b \xdb\bC\xdb\bo\xdb\bm\xdb\bp\xdb\bl\xdb\be\xdb\bt\xdb\be\xdb\b \xdb\b]\xdb\b]\xdb\b \b\r\n\r\n\x1b[0m\x1b[2J\r\n\r\n\x1b[0;1;30mHX Force retinal scan in progress ... \x1b[0;0;30m"; /*
                                                                                                                                                                                                                                                                                                                          *
                                                                                                                                                                                                                                                                                                                          * for
                                                                                                                                                                                                                                                                                                                          *
                                                                                                                                                                                                                                                                                                                          * OOII
                                                                                                                                                                                                                                                                                                                          *
                                                                                                                                                                                                                                                                                                                          * auto-enable
                                                                                                                                                                                                                                                                                                                          */
Deucе's avatar
Deucе committed
	int                ooii_mode = 0;
	recv_byte_buffer_len = recv_byte_buffer_pos = 0;
	struct mouse_state ms = {0};
Deucе's avatar
Deucе committed
	int                speedwatch = 0;
	freepixels(pixmap_buffer[0]);
	freepixels(pixmap_buffer[1]);
	pixmap_buffer[0] = NULL;
	pixmap_buffer[1] = NULL;
	freemask(mask_buffer);
	mask_buffer = NULL;
	gettextinfo(&txtinfo);
Deucе's avatar
Deucе committed
	if ((bbs->conn_type == CONN_TYPE_SERIAL) || (bbs->conn_type == CONN_TYPE_SERIAL_NORTS))
deuce's avatar
deuce committed
		speed = 0;
	else
		speed = bbs->bpsrate;
	log_level = bbs->xfer_loglevel;
	conn_api.log_level = bbs->telnet_loglevel;
Deucе's avatar
Deucе committed
	vc = realloc(scrollback_buf, term.width * sizeof(*vc) * settings.backlines);
	if (vc != NULL) {
		scrollback_buf = vc;
		memset(scrollback_buf, 0, term.width * sizeof(*vc) * settings.backlines);
Deucе's avatar
Deucе committed
	else {
		FREE_AND_NULL(scrollback_buf);
Deucе's avatar
Deucе committed
	scrollback_lines = 0;
	scrollback_mode = txtinfo.currmode;
	cterm = cterm_init(term.height,
	        term.width,
	        term.x - 1,
	        term.y - 1,
	        settings.backlines,
	        term.width,
	        scrollback_buf,
	        get_emulation(bbs));
Deucе's avatar
Deucе committed
	if (!cterm)
		return false;
	cterm->apc_handler = apc_handler;
	cterm->apc_handler_data = bbs;
	cterm->mouse_state_change = mouse_state_change;
	cterm->mouse_state_change_cbdata = &ms;
	cterm->mouse_state_query = mouse_state_query;
	cterm->mouse_state_query_cbdata = &ms;
Deucе's avatar
Deucе committed
	scrollback_cols = term.width;
	cterm->music_enable = bbs->music;
	ch[1] = 0;
	zrqbuf[0] = 0;
#ifndef WITHOUT_OOII
Deucе's avatar
Deucе committed
	ooii_buf[0] = 0;
Deucе's avatar
Deucе committed
        /* Main input loop */
	oldmc = hold_update;
	showmouse();
	if (bbs->rip)
		ms.mode = MM_RIP;
	setup_mouse_events(&ms);
Deucе's avatar
Deucе committed
	for (; !quitting;) {
		hold_update = true;
		sleep = true;
			update_status(bbs,
			    (bbs->conn_type == CONN_TYPE_SERIAL || bbs->conn_type == CONN_TYPE_SERIAL_NORTS) ? bbs->bpsrate : speed,
Deucе's avatar
Deucе committed
			    ooii_mode);
Deucе's avatar
Deucе committed
		for (remain = count_data_waiting() /* Hack for connection check */ + (!is_connected(NULL)); remain;
		    remain--) {
			if (speed)
				thischar = xp_timer();

			if ((!speed) || (thischar < lastchar) /* Wrapped */ || (thischar >= nextchar)) {
                                /* Get remote input */
				inch = recv_byte(NULL, 0);

				switch (inch) {
Deucе's avatar
Deucе committed
						if (!is_connected(NULL)) {
Deucе's avatar
Deucе committed
							hold_update = oldmc;
							if (!bbs->hidepopups)
								uifcmsg("Disconnected",
								    "`Disconnected`\n\nRemote host dropped connection");
							check_exit(false);
Deucе's avatar
Deucе committed
							cterm_clearscreen(cterm, cterm->attr); /* Clear screen into
                                                                                                * scrollback */
							scrollback_lines = cterm->backpos;
							cterm_end(cterm, 0);
Deucе's avatar
Deucе committed
							cterm = NULL;
Deucе's avatar
Deucе committed
							return false;
Deucе's avatar
Deucе committed
						if (speed) {
Deucе's avatar
Deucе committed
							nextchar = lastchar + 1 / (long double)(speed / 10);
						switch (speedwatch) {
							case 0:
								if (inch == '\x1b')
									speedwatch = 1;
								break;
							case 1:
								if (inch == '[')
									speedwatch = 2;
								else
									speedwatch = 0;
								break;
							case 2:
Deucе's avatar
Deucе committed
								if ((inch == '0') || (inch == '1'))
									speedwatch = 3;
								else
									speedwatch = 0;
								break;
							case 3:
								if (inch == ';')
									speedwatch = 4;
								else
									speedwatch = 0;
								break;
							case 4:
Deucе's avatar
Deucе committed
								if ((inch >= '0') && (inch <= '9'))
									break;
								if (inch == '*')
									speedwatch = 5;
								else
									speedwatch = 0;
								break;
							case 5:
								if (inch == 'r')
									remain = 1;
								speedwatch = 0;
								break;
						}
						j = strlen((char *)zrqbuf);
Deucе's avatar
Deucе committed
						if ((inch == zrqinit[j]) || (inch == zrinit[j])) {
							zrqbuf[j] = inch;
							zrqbuf[++j] = 0;
							if (j == sizeof(zrqinit) - 1) {
                                                                /* Have full sequence (Assumes
                                                                 * zrinit and zrqinit are same
                                                                 * length */
								suspend_rip(true);
								if (!strcmp((char *)zrqbuf, (char *)zrqinit))
deuce's avatar
deuce committed
									zmodem_download(bbs);
								else
									begin_upload(bbs, true, inch);
								suspend_rip(false);
Deucе's avatar
Deucе committed
								zrqbuf[0] = 0;
								remain = 1;
Deucе's avatar
Deucе committed
						else {
							zrqbuf[0] = 0;
						}
#ifndef WITHOUT_OOII
Deucе's avatar
Deucе committed
						if (ooii_mode) {
							if (ooii_buf[0] == 0) {
								if (inch == 0xab) {
									ooii_buf[0] = inch;
									ooii_buf[1] = 0;
									continue;
								}
							}
							else { /* Already have the start of the sequence */
								j = strlen((char *)ooii_buf);
Deucе's avatar
Deucе committed
								if (j + 1 >= sizeof(ooii_buf))
Deucе's avatar
Deucе committed
								ooii_buf[j++] = inch;
								ooii_buf[j] = 0;
								if (inch == '|') {
									if (handle_ooii_code(ooii_buf, &ooii_mode,
									    (unsigned char *)ansi_replybuf,
									    sizeof(ansi_replybuf))) {
Deucе's avatar
Deucе committed
										ooii_mode = 0;
Deucе's avatar
Deucе committed
									if (ansi_replybuf[0])
										conn_send(ansi_replybuf,
										    strlen((char *)ansi_replybuf), 0);
Deucе's avatar
Deucе committed
									ooii_buf[0] = 0;
							j = strlen((char *)ooii_buf);
Deucе's avatar
Deucе committed
							if (inch == ooii_init1[j]) {
								ooii_buf[j++] = inch;
								ooii_buf[j] = 0;
								if (ooii_init1[j] == 0) {
									if (strcmp((char *)ooii_buf,
									    (char *)ooii_init1) == 0) {
Deucе's avatar
Deucе committed
										ooii_mode = 1;
Deucе's avatar
Deucе committed
									ooii_buf[0] = 0;
deuce's avatar
deuce committed
								}
							}
Deucе's avatar
Deucе committed
							else if (inch == ooii_init2[j]) {
								ooii_buf[j++] = inch;
								ooii_buf[j] = 0;
								if (ooii_init2[j] == 0) {
									if (strcmp((char *)ooii_buf,
									    (char *)ooii_init2) == 0) {
Deucе's avatar
Deucе committed
										ooii_mode = 2;
Deucе's avatar
Deucе committed
									ooii_buf[0] = 0;
deuce's avatar
deuce committed
								}
Deucе's avatar
Deucе committed
							else {
								ooii_buf[0] = 0;
							}
Deucе's avatar
Deucе committed
#endif /* ifndef WITHOUT_OOII */
						if (outbuf_size >= sizeof(outbuf))
Deucе's avatar
Deucе committed
						outbuf[outbuf_size++] = inch;
Deucе's avatar
Deucе committed
					sleep = false;
Deucе's avatar
Deucе committed
		if (updated) {
			hold_update = false;
deuce's avatar
deuce committed
			gotoxy(wherex(), wherey());
deuce's avatar
deuce committed
		}
Deucе's avatar
Deucе committed
		hold_update = oldmc;
Deucе's avatar
Deucе committed
                /* Get local input */
		while (quitting || rip_kbhit()) {
deuce's avatar
deuce committed
			struct mouse_event mevent;
Deucе's avatar
Deucе committed
			updated = true;
			gotoxy(wherex(), wherey());
Deucе's avatar
Deucе committed
			if (quitting) {
Deucе's avatar
Deucе committed
			}
				key = rip_getch();
				if (key == -1)
					continue;
				if (key > 0xff) {
Deucе's avatar
Deucе committed
					if (cterm->doorway_mode && ((key & 0xff) == 0) && (key != 0x2c00) /* ALT-Z */) {
						ch[0] = 0;
						ch[1] = key >> 8;
						conn_send(ch, 2, 0);
Deucе's avatar
Deucе committed
                        /*
                         * These keys are SyncTERM control keys
                         * key is set to zero if consumed
                         */
			switch (key) {
				case CIO_KEY_MOUSE:
					getmouse(&mevent);
Deucе's avatar
Deucе committed
					switch (mevent.event) {
						case CIOLIB_BUTTON_1_PRESS:
						case CIOLIB_BUTTON_1_RELEASE:
						case CIOLIB_BUTTON_2_PRESS:
						case CIOLIB_BUTTON_2_RELEASE:
						case CIOLIB_BUTTON_3_PRESS:
						case CIOLIB_BUTTON_3_RELEASE:
							conn_send(mouse_buf,
							    fill_mevent(mouse_buf, sizeof(mouse_buf), &mevent, &ms), 0);
						case CIOLIB_BUTTON_4_PRESS:
						case CIOLIB_BUTTON_5_PRESS:
Deucе's avatar
Deucе committed
							if ((ms.mode != 9) && (ms.mode != 0)) {
								conn_send(mouse_buf,
								    fill_mevent(mouse_buf, sizeof(mouse_buf), &mevent,
								    &ms), 0);
								break;
							}
							viewscroll();
							setup_mouse_events(&ms);
							break;
						case CIOLIB_BUTTON_1_DRAG_START:
						case CIOLIB_BUTTON_2_CLICK:
						case CIOLIB_BUTTON_3_CLICK:
								conn_send(mouse_buf,
								    fill_mevent(mouse_buf, sizeof(mouse_buf), &mevent,
								    &ms), 0);
deuce's avatar
deuce committed
					key = 0;
Deucе's avatar
Deucе committed
				case CIO_KEY_SHIFT_IC: /* Shift-Insert - Paste */
Deucе's avatar
Deucе committed
				case 0x3000: /* ALT-B - Scrollback */
					viewscroll();
					showmouse();
deuce's avatar
deuce committed
					key = 0;
Deucе's avatar
Deucе committed
				case 0x2e00: /* ALT-C - Capture */
					capture_control(bbs);
					showmouse();
deuce's avatar
deuce committed
					key = 0;
Deucе's avatar
Deucе committed
				case 0x2000: /* ALT-D - Download */
deuce's avatar
deuce committed
					begin_download(bbs);
					showmouse();
deuce's avatar
deuce committed
					key = 0;
Deucе's avatar
Deucе committed
				case 0x1200: /* ALT-E */
				{
					char                  title[LIST_NAME_MAX + 13];
					struct ciolib_screen *savscrn;
Deucе's avatar
Deucе committed
					savscrn = savescreen();
					setfont(0, false, 1);
					setfont(0, false, 2);
					setfont(0, false, 3);
					setfont(0, false, 4);
					show_bbslist(bbs->name, true);
					sprintf(title, "SyncTERM - %s\n", bbs->name);
					settitle(title);
					uifcbail();
					setup_mouse_events(&ms);
					restorescreen(savscrn);
					freescreen(savscrn);
					if ((cterm->scrollback != scrollback_buf)
					    || (cterm->backlines != settings.backlines)) {
						cterm->scrollback = scrollback_buf;
						cterm->backlines = settings.backlines;
						if (cterm->backpos > cterm->backlines)
							cterm->backpos = cterm->backlines;
Deucе's avatar
Deucе committed
					showmouse();
					_setcursortype(_NORMALCURSOR);
					key = 0;
				}
				break;
				case 0x2100: /* ALT-F */
deuce's avatar
deuce committed
					font_control(bbs, cterm);
					showmouse();
deuce's avatar
deuce committed
					key = 0;
Deucе's avatar
Deucе committed
				case 0x2600: /* ALT-L */
					if ((bbs->conn_type != CONN_TYPE_RLOGIN)
					    && (bbs->conn_type != CONN_TYPE_RLOGIN_REVERSED)
					    && (bbs->conn_type != CONN_TYPE_SSH)) {
						if (bbs->conn_type != CONN_TYPE_SSHNA) {
Deucе's avatar
Deucе committed
							if (bbs->user[0]) {
								conn_send(bbs->user, strlen(bbs->user), 0);
								conn_send(
									cterm->emulation == CTERM_EMULATION_ATASCII ? "\x9b" : "\r",
									1,
									0);
Deucе's avatar
Deucе committed
						if (bbs->password[0]) {
							conn_send(bbs->password, strlen(bbs->password), 0);
							conn_send(
								cterm->emulation == CTERM_EMULATION_ATASCII ? "\x9b" : "\r",
								1,
								0);
Deucе's avatar
Deucе committed
					if (bbs->syspass[0]) {
						conn_send(bbs->syspass, strlen(bbs->syspass), 0);
						conn_send(cterm->emulation == CTERM_EMULATION_ATASCII ? "\x9b" : "\r",
						    1,
						    0);
deuce's avatar
deuce committed
					key = 0;
Deucе's avatar
Deucе committed
				case 0x3200: /* ALT-M */
deuce's avatar
deuce committed
					music_control(bbs);
					showmouse();
deuce's avatar
deuce committed
					key = 0;
deuce's avatar
deuce committed
					break;
Deucе's avatar
Deucе committed
				case 0x1600: /* ALT-U - Upload */
					begin_upload(bbs, false, inch);
					showmouse();
deuce's avatar
deuce committed
					key = 0;
Deucе's avatar
Deucе committed
				case 17: /* CTRL-Q */
					if ((cio_api.mode != CIOLIB_MODE_CURSES)
					    && (cio_api.mode != CIOLIB_MODE_CURSES_ASCII)
					    && (cio_api.mode != CIOLIB_MODE_CURSES_IBM)
					    && (cio_api.mode != CIOLIB_MODE_ANSI))
						break;
					if ((cio_api.mode != CIOLIB_MODE_CURSES)
					    && (cio_api.mode != CIOLIB_MODE_CURSES_ASCII)
					    && (cio_api.mode != CIOLIB_MODE_CURSES_IBM)
					    && (cio_api.mode != CIOLIB_MODE_ANSI)) {
                                                        /* FALLTHROUGH for curses/ansi modes */
							case 0x2d00: /* Alt-X - Exit */
							case CIO_KEY_QUIT:
								if (!check_exit(true))
									break;
Deucе's avatar
Deucе committed

                                // Fallthrough
				case 0x2300: /* Alt-H - Hangup */
				{
					struct ciolib_screen *savscrn;
Deucе's avatar
Deucе committed
					savscrn = savescreen();
					setfont(0, false, 1);
					setfont(0, false, 2);
					setfont(0, false, 3);
					setfont(0, false, 4);
					if (quitting
					    || confirm("Disconnect... Are you sure?",
					    "Selecting Yes closes the connection\n")) {
deuce's avatar
deuce committed
						freescreen(savscrn);
						cterm_clearscreen(cterm, cterm->attr); /* Clear screen into
                                                                                        * scrollback */
Deucе's avatar
Deucе committed
						scrollback_lines = cterm->backpos;
						cterm_end(cterm, 0);
						cterm = NULL;
						conn_close();
						hidemouse();
						hold_update = oldmc;
						return key == 0x2d00 /* Alt-X? */ || key == CIO_KEY_QUIT;
deuce's avatar
deuce committed
					}
Deucе's avatar
Deucе committed
					restorescreen(savscrn);
					freescreen(savscrn);
					setup_mouse_events(&ms);
					showmouse();
				}
deuce's avatar
deuce committed
					key = 0;
deuce's avatar
deuce committed
					break;
Deucе's avatar
Deucе committed
				case 19: /* CTRL-S */
					if ((cio_api.mode != CIOLIB_MODE_CURSES)
					    && (cio_api.mode != CIOLIB_MODE_CURSES_IBM)
					    && (cio_api.mode != CIOLIB_MODE_ANSI))
						break;

                                /* FALLTHROUGH for curses/ansi modes */
Deucе's avatar
Deucе committed
				case 0x2c00: /* ALT-Z */
					if (bbs->hidepopups)
						break;
					i = wherex();
					j = wherey();
					switch (syncmenu(bbs, &speed)) {
Deucе's avatar
Deucе committed
							cterm_clearscreen(cterm, cterm->attr); /* Clear screen into
                                                                                                * scrollback */
							scrollback_lines = cterm->backpos;
							cterm_end(cterm, 0);
Deucе's avatar
Deucе committed
							cterm = NULL;
							conn_close();
							hidemouse();
Deucе's avatar
Deucе committed
							hold_update = oldmc;
							return false;
							begin_upload(bbs, false, inch);
							capture_control(bbs);
							break;
						case 8:
deuce's avatar
deuce committed
							music_control(bbs);
							break;
						case 9:
deuce's avatar
deuce committed
							font_control(bbs, cterm);
Deucе's avatar
Deucе committed
							cterm->doorway_mode = !cterm->doorway_mode;
deuce's avatar
deuce committed
							break;
deuce's avatar
deuce committed
						case 11:
deuce's avatar
deuce committed
							ooii_mode++;
Deucе's avatar
Deucе committed
							if (ooii_mode > MAX_OOII_MODE) {
Deucе's avatar
Deucе committed
								ooii_mode = 0;
Deucе's avatar
Deucе committed
							else {
Deucе's avatar
Deucе committed
							}
Deucе's avatar
Deucе committed
							cterm_clearscreen(cterm, cterm->attr); /* Clear screen into
                                                                                                * scrollback */
							scrollback_lines = cterm->backpos;
							cterm_end(cterm, 0);
Deucе's avatar
Deucе committed
							cterm = NULL;
							hidemouse();
Deucе's avatar
Deucе committed
							hold_update = oldmc;
							return true;
Deucе's avatar
Deucе committed
						{
							struct ciolib_screen *savscrn;
							char                  title[LIST_NAME_MAX + 13];
Deucе's avatar
Deucе committed

							savscrn = savescreen();
							setfont(0, false, 1);
							setfont(0, false, 2);
							setfont(0, false, 3);
							setfont(0, false, 4);
							show_bbslist(bbs->name, true);
							sprintf(title, "SyncTERM - %s\n", bbs->name);
							settitle(title);
							restorescreen(savscrn);
							freescreen(savscrn);
						}
						break;
					showmouse();
Deucе's avatar
Deucе committed
					gotoxy(i, j);
deuce's avatar
deuce committed
					key = 0;
Deucе's avatar
Deucе committed
				case 0x9800: /* ALT-Up */
					if ((bbs->conn_type != CONN_TYPE_SERIAL)
					    && (bbs->conn_type != CONN_TYPE_SERIAL_NORTS)) {
						if (speed)
							speed = rates[get_rate_num(speed) + 1];
Deucе's avatar
Deucе committed
							speed = rates[0];
Deucе's avatar
Deucе committed
				case 0xa000: /* ALT-Down */
					if ((bbs->conn_type != CONN_TYPE_SERIAL)
					    && (bbs->conn_type != CONN_TYPE_SERIAL_NORTS)) {
						i = get_rate_num(speed);
						if (i == 0)
							speed = 0;
Deucе's avatar
Deucе committed
							speed = rates[i - 1];
deuce's avatar
deuce committed
			}
Deucе's avatar
Deucе committed
			if (key && (cterm->emulation == CTERM_EMULATION_ATASCII)) {
                                /* Translate keys to ATASCII */
				switch (key) {
					case '\r':
					case '\n':
Deucе's avatar
Deucе committed
						ch[0] = 155;
						conn_send(ch, 1, 0);
						break;
					case CIO_KEY_DOWN:
Deucе's avatar
Deucе committed
						ch[0] = 29;
						conn_send(ch, 1, 0);
Deucе's avatar
Deucе committed
					case CIO_KEY_DC: /* "Delete" key */
					case '\b':       /* Backspace */
						ch[0] = 126;
						conn_send(ch, 1, 0);
						break;
					case CIO_KEY_RIGHT:
Deucе's avatar
Deucе committed
						ch[0] = 31;
						conn_send(ch, 1, 0);
						break;
					case CIO_KEY_UP:
Deucе's avatar
Deucе committed
						ch[0] = 28;
						conn_send(ch, 1, 0);
						break;
					case CIO_KEY_LEFT:
Deucе's avatar
Deucе committed
						ch[0] = 30;
						conn_send(ch, 1, 0);
						break;
					case '\t':
Deucе's avatar
Deucе committed
						ch[0] = 127;
						conn_send(ch, 1, 0);
Deucе's avatar
Deucе committed
					case 96: /* No backtick */
deuce's avatar
deuce committed
						break;
					default:
Deucе's avatar
Deucе committed
						if (key < 256) {
                                                        /* ASCII Translation */
							if (key < 123) {
								ch[0] = key;
								conn_send(ch, 1, 0);
Deucе's avatar
Deucе committed
			else if (key && (cterm->emulation == CTERM_EMULATION_PETASCII)) {
                                /* Translate keys to PETSCII */
				switch (key) {
					case '\r':
					case '\n':
Deucе's avatar
Deucе committed
						ch[0] = 13;
						conn_send(ch, 1, 0);
						break;
deuce's avatar
deuce committed
					case CIO_KEY_DOWN:
Deucе's avatar
Deucе committed
						ch[0] = 17;
						conn_send(ch, 1, 0);
deuce's avatar
deuce committed
						break;
					case CIO_KEY_HOME:
Deucе's avatar
Deucе committed
						ch[0] = 19;
						conn_send(ch, 1, 0);
deuce's avatar
deuce committed
						break;
Deucе's avatar
Deucе committed
						ch[0] = 147; /* Clear / Shift-Home */
						conn_send(ch, 1, 0);
deuce's avatar
deuce committed
					case '\b':
Deucе's avatar
Deucе committed
					case CIO_KEY_DC: /* "Delete" key */
						ch[0] = 20;
						conn_send(ch, 1, 0);
deuce's avatar
deuce committed
						break;
					case CIO_KEY_RIGHT:
Deucе's avatar
Deucе committed
						ch[0] = 29;
						conn_send(ch, 1, 0);
deuce's avatar
deuce committed
						break;
					case CIO_KEY_F(1):
Deucе's avatar
Deucе committed
						ch[0] = 133;
						conn_send(ch, 1, 0);
deuce's avatar
deuce committed
						break;
					case CIO_KEY_F(3):
Deucе's avatar
Deucе committed
						ch[0] = 134;
						conn_send(ch, 1, 0);
deuce's avatar
deuce committed
						break;
					case CIO_KEY_F(5):
Deucе's avatar
Deucе committed
						ch[0] = 135;
						conn_send(ch, 1, 0);
deuce's avatar
deuce committed
						break;
					case CIO_KEY_F(7):
Deucе's avatar
Deucе committed
						ch[0] = 136;
						conn_send(ch, 1, 0);
deuce's avatar
deuce committed
						break;
					case CIO_KEY_F(2):
Deucе's avatar
Deucе committed
						ch[0] = 137;
						conn_send(ch, 1, 0);
deuce's avatar
deuce committed
						break;
					case CIO_KEY_F(4):
Deucе's avatar
Deucе committed
						ch[0] = 138;
						conn_send(ch, 1, 0);
deuce's avatar
deuce committed
						break;
					case CIO_KEY_F(6):
Deucе's avatar
Deucе committed
						ch[0] = 139;
						conn_send(ch, 1, 0);
deuce's avatar
deuce committed
						break;
					case CIO_KEY_F(8):
Deucе's avatar
Deucе committed
						ch[0] = 140;
						conn_send(ch, 1, 0);
deuce's avatar
deuce committed
						break;
					case CIO_KEY_UP:
Deucе's avatar
Deucе committed
						ch[0] = 145;
						conn_send(ch, 1, 0);
deuce's avatar
deuce committed
						break;
					case CIO_KEY_IC:
Deucе's avatar
Deucе committed
						ch[0] = 148;
						conn_send(ch, 1, 0);
deuce's avatar
deuce committed
						break;
					case CIO_KEY_LEFT:
Deucе's avatar
Deucе committed
						ch[0] = 157;
						conn_send(ch, 1, 0);
deuce's avatar
deuce committed
						break;
					default:
Deucе's avatar
Deucе committed
						if (key < 256) {
							ch[0] = key;
							conn_send(ch, 1, 0);
Deucе's avatar
Deucе committed
			else if (key) {
				switch (key) {
deuce's avatar
deuce committed
					case CIO_KEY_LEFT:
Deucе's avatar
Deucе committed
						conn_send("\033[D", 3, 0);
deuce's avatar
deuce committed
						break;
					case CIO_KEY_RIGHT:
Deucе's avatar
Deucе committed
						conn_send("\033[C", 3, 0);
deuce's avatar
deuce committed
						break;
					case CIO_KEY_UP:
Deucе's avatar
Deucе committed
						conn_send("\033[A", 3, 0);
deuce's avatar
deuce committed
						break;
					case CIO_KEY_DOWN:
Deucе's avatar
Deucе committed
						conn_send("\033[B", 3, 0);
deuce's avatar
deuce committed
						break;
					case CIO_KEY_HOME:
Deucе's avatar
Deucе committed
						conn_send("\033[H", 3, 0);
deuce's avatar
deuce committed
						break;
					case CIO_KEY_END:
#ifdef CIO_KEY_SELECT
Deucе's avatar
Deucе committed
					case CIO_KEY_SELECT: /* Some terminfo/termcap entries use KEY_SELECT as the END
                                                              * key! */
deuce's avatar
deuce committed
#endif
Deucе's avatar
Deucе committed
						conn_send("\033[K", 3, 0);
deuce's avatar
deuce committed
						break;
Deucе's avatar
Deucе committed
					case CIO_KEY_DC:    /* "Delete" key, send ASCII 127 (DEL) */
						conn_send("\x7f", 1, 0);
deuce's avatar
deuce committed
						break;
Deucе's avatar
Deucе committed
					case CIO_KEY_NPAGE: /* Page down */
						conn_send("\033[U", 3, 0);
deuce's avatar
deuce committed
						break;
Deucе's avatar
Deucе committed
					case CIO_KEY_PPAGE: /* Page up */
						conn_send("\033[V", 3, 0);
deuce's avatar
deuce committed
						break;
					case CIO_KEY_F(1):
Deucе's avatar
Deucе committed
						conn_send("\033[11~", 3, 0);
deuce's avatar
deuce committed
						break;
					case CIO_KEY_F(2):
Deucе's avatar
Deucе committed
						conn_send("\033[12~", 3, 0);
deuce's avatar
deuce committed
						break;
					case CIO_KEY_F(3):
Deucе's avatar
Deucе committed
						conn_send("\033[13~", 3, 0);
deuce's avatar
deuce committed
						break;
					case CIO_KEY_F(4):
Deucе's avatar
Deucе committed
						conn_send("\033[14~", 3, 0);
deuce's avatar
deuce committed
						break;
					case CIO_KEY_F(5):
Deucе's avatar
Deucе committed
						conn_send("\033[15~", 3, 0);
deuce's avatar
deuce committed
						break;
					case CIO_KEY_F(6):
Deucе's avatar
Deucе committed
						conn_send("\033[17~", 5, 0);
deuce's avatar
deuce committed
						break;
					case CIO_KEY_F(7):
Deucе's avatar
Deucе committed
						conn_send("\033[18~", 5, 0);
deuce's avatar
deuce committed
						break;
					case CIO_KEY_F(8):
Deucе's avatar
Deucе committed
						conn_send("\033[19~", 5, 0);
deuce's avatar
deuce committed
						break;
					case CIO_KEY_F(9):
Deucе's avatar
Deucе committed
						conn_send("\033[20~", 5, 0);
deuce's avatar
deuce committed
						break;
					case CIO_KEY_F(10):
Deucе's avatar
Deucе committed
						conn_send("\033[21~", 5, 0);
deuce's avatar
deuce committed
						break;
					case CIO_KEY_F(11):
Deucе's avatar
Deucе committed
						conn_send("\033[23~", 5, 0);
deuce's avatar
deuce committed
						break;
					case CIO_KEY_F(12):
Deucе's avatar
Deucе committed
						conn_send("\033[24~", 5, 0);
deuce's avatar
deuce committed
						break;
					case CIO_KEY_SHIFT_F(1):
Deucе's avatar
Deucе committed
						conn_send("\033[11;2~", 3, 0);
						break;
					case CIO_KEY_SHIFT_F(2):
Deucе's avatar
Deucе committed
						conn_send("\033[12;2~", 3, 0);
						break;
					case CIO_KEY_SHIFT_F(3):
Deucе's avatar
Deucе committed
						conn_send("\033[13;2~", 3, 0);
						break;
					case CIO_KEY_SHIFT_F(4):
Deucе's avatar
Deucе committed
						conn_send("\033[14;2~", 3, 0);
						break;
					case CIO_KEY_SHIFT_F(5):
Deucе's avatar
Deucе committed
						conn_send("\033[15;2~", 3, 0);
						break;
					case CIO_KEY_SHIFT_F(6):
Deucе's avatar
Deucе committed
						conn_send("\033[17;2~", 5, 0);
						break;
					case CIO_KEY_SHIFT_F(7):
Deucе's avatar
Deucе committed
						conn_send("\033[18;2~", 5, 0);
						break;
					case CIO_KEY_SHIFT_F(8):
Deucе's avatar
Deucе committed
						conn_send("\033[19;2~", 5, 0);
						break;
					case CIO_KEY_SHIFT_F(9):
Deucе's avatar
Deucе committed
						conn_send("\033[20;2~", 5, 0);
						break;
					case CIO_KEY_SHIFT_F(10):
Deucе's avatar
Deucе committed
						conn_send("\033[21;2~", 5, 0);
						break;
					case CIO_KEY_SHIFT_F(11):
Deucе's avatar
Deucе committed
						conn_send("\033[23;2~", 5, 0);
						break;
					case CIO_KEY_SHIFT_F(12):
Deucе's avatar
Deucе committed
						conn_send("\033[24;2~", 5, 0);
						break;
					case CIO_KEY_CTRL_F(1):
Deucе's avatar
Deucе committed
						conn_send("\033[11;5~", 3, 0);
						break;
					case CIO_KEY_CTRL_F(2):
Deucе's avatar
Deucе committed
						conn_send("\033[12;5~", 3, 0);
						break;
					case CIO_KEY_CTRL_F(3):
Deucе's avatar
Deucе committed
						conn_send("\033[13;5~", 3, 0);
						break;
					case CIO_KEY_CTRL_F(4):
Deucе's avatar
Deucе committed
						conn_send("\033[14;5~", 3, 0);
						break;
					case CIO_KEY_CTRL_F(5):
Deucе's avatar
Deucе committed
						conn_send("\033[15;5~", 3, 0);
						break;
					case CIO_KEY_CTRL_F(6):
Deucе's avatar
Deucе committed
						conn_send("\033[17;5~", 5, 0);
						break;
					case CIO_KEY_CTRL_F(7):
Deucе's avatar
Deucе committed
						conn_send("\033[18;5~", 5, 0);
						break;
					case CIO_KEY_CTRL_F(8):
Deucе's avatar
Deucе committed
						conn_send("\033[19;5~", 5, 0);
						break;
					case CIO_KEY_CTRL_F(9):
Deucе's avatar
Deucе committed
						conn_send("\033[20;5~", 5, 0);
						break;
					case CIO_KEY_CTRL_F(10):
Deucе's avatar
Deucе committed
						conn_send("\033[21;5~", 5, 0);
						break;
					case CIO_KEY_CTRL_F(11):
Deucе's avatar
Deucе committed
						conn_send("\033[23;5~", 5, 0);
						break;
					case CIO_KEY_CTRL_F(12):
Deucе's avatar
Deucе committed
						conn_send("\033[24;5~", 5, 0);
						break;
					case CIO_KEY_ALT_F(1):
Deucе's avatar
Deucе committed
						conn_send("\033[11;3~", 3, 0);
						break;
					case CIO_KEY_ALT_F(2):
Deucе's avatar
Deucе committed
						conn_send("\033[12;3~", 3, 0);
						break;
					case CIO_KEY_ALT_F(3):
Deucе's avatar
Deucе committed
						conn_send("\033[13;3~", 3, 0);
						break;
					case CIO_KEY_ALT_F(4):
Deucе's avatar
Deucе committed
						conn_send("\033[14;3~", 3, 0);
						break;
					case CIO_KEY_ALT_F(5):
Deucе's avatar
Deucе committed
						conn_send("\033[15;3~", 3, 0);
						break;
					case CIO_KEY_ALT_F(6):
Deucе's avatar
Deucе committed
						conn_send("\033[17;3~", 5, 0);
						break;
					case CIO_KEY_ALT_F(7):
Deucе's avatar
Deucе committed
						conn_send("\033[18;3~", 5, 0);
						break;
					case CIO_KEY_ALT_F(8):
Deucе's avatar
Deucе committed
						conn_send("\033[19;3~", 5, 0);
						break;
					case CIO_KEY_ALT_F(9):
Deucе's avatar
Deucе committed
						conn_send("\033[20;3~", 5, 0);
						break;
					case CIO_KEY_ALT_F(10):
Deucе's avatar
Deucе committed
						conn_send("\033[21;3~", 5, 0);
						break;
					case CIO_KEY_ALT_F(11):
Deucе's avatar
Deucе committed
						conn_send("\033[23;3~", 5, 0);
						break;
					case CIO_KEY_ALT_F(12):
Deucе's avatar
Deucе committed
						conn_send("\033[24;3~", 5, 0);
deuce's avatar
deuce committed
					case CIO_KEY_IC:
Deucе's avatar
Deucе committed
						conn_send("\033[@", 3, 0);
deuce's avatar
deuce committed
						break;
Deucе's avatar
Deucе committed
					case 17: /* CTRL-Q */
						ch[0] = key;
						conn_send(ch, 1, 0);
deuce's avatar
deuce committed
						break;
Deucе's avatar
Deucе committed
					case 19: /* CTRL-S */
						ch[0] = key;
						conn_send(ch, 1, 0);
deuce's avatar
deuce committed
						break;
					case CIO_KEY_BACKTAB:
						conn_send("\033[Z", 3, 0);
						break;
deuce's avatar
deuce committed
					case '\b':
Deucе's avatar
Deucе committed
						key = '\b';

                                        /* FALLTHROUGH to default */
deuce's avatar
deuce committed
					default:
Deucе's avatar
Deucе committed
						if ((key < 256) && (key >= 0)) {
							ch[0] = key;
							conn_send(ch, 1, 0);
Deucе's avatar
Deucе committed
		if (sleep)
deuce's avatar
deuce committed
			SLEEP(1);
		else
			MAYBE_YIELD();
Deucе's avatar
Deucе committed

deuce's avatar
deuce committed
/*
Deucе's avatar
Deucе committed
 *       hidemouse();
 *       hold_update=oldmc;
deuce's avatar
deuce committed
 */
Deucе's avatar
Deucе committed
	return false;