-- Now that extra leading is supported, the underline text attribute could
- potentially place the line a bit slower when it is > 0.
+- There is a cursor related refresh issue that is noticeable when enabling
+ the slow bandwidth feature. It may be more noticeable in slow scroll mode.
+- Maybe provide the option to have text blinking settings independent from
+ cursor blinking ones. Text blinking usually doesn't need to be asymmetric
+ and it may be too slow when used with rarely blinking cursors.
- xterm and urxvt appear to set the tty(4) termios ospeed/ispeed so that
baudrate be displayed as 38400 by stty(1). Verify what they do, if it
serves a purpose and if they observe any delay for NUL character padding at
application input. stty's "speed" dispay seems to be ispeed not ospeed.
-- Maybe support explicit or implicit login shell mode except for -e
+- Maybe support explicit or implicit login shell mode except for -e.
+ Now it is possible by using -e /bin/sh -c.
- Now that glyphs can be modified it is obviously a suboptimal method.
General font loading could work, it may be worth checking older standard
practice for reference and ideas.
- Maybe support a configuration file. It would also be possible to use X11
resources for this. Currently it is src/config.h meaning higher performance
is possible for some math using constants, but also hardcoding options.
-- Maybe reimplement smooth scrolling, was implemented in XScreenSaver-based
- AnalogTerm. Smooth scrolling could theoretically work with scroll
- regions (see TODO there)... Then it may be possible to limit application
- input to display speed, either with XON/XOFF or slow reading...
- Look at tcflow(3). It's possible that with signals on there's nothing else
- to do than to delay until scrolling is done for I/O to flow properly. The
- new feature to limit the application input speed to emulate low bandwidth
- suggests that is the case.
- May be too slow if XShm is not available.
- DECs that support smooth scrolling interestingly disable the cursor when
- scrolling to immediately re-enable it after. When the input bps is too
- slow, the gradually appearing line can still be seen being written and a
- little jump can be perceived between the lines. When bps is high enough,
- scrolling is smoother and steady.
+- Smooth scrolling could theoretically work with scroll regions instead of
+ only working when scrolling from the bottom of the screen. The current
+ implementation could also be improved and more configurable. Notably, the
+ top line is not scrolled and an extra memory buffer is allocated for
+ simplicity. Also look at tcflow(3). Currently, for smooth scrolling to
+ work with screen(1)/tmux(1), the status bar must be disabled or configured
+ to be at the top.
- Check termios(4) IMAXBEL and queue size, also reported by stty(1).
This may allow to tweak interactive performance when a lot of application
output happens.
bool cfg_condensed;
bool cfg_stderr;
bool cfg_sleep;
-bool cfg_slowscroll;
+bool cfg_slowscroll, cfg_smoothscroll;
int cfg_jumpscrolllines;
int cfg_leading;
int cfg_inputsleep, cfg_inputsleepskip;
cfg_stderr = false;
cfg_sleep = false;
cfg_slowscroll = SLOW_SCROLL;
+ cfg_smoothscroll = SMOOTH_SCROLL;
cfg_jumpscrolllines = JUMP_SCROLL_LINES;
cfg_leading = EXTRA_LEADING;
* determine the next refresh no matter the number of operations done within
* that time frame. The mode can be changed using DEC Private Mode 4.
* JUMP_SCROLL_LINES specifies how many lines of scrolling to allow at a time
- * before forcing a refresh,
+ * before forcing a refresh. SMOOTH_SCROLL means that pixel-level smooth
+ * scrolling should be enabled when SLOW_SCROLL is also enabled.
*/
#define SLOW_SCROLL false
+#define SMOOTH_SCROLL false
#define JUMP_SCROLL_LINES 8
-extern bool cfg_slowscroll;
+extern bool cfg_slowscroll, cfg_smoothscroll;
extern int cfg_jumpscrolllines;
if (sig != SIGALRM)
return;
+ /* Flag expiration condition to trigger a refresh */
refresh_expired = true;
+
+ /* Smooth scrolling temporarily disables the cursor with a timer */
+ if (state->cursor_disabled_ticks > 0) {
+ if (--state->cursor_disabled_ticks == 0)
+ draw_blink_reset();
+ }
+
+ /* Modulate the blinking state */
blinkspeed = (draw_blink_state ? cfg_blinkspeedoff : cfg_blinkspeedon);
if (++blink_ticks == blinkspeed) {
blink_ticks = 0;
sc->update_x = 0;
sc->update_w = scanjmp;
- /* XXX May want to adjust instead of always resetting */
+ /* Original code without smooth scrolling. */
+ /*
sc->update_y = low * (FONT_HEIGHT + cfg_leading) * 2;
+ sc->update_h = ((high + 1) * ((FONT_HEIGHT + cfg_leading) * 2))
+ - sc->update_y;
+ */
+
+ /*
+ * XXX
+ * Smooth scrolling version. Instead of stopping at the required
+ * scanline, this implementation expects an extra line in bitmap
+ * memory that extends the visible window. Not ideal but easy without
+ * affecting performance. It also does not handle properly the top
+ * line that is to be discarded, which gets overwritten bottom-up
+ * rather than scrolled. It is not very noticeable but still
+ * considered to be a bug. It will be visible at low refresh rates.
+ */
+ sc->update_y = ((low * (FONT_HEIGHT + cfg_leading)) +
+ st->smoothscroll_offset) * 2;
sc->update_h = ((high + 1) * ((FONT_HEIGHT + cfg_leading) * 2))
- sc->update_y;
+ /*
+ * Modulate the smooth scrolling offset.
+ * If done, disable and request one last full refresh.
+ */
+ if (st->smoothscroll_offset > 0) {
+ /* XXX Skip could be configurable */
+ if (--st->smoothscroll_offset <= 0) {
+ st->smoothscroll_offset = 0;
+ st->text_updateall = true;
+ }
+ }
+
/* scanptr2 points to the odd scanline below scanptr1 */
scanptr1 = &sc->pixels[sc->update_y * scanjmp];
scanptr2 = &scanptr1[scanjmp];
(m & TMODE_GFX) != 0);
/* XOR text cursor handling */
- if (!st->cursor_disabled && st->cursor_y == row &&
+ if (st->cursor_disabled_ticks == 0 &&
+ !st->cursor_disabled && st->cursor_y == row &&
st->cursor_x == col &&
(! ((draw_blink_state && sc->focus) &&
cursor_waitnext == 0))) {
refresh_expired = false;
jump_scroll_lines = 0;
- /* Special general full-update case */
- if (st->text_updateall) {
+ /*
+ * Special general full-update case.
+ * Ongoing smooth scrolling requires constant updates.
+ */
+ if (st->text_updateall || st->smoothscroll_offset > 0) {
for (h = 0; h < cfg_text_height; h++)
st->text_updated[h] = false;
st->text_updateall = false;
/*
* Run through updated lines, coalescing contiguous ones in single
- * operations.
+ * operations for a partial update if necessary.
*/
/* XXX Doing a singe update for now as a first test */
for (low = high = -1, h = 0; h < cfg_text_height; h++) {
if (state->blink_update)
draw_blink_update();
- if (!state->scroll_slow && refresh_expired)
+ if (refresh_expired)
draw_update_screen(screen, state);
while (XPending(screen->dpy) > 0) {
errno = EINVAL;
fprintf(stderr,
- "\nUsage: %s [-8|-u] [-w <cols>] [-h <rows>] [-E <n>] [-s]\n"
+ "\nUsage: %s [-1] [-8|-u] [-w <cols>] [-h <rows>] [-E <n>] [-s]\n"
" [-C <col>] [-c] [-W] [-b] [-r <ms>] [-B <ticks>[,<ticks>]]\n"
" [-j <n>] [-P] [-m <mode>] [-M] [-d] [-D] [-S]\n"
" [-p <parameters>] [-l <pixels>] [-f <delay>] [-U] [-z <ms>]\n"
" [-Z <skip>] [-e <command> [<arguments>]]\n\n"
"Where:\n"
+ " -1 - Activate slow and smooth scrolling at startup.\n"
" -8 / -u - 8-bit mode (Latin-1), unicode+UTF-8. 8-bit mode is\n"
" automatically enabled by default if the LANG\n"
" environment variable is \"C\" or contains \"8859\".\n"
" The mode can also be changed using the ESC sequences:\n"
" [?658467;65536h (color), [?658467;65537h (mono).\n"
" -W - Toggle X zoom between wide (2x) and condensed (1.5x)\n"
- " -r - Set the maximum screen refresh speed when in jump scroll\n"
- " in microseconds (limits: 11111 (90fps) - 100000 (10fps)).\n"
- " Also affects the text/cursor blinking rate setting -B.\n"
- " Low settings save CPU but affect interactive performance.\n"
- " Jump scroll is the default but can be changed with the\n"
- " DEC Private Mode sequence 4: [?4h and [?4l.\n"
+ " -r - Set the maximum screen refresh speedin microseconds.\n"
+ " Limits: 11111 (90fps) - 100000 (10fps). This is also a\n"
+ " general timing source that also affects the text/cursor\n"
+ " blinking rate setting -B and the speed of smooth scroll\n"
+ " when enabled. Low settings save CPU but will affect\n"
+ " interactive performance.\n"
" -b - Toggle blinking cursor.\n"
" -B - Set the cursor and text blinking speed, in refresh ticks.\n"
" Two comma-separated values may optionally be provided for\n"
" This depends on the refresh rate setting, -r.\n"
" -j - Maximum number of lines to jump in fast scrolling mode.\n"
" Also depends on the -r setting.\n"
+ " Jump scroll is the default but can be changed with the\n"
+ " DEC Private Mode sequence 4: [?4h and [?4l. Pixel-level\n"
+ " smooth scrolling may also be enabled and disabled when\n"
+ " slow scrolling is also enabled with the [?658467;65543h\n"
+ " and [?658467;65544h special sequences, respectively.\n"
" -P - If the cursor is blinking, reset its timer for printing.\n"
" By default, it is only reset \"on\" by user input.\n"
" -m - Set default cursor style (0 = block, 1 = line, 2 = bar).\n"
progname = strdup(argv[0]);
while ((ch = getopt(argc, argv,
- "?8uw:h:E:scC:Wr:bB:j:Pm:MdDSp:l:f:Uz:Z:e:")) != -1) {
+ "?18uw:h:E:scC:Wr:bB:j:Pm:MdDSp:l:f:Uz:Z:e:")) != -1) {
switch (ch) {
+ case '1':
+ cfg_slowscroll = cfg_smoothscroll = true;
+ break;
case '8':
cfg_unicode = false;
break;
}
}
+ /*
+ * Fancy retro welcome message.
+ */
state_goto(state, 1, 1);
state_prints(state, "READY", 0);
state_goto(state, 1, (cfg_text_width / 2) - 17);
/*
* We set the pty/application input FD in non-blocking mode. This
* prevents locking on that FD when there may also be X11 events to
- * process.
+ * process. We then poll(2) for input from both X11 and the
+ * application tty(4).
*/
fdmode(ttys->ptyfd, false);
if ((ret = poll(pfd, 2, -1)) == -1) {
if (errno == EINTR) {
+ /*
+ * Interrupted by signal, still check X11 for
+ * high interactive response.
+ */
if (!x11_process(screen, ttys, state)) {
warnx("x11_process() false");
break;
break;
}
if ((pfd[0].revents & POLLIN) != 0) {
+ /* X11 event(s) received, process */
if (!x11_process(screen, ttys, state)) {
warnx("x11_process() false");
break;
if ((pfd[1].revents & POLLIN) != 0) {
size_t rbufsize = RBUF_SIZE;
- /* For interactive performance */
+ /*
+ * Application input available, still process any
+ * pending X11 events in non-blocking mode for
+ * interactive performance.
+ */
if (!x11_process(screen, ttys, state)) {
warnx("x11_process() false");
break;
}
- /* Low bandwidth simulation */
+ /*
+ * Low bandwidth simulation.
+ * Read one byte at a time and sleep if necessary.
+ */
if (cfg_inputsleep > 0) {
rbufsize = 1;
if (--inputsleepskip < 1) {
}
}
+ /*
+ * Restrict to one byte at a time if smooth scrolling
+ * is in progress.
+ */
+ if (state->cursor_disabled_ticks != 0)
+ rbufsize = 1;
+
+ /* Finally read from the application tty(4) */
if ((size = read(ttys->ptyfd, ttys->rbuf, rbufsize))
== -1 && errno != EAGAIN && errno != EINTR) {
warn("read(shell) error");
warnx("read(shell) EOF");
break;
}
+
+ /* Emulate terminal and update screen */
for (i = 0; i < size; i++)
state_emul_printc(state, ttys->rbuf[i]);
draw_update_screen(screen, state);
}
}
+ /*
+ * End of application input received and requested to wait before
+ * closing the terminal window.
+ */
if (cfg_sleep) {
warnx("Waiting.");
for (;;) {
s->visual = s->vinfo.visual;
depth = s->vinfo.depth;
+ /*
+ * XXX
+ * We allocate an extra line for use by the optional smooth scrolling
+ * feature, that exists in the underlaying ZPixmap although the X
+ * window size is kept smaller. See draw.c for more related notes.
+ */
+
/* Font rendered with 2x/1.5x software zoom */
if (cfg_condensed)
width = ((int)((cfg_text_width * FONT_WIDTH) * 1.5)) + 1;
else
width = ((cfg_text_width * FONT_WIDTH) * 2) + 1;
#ifdef EXPAND_Y
- height = ((int)((cfg_text_height * (FONT_HEIGHT + cfg_leading)) * 2.5)
- + 1);
+ height = ((int)(((cfg_text_height + 1) * (FONT_HEIGHT + cfg_leading))
+ * 2.5) + 1);
#else
- height = (cfg_text_height * (FONT_HEIGHT + cfg_leading)) * 2;
+ height = ((cfg_text_height + 1) * (FONT_HEIGHT + cfg_leading)) * 2;
#endif
s->pixels_width = width;
s->pixels_height = height;
/* XXX Report error properly */
- if ((s->pixels = malloc((width * height) * sizeof(uint32_t))) == NULL)
+ if ((s->pixels = malloc((width * height) * sizeof(uint32_t)))
+ == NULL)
goto err;
- s->win = XCreateWindow(s->dpy, s->parentwin, 0, 22, width, height, 0,
+ s->win = XCreateWindow(s->dpy, s->parentwin, 0, 22, width,
+ height - ((FONT_HEIGHT + cfg_leading) * 2), 0,
depth, InputOutput, s->visual,
/*CWBackPixel | CWColormap | CWBorderPixel*/0, &s->attr);
static inline void state_limit(int *, int *);
static inline void state_setchar(state_t *, int, int, uint32_t, uint32_t);
static inline void state_updated(state_t *, int);
+static inline void state_update_cursor(state_t *);
static void unsupported(emulstate_t *, int, char, char);
estate.curparam = -1;
estate.unicruds = 0;
+ /* Smooth scrolling */
+ st->smoothscroll_offset = 0;
+ st->cursor_disabled_ticks = 0;
+
return st;
err:
st->text_updated[y] = true;
}
+inline void
+state_update_cursor(state_t *st)
+{
+ int y = st->cursor_y;
+
+ state_limit(NULL, &y);
+ st->text_updated[y] = true;
+}
+
void
state_clearall(state_t *st)
{
st->scroll_top = 0;
st->scroll_bottom = cfg_text_height - 1;
st->scroll_slow = cfg_slowscroll;
+ st->scroll_smooth = cfg_smoothscroll;
st->lastchar = (uint32_t)-1;
st->selecting = false;
state_scroll_range(state_t *st, int offset)
{
int y, aoffset = abs(offset);
- /*bool smooth = false; XXX */
+ bool smooth = false;
/* Nothing to do */
if (offset == 0)
return;
+
if (!st->scroll_slow)
jump_scroll_lines += aoffset;
return;
}
- /* XXX
- if (st->scroll_slow && st->cursor_y == (cfg_text_height - 1) &&
- offset == -1)
+ /*
+ * If scrolling from the bottom by one line, slow scrolling and smooth
+ * scrolling are enabled, activate smooth mode for this operation.
+ * XXX It may be possible to eventually also support smooth scrolling
+ * inside custom scroll regions. This may more easily work within
+ * applications like tmux/screen. Currently, with those applications
+ * the status bar needs to be configured to be at the top, or
+ * disabled, for smooth scrolling to work.
+ */
+ if (st->scroll_smooth && st->scroll_slow &&
+ st->cursor_y == (cfg_text_height - 1) && offset == -1)
smooth = true;
- */
/* Only one line to clear */
if (st->scroll_top == st->scroll_bottom) {
for (y = st->scroll_bottom + 1 + offset; y <= st->scroll_bottom;
y++)
state_clear(st, 0, y, cfg_text_width);
- /* XXX
if (smooth) {
- smooth_scroll_offset = 8;
- smooth_scroll_offset_cnt =
- smooth_scroll_offset_cnt_start;
- } */
+ /*
+ * Activate smooth scrolling offset by one line and
+ * temporarily disable the cursor.
+ */
+ st->smoothscroll_offset = FONT_HEIGHT + cfg_leading;
+ st->cursor_disabled_ticks =
+ (int)(st->smoothscroll_offset * 1.5);
+ state_update_cursor(st);
+ }
} else {
/* Bottom to top moving lines down, blank at top */
for (y = st->scroll_bottom - offset; y >= st->scroll_top; y--)
if (++st->cursor_x == cfg_text_width) {
if (st->cursor_y < st->scroll_bottom &&
st->cursor_y < cfg_text_height - 1) {
+ st->text_updated[st->cursor_y] = true; /* XXX */
st->cursor_y++;
st->text_updated[st->cursor_y] = true; /* XXX */
} else {
* change it with sequence 12, only
* the user with 13.
*/
- if (state->csiparam[i] == 13)
+ if (state->csiparam[i] == 13) {
st->cursor_blink = true;
+ draw_blink_reset();
+ state_update_cursor(st);
+ }
break;
case 25: /* Show cursor (DECTCEM, cvvis) */
/* XXX "Very visible" vs cnorm */
st->cursor_disabled = false;
+ draw_blink_reset();
+ state_update_cursor(st);
break;
case 2004: /* Bracketed paste mode */
st->quotedpaste = true;
cfg_cursorbright =
st->cursor_bright
= false;
+ } else if (p == 65543) {
+ st->scroll_smooth =
+ st->scroll_slow =
+ true;
+ } else if (p == 65544) {
+ st->scroll_smooth =
+ false;
+ st->scroll_slow =
+ cfg_slowscroll;
}
} else if ((state->curparam == 2 ||
state->curparam == 3) &&
if (s2 > 0 && s2 < 501)
cfg_blinkspeedoff = s2;
draw_blink_reset();
+ state_update_cursor(st);
} else if (state->curparam == 3 &&
state->csiparam[1] ==
65539) {
st->cursor_mode = cfg_cursormode;
st->cursor_blink = cfg_cursorblink;
st->cursor_bright = cfg_cursorbright;
+ draw_blink_reset();
+ state_update_cursor(st);
break;
case 12:
case 13: /* FALLTHROUGH
* change it with sequence 12, only
* the user with 13.
*/
- if (state->csiparam[i] == 13)
+ if (state->csiparam[i] == 13) {
st->cursor_blink = false;
+ state_update_cursor(st);
+ }
break;
case 25: /* Hide cursor (DECTCEM, civis) */
if (cfg_cursordisable)
st->cursor_bright = cfg_cursorbright;
break;
}
+ draw_blink_reset();
+ state_update_cursor(st);
}
state->escstate = 0;
break;
int monocolor;
int cursor_mode, cursor_x, cursor_y;
int saved_cursor_x, saved_cursor_y;
+ int cursor_disabled_ticks;
uint32_t mode;
int scroll_top, scroll_bottom;
- bool scroll_slow;
+ bool scroll_slow, scroll_smooth;
+ int smoothscroll_offset;
uint32_t lastchar;
int pty;