#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
+#include <limits.h>
#include <time.h>
#include <assert.h>
#define has_compat(x) ( ((CL_##x)&term->compatibility_level) != 0 )
-char *EMPTY_WINDOW_TITLE = "";
+const char *EMPTY_WINDOW_TITLE = "";
const char sco2ansicolour[] = { 0, 4, 2, 6, 1, 5, 3, 7 };
static void resizeline(Terminal *, termline *, int);
static termline *lineptr(Terminal *, int, int, int);
static void unlineptr(termline *);
+static void check_line_size(Terminal *, termline *);
static void do_paint(Terminal *, Context, int);
static void erase_lots(Terminal *, int, int, int);
static int find_last_nonempty_line(Terminal *, tree234 *);
#ifdef OPTIMISE_SCROLL
static void scroll_display(Terminal *, int, int, int);
#endif /* OPTIMISE_SCROLL */
-static void term_resume_pasting(Terminal *term);
-static void term_paste_callback(void *vterm);
-static void term_paste_queue(Terminal *term, int timed);
static termline *newline(Terminal *term, int cols, int bce)
{
}
assert(line != NULL);
- resizeline(term, line, term->cols);
- /* FIXME: should we sort the compressed scrollback out here? */
+ /*
+ * Here we resize lines to _at least_ the right length, but we
+ * don't truncate them. Truncation is done as a side effect of
+ * modifying the line.
+ *
+ * The point of this policy is to try to arrange that resizing the
+ * terminal window repeatedly - e.g. successive steps in an X11
+ * opaque window-resize drag, or resizing as a side effect of
+ * retiling by tiling WMs such as xmonad - does not throw away
+ * data gratuitously. Specifically, we want a sequence of resize
+ * operations with no terminal output between them to have the
+ * same effect as a single resize to the ultimate terminal size,
+ * and also (for the case in which xmonad narrows a window that's
+ * scrolling things) we want scrolling up new text at the bottom
+ * of a narrowed window to avoid truncating lines further up when
+ * the window is re-widened.
+ */
+ if (term->cols > line->cols)
+ resizeline(term, line, term->cols);
return line;
}
#define lineptr(x) (lineptr)(term,x,__LINE__,FALSE)
#define scrlineptr(x) (lineptr)(term,x,__LINE__,TRUE)
+/*
+ * Coerce a termline to the terminal's current width. Unlike the
+ * optional resize in lineptr() above, this is potentially destructive
+ * of text, since it can shrink as well as grow the line.
+ *
+ * We call this whenever a termline is actually going to be modified.
+ * Helpfully, putting a single call to this function in check_boundary
+ * deals with _nearly_ all such cases, leaving only a few things like
+ * bulk erase and ESC#8 to handle separately.
+ */
+static void check_line_size(Terminal *term, termline *line)
+{
+ if (term->cols != line->cols) /* trivial optimisation */
+ resizeline(term, line, term->cols);
+}
+
static void term_schedule_tblink(Terminal *term);
static void term_schedule_cblink(Terminal *term);
{
power_on(term, clear);
if (term->ldisc) /* cause ldisc to notice changes */
- ldisc_send(term->ldisc, NULL, 0, 0);
+ ldisc_echoedit_update(term->ldisc);
term->disptop = 0;
deselect(term);
term_update(term);
void term_clrsb(Terminal *term)
{
unsigned char *line;
+ int i;
+
+ /*
+ * Scroll forward to the current screen, if we were back in the
+ * scrollback somewhere until now.
+ */
term->disptop = 0;
+
+ /*
+ * Clear the actual scrollback.
+ */
while ((line = delpos234(term->scrollback, 0)) != NULL) {
sfree(line); /* this is compressed data, not a termline */
}
+
+ /*
+ * When clearing the scrollback, we also truncate any termlines on
+ * the current screen which have remembered data from a previous
+ * larger window size. Rationale: clearing the scrollback is
+ * sometimes done to protect privacy, so the user intention is
+ * specifically that we should not retain evidence of what
+ * previously happened in the terminal, and that ought to include
+ * evidence to the right as well as evidence above.
+ */
+ for (i = 0; i < term->rows; i++)
+ check_line_size(term, scrlineptr(i));
+
+ /*
+ * There are now no lines of real scrollback which can be pulled
+ * back into the screen by a resize, and no lines of the alternate
+ * screen which should be displayed as if part of the scrollback.
+ */
term->tempsblines = 0;
term->alt_sblines = 0;
+
+ /*
+ * Update the scrollbar to reflect the new state of the world.
+ */
update_sbar(term);
}
termline *ldata;
/* Validate input coordinates, just in case. */
- if (x == 0 || x > term->cols)
+ if (x <= 0 || x > term->cols)
return;
ldata = scrlineptr(y);
+ check_line_size(term, ldata);
if (x == term->cols) {
ldata->lattr &= ~LATTR_WRAPPED2;
} else {
} else {
termline *ldata = scrlineptr(start.y);
while (poslt(start, end)) {
+ check_line_size(term, ldata);
if (start.x == term->cols) {
if (!erase_lattr)
ldata->lattr &= ~(LATTR_WRAPPED | LATTR_WRAPPED2);
{
int dir = (n < 0 ? -1 : +1);
int m, j;
- pos cursplus;
+ pos eol;
termline *ldata;
n = (n < 0 ? -n : n);
if (n > term->cols - term->curs.x)
n = term->cols - term->curs.x;
m = term->cols - term->curs.x - n;
- cursplus.y = term->curs.y;
- cursplus.x = term->curs.x + n;
- check_selection(term, term->curs, cursplus);
+
+ /*
+ * We must de-highlight the selection if it overlaps any part of
+ * the region affected by this operation, i.e. the region from the
+ * current cursor position to end-of-line, _unless_ the entirety
+ * of the selection is going to be moved to the left or right by
+ * this operation but otherwise unchanged, in which case we can
+ * simply move the highlight with the text.
+ */
+ eol.y = term->curs.y;
+ eol.x = term->cols;
+ if (poslt(term->curs, term->selend) && poslt(term->selstart, eol)) {
+ pos okstart = term->curs;
+ pos okend = eol;
+ if (dir > 0) {
+ /* Insertion: n characters at EOL will be splatted. */
+ okend.x -= n;
+ } else {
+ /* Deletion: n characters at cursor position will be splatted. */
+ okstart.x += n;
+ }
+ if (posle(okstart, term->selstart) && posle(term->selend, okend)) {
+ /* Selection is contained entirely in the interval
+ * [okstart,okend), so we need only adjust the selection
+ * bounds. */
+ term->selstart.x += dir * n;
+ term->selend.x += dir * n;
+ assert(term->selstart.x >= term->curs.x);
+ assert(term->selstart.x < term->cols);
+ assert(term->selend.x > term->curs.x);
+ assert(term->selend.x <= term->cols);
+ } else {
+ /* Selection is not wholly contained in that interval, so
+ * we must unhighlight it. */
+ deselect(term);
+ }
+ }
+
check_boundary(term, term->curs.x, term->curs.y);
if (dir < 0)
check_boundary(term, term->curs.x + n, term->curs.y);
case 10: /* DECEDM: set local edit mode */
term->term_editing = state;
if (term->ldisc) /* cause ldisc to notice changes */
- ldisc_send(term->ldisc, NULL, 0, 0);
+ ldisc_echoedit_update(term->ldisc);
break;
case 25: /* DECTCEM: enable/disable cursor */
compatibility2(OTHER, VT220);
compatibility(OTHER);
deselect(term);
swap_screen(term, term->no_alt_screen ? 0 : state, FALSE, FALSE);
- term->disptop = 0;
+ if (term->scroll_on_disp)
+ term->disptop = 0;
break;
case 1000: /* xterm mouse 1 (normal) */
term->xterm_mouse = state ? 1 : 0;
compatibility(OTHER);
deselect(term);
swap_screen(term, term->no_alt_screen ? 0 : state, TRUE, TRUE);
- term->disptop = 0;
+ if (term->scroll_on_disp)
+ term->disptop = 0;
break;
case 1048: /* save/restore cursor */
if (!term->no_alt_screen)
swap_screen(term, term->no_alt_screen ? 0 : state, TRUE, FALSE);
if (!state && !term->no_alt_screen)
save_cursor(term, state);
- term->disptop = 0;
+ if (term->scroll_on_disp)
+ term->disptop = 0;
break;
case 2004: /* xterm bracketed paste */
term->bracketed_paste = state ? TRUE : FALSE;
case 12: /* SRM: set echo mode */
term->term_echoing = !state;
if (term->ldisc) /* cause ldisc to notice changes */
- ldisc_send(term->ldisc, NULL, 0, 0);
+ ldisc_echoedit_update(term->ldisc);
break;
case 20: /* LNM: Return sends ... */
term->cr_lf_return = state;
term->curs.x = 0;
term->wrapnext = FALSE;
seen_disp_event(term);
- term_resume_pasting(term);
if (term->crhaslf) {
if (term->curs.y == term->marg_b)
if (has_compat(SCOANSI)) {
move(term, 0, 0, 0);
erase_lots(term, FALSE, FALSE, TRUE);
- term->disptop = 0;
+ if (term->scroll_on_disp)
+ term->disptop = 0;
term->wrapnext = FALSE;
seen_disp_event(term);
break;
term->curs.x = 0;
term->wrapnext = FALSE;
seen_disp_event(term);
- term_resume_pasting(term);
if (term->logctx)
logtraffic(term->logctx, (unsigned char) c, LGTYP_ASCII);
break;
compatibility(VT100);
power_on(term, TRUE);
if (term->ldisc) /* cause ldisc to notice changes */
- ldisc_send(term->ldisc, NULL, 0, 0);
+ ldisc_echoedit_update(term->ldisc);
if (term->reset_132) {
if (!term->no_remote_resize)
request_resize(term->frontend, 80, term->rows);
term->reset_132 = 0;
}
- term->disptop = 0;
+ if (term->scroll_on_disp)
+ term->disptop = 0;
seen_disp_event(term);
break;
case 'H': /* HTS: set a tab */
for (i = 0; i < term->rows; i++) {
ldata = scrlineptr(i);
+ check_line_size(term, ldata);
for (j = 0; j < term->cols; j++) {
copy_termchar(ldata, j,
&term->basic_erase_char);
}
ldata->lattr = LATTR_NORM;
}
- term->disptop = 0;
+ if (term->scroll_on_disp)
+ term->disptop = 0;
seen_disp_event(term);
scrtop.x = scrtop.y = 0;
scrbot.x = 0;
compatibility(VT100);
{
int nlattr;
+ termline *ldata;
switch (ANSI(c, term->esc_query)) {
case ANSI('3', '#'): /* DECDHL: 2*height, top */
nlattr = LATTR_WIDE;
break;
}
- scrlineptr(term->curs.y)->lattr = nlattr;
+ ldata = scrlineptr(term->curs.y);
+ check_line_size(term, ldata);
+ ldata->lattr = nlattr;
}
break;
/* GZD4: G0 designate 94-set */
if (term->esc_nargs <= ARGS_MAX) {
if (term->esc_args[term->esc_nargs - 1] == ARG_DEFAULT)
term->esc_args[term->esc_nargs - 1] = 0;
- term->esc_args[term->esc_nargs - 1] =
- 10 * term->esc_args[term->esc_nargs - 1] + c - '0';
+ if (term->esc_args[term->esc_nargs - 1] <=
+ UINT_MAX / 10 &&
+ term->esc_args[term->esc_nargs - 1] * 10 <=
+ UINT_MAX - c - '0')
+ term->esc_args[term->esc_nargs - 1] =
+ 10 * term->esc_args[term->esc_nargs - 1] +
+ c - '0';
+ else
+ term->esc_args[term->esc_nargs - 1] = UINT_MAX;
}
term->termstate = SEEN_CSI;
} else if (c == ';') {
term->esc_query = c;
term->termstate = SEEN_CSI;
} else
+#define CLAMP(arg, lim) ((arg) = ((arg) > (lim)) ? (lim) : (arg))
switch (ANSI(c, term->esc_query)) {
case 'A': /* CUU: move up N lines */
+ CLAMP(term->esc_args[0], term->rows);
move(term, term->curs.x,
term->curs.y - def(term->esc_args[0], 1), 1);
seen_disp_event(term);
compatibility(ANSI);
/* FALLTHROUGH */
case 'B': /* CUD: Cursor down */
+ CLAMP(term->esc_args[0], term->rows);
move(term, term->curs.x,
term->curs.y + def(term->esc_args[0], 1), 1);
seen_disp_event(term);
compatibility(ANSI);
/* FALLTHROUGH */
case 'C': /* CUF: Cursor right */
+ CLAMP(term->esc_args[0], term->cols);
move(term, term->curs.x + def(term->esc_args[0], 1),
term->curs.y, 1);
seen_disp_event(term);
break;
case 'D': /* CUB: move left N cols */
+ CLAMP(term->esc_args[0], term->cols);
move(term, term->curs.x - def(term->esc_args[0], 1),
term->curs.y, 1);
seen_disp_event(term);
break;
case 'E': /* CNL: move down N lines and CR */
compatibility(ANSI);
+ CLAMP(term->esc_args[0], term->rows);
move(term, 0,
term->curs.y + def(term->esc_args[0], 1), 1);
seen_disp_event(term);
break;
case 'F': /* CPL: move up N lines and CR */
compatibility(ANSI);
+ CLAMP(term->esc_args[0], term->rows);
move(term, 0,
term->curs.y - def(term->esc_args[0], 1), 1);
seen_disp_event(term);
case 'G': /* CHA */
case '`': /* HPA: set horizontal posn */
compatibility(ANSI);
+ CLAMP(term->esc_args[0], term->cols);
move(term, def(term->esc_args[0], 1) - 1,
term->curs.y, 0);
seen_disp_event(term);
break;
case 'd': /* VPA: set vertical posn */
compatibility(ANSI);
+ CLAMP(term->esc_args[0], term->rows);
move(term, term->curs.x,
((term->dec_om ? term->marg_t : 0) +
def(term->esc_args[0], 1) - 1),
case 'f': /* HVP: set horz and vert posns at once */
if (term->esc_nargs < 2)
term->esc_args[1] = ARG_DEFAULT;
+ CLAMP(term->esc_args[0], term->rows);
+ CLAMP(term->esc_args[1], term->cols);
move(term, def(term->esc_args[1], 1) - 1,
((term->dec_om ? term->marg_t : 0) +
def(term->esc_args[0], 1) - 1),
erase_lots(term, FALSE, !!(i & 2), !!(i & 1));
}
}
- term->disptop = 0;
+ if (term->scroll_on_disp)
+ term->disptop = 0;
seen_disp_event(term);
break;
case 'K': /* EL: erase line or parts of it */
break;
case 'L': /* IL: insert lines */
compatibility(VT102);
+ CLAMP(term->esc_args[0], term->rows);
if (term->curs.y <= term->marg_b)
scroll(term, term->curs.y, term->marg_b,
-def(term->esc_args[0], 1), FALSE);
break;
case 'M': /* DL: delete lines */
compatibility(VT102);
+ CLAMP(term->esc_args[0], term->rows);
if (term->curs.y <= term->marg_b)
scroll(term, term->curs.y, term->marg_b,
def(term->esc_args[0], 1),
case '@': /* ICH: insert chars */
/* XXX VTTEST says this is vt220, vt510 manual says vt102 */
compatibility(VT102);
+ CLAMP(term->esc_args[0], term->cols);
insch(term, def(term->esc_args[0], 1));
seen_disp_event(term);
break;
case 'P': /* DCH: delete chars */
compatibility(VT102);
+ CLAMP(term->esc_args[0], term->cols);
insch(term, -def(term->esc_args[0], 1));
seen_disp_event(term);
break;
compatibility(VT100);
if (term->esc_nargs <= 2) {
int top, bot;
+ CLAMP(term->esc_args[0], term->rows);
+ CLAMP(term->esc_args[1], term->rows);
top = def(term->esc_args[0], 1) - 1;
bot = (term->esc_nargs <= 1
|| term->esc_args[1] == 0 ?
switch (term->esc_args[0]) {
int x, y, len;
- char buf[80], *p;
+ char buf[80];
+ const char *p;
case 1:
set_iconic(term->frontend, FALSE);
break;
case 13:
if (term->ldisc) {
get_window_pos(term->frontend, &x, &y);
- len = sprintf(buf, "\033[3;%d;%dt", x, y);
+ len = sprintf(buf, "\033[3;%u;%ut",
+ (unsigned)x,
+ (unsigned)y);
ldisc_send(term->ldisc, buf, len, 0);
}
break;
}
break;
case 'S': /* SU: Scroll up */
+ CLAMP(term->esc_args[0], term->rows);
compatibility(SCOANSI);
scroll(term, term->marg_t, term->marg_b,
def(term->esc_args[0], 1), TRUE);
seen_disp_event(term);
break;
case 'T': /* SD: Scroll down */
+ CLAMP(term->esc_args[0], term->rows);
compatibility(SCOANSI);
scroll(term, term->marg_t, term->marg_b,
-def(term->esc_args[0], 1), TRUE);
/* XXX VTTEST says this is vt220, vt510 manual
* says vt100 */
compatibility(ANSIMIN);
+ CLAMP(term->esc_args[0], term->cols);
{
int n = def(term->esc_args[0], 1);
pos cursplus;
break;
case 'Z': /* CBT */
compatibility(OTHER);
+ CLAMP(term->esc_args[0], term->cols);
{
int i = def(term->esc_args[0], 1);
pos old_curs = term->curs;
break;
case ANSI('F', '='): /* set normal foreground */
compatibility(SCOANSI);
- if (term->esc_args[0] >= 0 && term->esc_args[0] < 16) {
+ if (term->esc_args[0] < 16) {
long colour =
(sco2ansicolour[term->esc_args[0] & 0x7] |
(term->esc_args[0] & 0x8)) <<
break;
case ANSI('G', '='): /* set normal background */
compatibility(SCOANSI);
- if (term->esc_args[0] >= 0 && term->esc_args[0] < 16) {
+ if (term->esc_args[0] < 16) {
long colour =
(sco2ansicolour[term->esc_args[0] & 0x7] |
(term->esc_args[0] & 0x8)) <<
case '7':
case '8':
case '9':
- term->esc_args[0] = 10 * term->esc_args[0] + c - '0';
+ if (term->esc_args[0] <= UINT_MAX / 10 &&
+ term->esc_args[0] * 10 <= UINT_MAX - c - '0')
+ term->esc_args[0] = 10 * term->esc_args[0] + c - '0';
+ else
+ term->esc_args[0] = UINT_MAX;
break;
case 'L':
/*
case '7':
case '8':
case '9':
- term->esc_args[0] = 10 * term->esc_args[0] + c - '0';
+ if (term->esc_args[0] <= UINT_MAX / 10 &&
+ term->esc_args[0] * 10 <= UINT_MAX - c - '0')
+ term->esc_args[0] = 10 * term->esc_args[0] + c - '0';
+ else
+ term->esc_args[0] = UINT_MAX;
break;
default:
term->termstate = OSC_STRING;
break;
case 'J':
erase_lots(term, FALSE, FALSE, TRUE);
- term->disptop = 0;
+ if (term->scroll_on_disp)
+ term->disptop = 0;
break;
case 'K':
erase_lots(term, TRUE, FALSE, TRUE);
/* compatibility(ATARI) */
move(term, 0, 0, 0);
erase_lots(term, FALSE, FALSE, TRUE);
- term->disptop = 0;
+ if (term->scroll_on_disp)
+ term->disptop = 0;
break;
case 'L':
/* compatibility(ATARI) */
case 'd':
/* compatibility(ATARI) */
erase_lots(term, FALSE, TRUE, FALSE);
- term->disptop = 0;
+ if (term->scroll_on_disp)
+ term->disptop = 0;
break;
case 'e':
/* compatibility(ATARI) */
}
term_print_flush(term);
- if (term->logflush)
+ if (term->logflush && term->logctx)
logflush(term->logctx);
}
}
}
-static void term_resume_pasting(Terminal *term)
-{
- expire_timer_context(&term->paste_timer_ctx);
- term_paste_queue(term, FALSE);
-}
-
-static void term_paste_timing_callback(void *vterm, unsigned long now)
-{
- Terminal *term = *(Terminal **)vterm;
- term_resume_pasting(term);
-}
-
-static void term_paste_queue(Terminal *term, int timed)
-{
- if (timed) {
- /*
- * Delay sending the rest of the paste buffer until we have
- * seen a newline coming back from the server (indicating that
- * it's absorbed the data we've sent so far). As a fallback,
- * continue sending anyway after a longish timeout.
- *
- * We use the pointless structure field term->paste_timer_ctx
- * (which is a Terminal *, and we'll make sure it points
- * straight back to term) as our timer context, so that it can
- * be distinguished from term itself. That way, if we see a
- * reason to continue pasting before the timer goes off, we
- * can cancel just this timer and none of the other terminal
- * timers handling display updates, blinking text and cursor,
- * and visual bells.
- */
- term->paste_timer_ctx = term;
- schedule_timer(450, term_paste_timing_callback,
- &term->paste_timer_ctx);
- } else {
- /*
- * Just arrange to call term_paste_callback from the top level
- * at the next opportunity.
- */
- queue_toplevel_callback(term_paste_callback, term);
- }
-}
-
static void term_paste_callback(void *vterm)
{
Terminal *term = (Terminal *)vterm;
term->paste_pos += n;
if (term->paste_pos < term->paste_len) {
- term_paste_queue(term, TRUE);
+ queue_toplevel_callback(term_paste_callback, term);
return;
}
}
}
get_clip(term->frontend, NULL, NULL);
- term_paste_queue(term, FALSE);
+ queue_toplevel_callback(term_paste_callback, term);
}
void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked,
*/
if (raw_mouse &&
(term->selstate != ABOUT_TO) && (term->selstate != DRAGGING)) {
- int encstate = 0, r, c;
+ int encstate = 0, r, c, wheel;
char abuf[32];
int len = 0;
switch (braw) {
case MBT_LEFT:
encstate = 0x00; /* left button down */
+ wheel = FALSE;
break;
case MBT_MIDDLE:
encstate = 0x01;
+ wheel = FALSE;
break;
case MBT_RIGHT:
encstate = 0x02;
+ wheel = FALSE;
break;
case MBT_WHEEL_UP:
encstate = 0x40;
+ wheel = TRUE;
break;
case MBT_WHEEL_DOWN:
encstate = 0x41;
+ wheel = TRUE;
break;
- default: break; /* placate gcc warning about enum use */
+ default:
+ return;
}
- switch (a) {
+ if (wheel) {
+ /* For mouse wheel buttons, we only ever expect to see
+ * MA_CLICK actions, and we don't try to keep track of
+ * the buttons being 'pressed' (since without matching
+ * click/release pairs that's pointless). */
+ if (a != MA_CLICK)
+ return;
+ } else switch (a) {
case MA_DRAG:
if (term->xterm_mouse == 1)
return;
return;
term->mouse_is_down = braw;
break;
- default: break; /* placate gcc warning about enum use */
+ default:
+ return;
}
if (shift)
encstate += 0x04;
} else if (c <= 223 && r <= 223) {
len = sprintf(abuf, "\033[M%c%c%c", encstate + 32, c + 32, r + 32);
}
- ldisc_send(term->ldisc, abuf, len, 0);
+ if (len > 0)
+ ldisc_send(term->ldisc, abuf, len, 0);
}
return;
}
sel_spread(term);
} else if ((bcooked == MBT_SELECT && a == MA_DRAG) ||
(bcooked == MBT_EXTEND && a != MA_RELEASE)) {
+ if (a == MA_DRAG &&
+ (term->selstate == NO_SELECTION || term->selstate == SELECTED)) {
+ /*
+ * This can happen if a front end has passed us a MA_DRAG
+ * without a prior MA_CLICK. OS X GTK does so, for
+ * example, if the initial button press was eaten by the
+ * WM when it activated the window in the first place. The
+ * nicest thing to do in this situation is to ignore
+ * further drags, and wait for the user to click in the
+ * window again properly if they want to select.
+ */
+ return;
+ }
if (term->selstate == ABOUT_TO && poseq(term->selanchor, selpoint))
return;
if (bcooked == MBT_EXTEND && a != MA_DRAG &&
{
if (term->paste_len == 0)
return;
- expire_timer_context(&term->paste_timer_ctx);
sfree(term->paste_buffer);
term->paste_buffer = NULL;
term->paste_len = 0;
*/
char *term_get_ttymode(Terminal *term, const char *mode)
{
- char *val = NULL;
+ const char *val = NULL;
if (strcmp(mode, "ERASE") == 0) {
val = term->bksp_is_delete ? "^?" : "^H";
}
* input.
*/
int term_get_userpass_input(Terminal *term, prompts_t *p,
- unsigned char *in, int inlen)
+ const unsigned char *in, int inlen)
{
struct term_userpass_state *s = (struct term_userpass_state *)p->data;
if (!s) {