AnalogTerm2: Implement smooth scrolling support. Improve comments.
authorMatthew Mondor <mmondor@pulsar-zone.net>
Thu, 20 Apr 2023 01:06:53 +0000 (01:06 +0000)
committerMatthew Mondor <mmondor@pulsar-zone.net>
Thu, 20 Apr 2023 01:06:53 +0000 (01:06 +0000)
mmsoftware/analogterm2/TODO.txt
mmsoftware/analogterm2/src/config.c
mmsoftware/analogterm2/src/config.h
mmsoftware/analogterm2/src/draw.c
mmsoftware/analogterm2/src/main.c
mmsoftware/analogterm2/src/screen.c
mmsoftware/analogterm2/src/state.c
mmsoftware/analogterm2/src/state.h

index 675c482..fe540c5 100644 (file)
@@ -1,10 +1,14 @@
-- 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.
index cefa40a..ba6e438 100644 (file)
@@ -41,7 +41,7 @@ bool  cfg_unicode;
 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;
@@ -81,6 +81,7 @@ cfg_setdefaults(void)
        cfg_stderr = false;
        cfg_sleep = false;
        cfg_slowscroll = SLOW_SCROLL;
+       cfg_smoothscroll = SMOOTH_SCROLL;
        cfg_jumpscrolllines = JUMP_SCROLL_LINES;
 
        cfg_leading = EXTRA_LEADING;
index 5b173c8..3720973 100644 (file)
@@ -236,12 +236,14 @@ extern bool cfg_unicode;
  * 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;
 
 
index 8360f8f..6ba8517 100644 (file)
@@ -159,7 +159,16 @@ alarm_sighandler(int sig)
        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;
@@ -280,11 +289,40 @@ draw_lines(state_t *st, screen_t *sc, int low, int high)
        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];
@@ -332,7 +370,8 @@ draw_lines(state_t *st, screen_t *sc, int low, int high)
                            (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))) {
@@ -624,8 +663,11 @@ draw_update_screen(screen_t *sc, state_t *st)
        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;
@@ -635,7 +677,7 @@ draw_update_screen(screen_t *sc, state_t *st)
 
        /*
         * 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++) {
index ed58834..eeb28b6 100644 (file)
@@ -84,7 +84,7 @@ x11_process(screen_t *screen, tty_t *ttys, state_t *state)
 
        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) {
@@ -175,12 +175,13 @@ usage(void)
 
        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"
@@ -195,12 +196,12 @@ usage(void)
            "      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"
@@ -209,6 +210,11 @@ usage(void)
            "      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"
@@ -285,8 +291,11 @@ main(int argc, char **argv, char **envp)
 
        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;
@@ -482,6 +491,9 @@ endopt:
                }
        }
 
+       /*
+        * Fancy retro welcome message.
+        */
        state_goto(state, 1, 1);
        state_prints(state, "READY", 0);
        state_goto(state, 1, (cfg_text_width / 2) - 17);
@@ -506,7 +518,8 @@ endopt:
        /*
         * 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);
 
@@ -523,6 +536,10 @@ endopt:
 
                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;
@@ -542,6 +559,7 @@ endopt:
                        break;
                }
                if ((pfd[0].revents & POLLIN) != 0) {
+                       /* X11 event(s) received, process */
                        if (!x11_process(screen, ttys, state)) {
                                warnx("x11_process() false");
                                break;
@@ -563,13 +581,20 @@ endopt:
                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) {
@@ -578,6 +603,14 @@ endopt:
                                }
                        }
 
+                       /*
+                        * 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");
@@ -589,12 +622,18 @@ endopt:
                                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 (;;) {
index 3e87408..04627ff 100644 (file)
@@ -145,26 +145,35 @@ screen_init(void)
        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);
 
index 2bbb88f..d8d17d1 100644 (file)
@@ -40,6 +40,7 @@ typedef struct emulstate_s {
 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);
 
 
@@ -127,6 +128,10 @@ state_init(int pty)
        estate.curparam = -1;
        estate.unicruds = 0;
 
+       /* Smooth scrolling */
+       st->smoothscroll_offset = 0;
+       st->cursor_disabled_ticks = 0;
+
        return st;
 
 err:
@@ -193,6 +198,15 @@ state_updated(state_t *st, int y)
        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)
 {
@@ -229,6 +243,7 @@ state_reset(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;
@@ -323,11 +338,12 @@ void
 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;
 
@@ -340,11 +356,18 @@ state_scroll_range(state_t *st, int offset)
                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) {
@@ -367,12 +390,16 @@ state_scroll_range(state_t *st, int offset)
                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--)
@@ -440,6 +467,7 @@ state_printc_1(state_t *st, uint32_t c, uint32_t m, bool scroll)
                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 {
@@ -977,12 +1005,17 @@ state_emul_printc(state_t *st, uint8_t c)
                                          * 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;
@@ -1032,6 +1065,15 @@ state_emul_printc(state_t *st, uint8_t c)
                                                        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) &&
@@ -1048,6 +1090,7 @@ state_emul_printc(state_t *st, uint8_t c)
                                                if (s2 > 0 && s2 < 501)
                                                        cfg_blinkspeedoff = s2;
                                                draw_blink_reset();
+                                               state_update_cursor(st);
                                        } else if (state->curparam == 3 &&
                                                   state->csiparam[1] ==
                                                   65539) {
@@ -1175,6 +1218,8 @@ endh:
                                        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
@@ -1183,8 +1228,10 @@ endh:
                                          * 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)
@@ -1518,6 +1565,8 @@ endh:
                                        st->cursor_bright = cfg_cursorbright;
                                        break;
                                }
+                               draw_blink_reset();
+                               state_update_cursor(st);
                        }
                        state->escstate = 0;
                        break;
index 20a841c..31e7e61 100644 (file)
@@ -58,9 +58,11 @@ struct state_s {
        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;