module st; import core.stdc.stdint; import core.stdc.time : time_t; import core.sys.posix.sys.types; import core.sys.posix.time : timespec; import core.sys.posix.termios; import core.sys.posix.unistd; import core.sys.posix.fcntl; import core.sys.posix.sys.ioctl; import core.sys.posix.sys.select; import core.sys.posix.sys.wait; import core.sys.posix.signal; import core.stdc.errno; import core.stdc.stdio; import core.stdc.stdlib; import core.stdc.stdlib : strtol, atoi; import core.stdc.string; import core.stdc.string : strchr; import core.stdc.wchar_; import core.stdc.ctype; import core.stdc.limits; import patches; import win : WinMode, MOUSE, xbell, xsetmode, xsetpointermotion, xsetcursor; import config; import xft_types; import std.conv : octal, to; import std.process : environment; import std.logger; // Additional POSIX imports public import core.sys.posix.sys.select : fd_set, FD_ZERO, FD_SET, FD_CLR, FD_ISSET, timeval; // External C functions extern(C) { pid_t setsid(); int system(const(char)*); void perror(const(char)*); int execvp(const(char)*, char**); void _exit(int) nothrow @nogc; int dup2(int, int); int wcwidth(wchar_t); // Platform-specific openpty version(linux) { int openpty(int* master, int* slave, char* name, const(termios)* termp, const(winsize)* winp); } else version(OSX) { int openpty(int* master, int* slave, char* name, const(termios)* termp, const(winsize)* winp); } else version(FreeBSD) { int openpty(int* master, int* slave, char* name, const(termios)* termp, const(winsize)* winp); } else version(OpenBSD) { int openpty(int* master, int* slave, char* name, const(termios)* termp, const(winsize)* winp); } else { static assert(0, "openpty not available on this platform"); } } // Terminal constants enum { UTF_INVALID = 0xFFFD, UTF_SIZ = 4, ESC_BUF_SIZ = 128 * UTF_SIZ, ESC_ARG_SIZ = 16, STR_BUF_SIZ = ESC_BUF_SIZ, STR_ARG_SIZ = ESC_ARG_SIZ, BUFSIZ = 8192, // Standard buffer size } static if (isPatchEnabled!"UNDERCURL_PATCH") { enum CAR_PER_ARG = 4; } immutable string STR_TERM_ST = "\033\\"; immutable string STR_TERM_BEL = "\007"; // Helper macros converted to functions bool IS_SET(uint flag) { return (term.mode & flag) != 0; } bool ISCONTROLC0(uint c) { return between(c, 0, 0x1f) || c == 0x7f; } bool ISCONTROLC1(uint c) { return between(c, 0x80, 0x9f); } bool ISCONTROL(uint c) { return ISCONTROLC0(c) || ISCONTROLC1(c); } bool ISDELIM(wchar_t u) { if (u == 0) return false; for (wchar* p = worddelimiters; *p; p++) { if (*p == u) return true; } return false; } // Terminal modes enum TermMode : uint { WRAP = 1 << 0, INSERT = 1 << 1, ALTSCREEN = 1 << 2, CRLF = 1 << 3, ECHO = 1 << 4, PRINT = 1 << 5, UTF8 = 1 << 6, } // Escape state flags enum : uint { ESC_START = 1, ESC_CSI = 2, ESC_STR = 4, ESC_ALTCHARSET = 8, ESC_STR_END = 16, ESC_TEST = 32, ESC_UTF8 = 64, } static if (isPatchEnabled!"SIXEL_PATCH") { enum TermMode SIXEL = cast(TermMode)(1 << 7); enum TermMode SIXEL_CUR_RT = cast(TermMode)(1 << 8); enum TermMode SIXEL_SDM = cast(TermMode)(1 << 9); } static if (isPatchEnabled!"REFLOW_PATCH") { enum ScrollMode { RESIZE = -1, NOSAVEHIST = 0, SAVEHIST = 1, } } enum CursorMovement { SAVE, LOAD, } enum CursorState { DEFAULT = 0, WRAPNEXT = 1, ORIGIN = 2, } enum SelMode { IDLE = 0, EMPTY = 1, READY = 2 } enum SelType { REGULAR = 1, RECTANGULAR = 2 } enum SnapMode { WORD = 1, LINE = 2 } enum Charset { GRAPHIC0, GRAPHIC1, UK, USA, MULTI, GER, FIN, } enum EscapeState { START = 1, CSI = 2, STR = 4, /* DCS, OSC, PM, APC */ ALTCHARSET = 8, STR_END = 16, /* a final string was encountered */ TEST = 32, /* Enter in test mode */ UTF8 = 64, } static if (isPatchEnabled!"SIXEL_PATCH") { enum EscapeState DCS = cast(EscapeState)128; } // Selection structure struct Selection { int mode; int type; int snap; struct Point { int x, y; } Point nb, ne, ob, oe; int alt; } // CSI Escape sequence struct struct CSIEscape { char[ESC_BUF_SIZ] buf; size_t len; char priv; int[ESC_ARG_SIZ] arg; int narg; char[2] mode; static if (isPatchEnabled!"UNDERCURL_PATCH") { int[ESC_ARG_SIZ][CAR_PER_ARG] carg; } } // STR Escape sequence struct struct STREscape { char type; char* buf; size_t siz; size_t len; char*[STR_ARG_SIZ] args; int narg; char* term; } // Global state __gshared { Selection sel; CSIEscape csiescseq; STREscape strescseq; int cmdfd; int csdfd; // slave fd of the pty pid_t pid; int iofd = 1; char* base64_digits = cast(char*)"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; } // Terminal I/O definitions enum TIOCSCTTY = 0x540E; // Linux value, may need adjustment for other platforms enum TIOCSWINSZ = 0x5414; // Linux value // Wait status macros bool WIFEXITED(int status) nothrow @nogc { return (status & 0x7f) == 0; } int WEXITSTATUS(int status) nothrow @nogc { return (status >> 8) & 0xff; } bool WIFSIGNALED(int status) nothrow @nogc { return ((status & 0x7f) + 1) >> 1 > 0; } int WTERMSIG(int status) nothrow @nogc { return status & 0x7f; } // Import X11 types from deimos import deimos.X11.X; import deimos.X11.Xlib; // Additional X11 types struct FcPattern; // FcFontSet structure struct FcFontSet { int nfont; int sfont; FcPattern** fonts; } // Helper functions/macros T min(T)(T a, T b) { return a < b ? a : b; } T max(T)(T a, T b) { return a < b ? b : a; } size_t len(T)(T[] a) { return a.length; } bool between(T)(T x, T a, T b) { return a <= x && x <= b; } T divceil(T)(T n, T d) { return (n + d - 1) / d; } void defaultTo(T)(ref T a, T b) { if (!a) a = b; } void DEFAULT(T)(ref T a, T b) { if (a == 0) a = b; } void limit(T)(ref T x, T a, T b) { if (x < a) x = a; else if (x > b) x = b; } T clamp(T)(T x, T a, T b) { if (x < a) return a; else if (x > b) return b; return x; } // Define ATTR_WRAP and ATTR_LIGA for easier use enum uint ATTR_WRAP = GlyphAttribute.WRAP; static if (isPatchEnabled!"LIGATURES_PATCH") { enum uint ATTR_LIGA = cast(uint)(1 << 15); } static if (isPatchEnabled!"LIGATURES_PATCH") { bool attrCmp(Glyph a, Glyph b) { return ((a.mode & ~ATTR_WRAP & ~ATTR_LIGA) != (b.mode & ~ATTR_WRAP & ~ATTR_LIGA)) || a.fg != b.fg || a.bg != b.bg; } } else { bool attrCmp(Glyph a, Glyph b) { return a.mode != b.mode || a.fg != b.fg || a.bg != b.bg; } } long timeDiff(timespec t1, timespec t2) { return (t1.tv_sec - t2.tv_sec) * 1000 + (t1.tv_nsec - t2.tv_nsec) / 1_000_000; } void modBit(T)(ref T x, bool set, T bit) { if (set) x |= bit; else x &= ~bit; } uint trueColor(ubyte r, ubyte g, ubyte b) { return (1 << 24) | (r << 16) | (g << 8) | b; } alias TRUECOLOR = trueColor; bool isTrueCol(uint x) { return ((1 << 24) & x) != 0; } enum GlyphAttribute : uint { NULL = 0, SET = 1 << 0, BOLD = 1 << 1, FAINT = 1 << 2, ITALIC = 1 << 3, UNDERLINE = 1 << 4, BLINK = 1 << 5, REVERSE = 1 << 6, INVISIBLE = 1 << 7, STRUCK = 1 << 8, WRAP = 1 << 9, WIDE = 1 << 10, WDUMMY = 1 << 11, } static if (isPatchEnabled!"SELECTION_COLORS_PATCH") { enum GlyphAttribute ATTR_SELECTED = cast(GlyphAttribute)(1 << 12); } static if (isPatchEnabled!"BOXDRAW_PATCH") { enum GlyphAttribute ATTR_BOXDRAW = cast(GlyphAttribute)(1 << 13); } static if (isPatchEnabled!"UNDERCURL_PATCH") { enum GlyphAttribute ATTR_DIRTYUNDERLINE = cast(GlyphAttribute)(1 << 14); } static if (isPatchEnabled!"LIGATURES_PATCH") { enum GlyphAttribute ATTR_LIGA = cast(GlyphAttribute)(1 << 15); } static if (isPatchEnabled!"SIXEL_PATCH") { enum GlyphAttribute ATTR_SIXEL = cast(GlyphAttribute)(1 << 16); } static if (isPatchEnabled!"KEYBOARDSELECT_PATCH" && isPatchEnabled!"REFLOW_PATCH") { enum GlyphAttribute ATTR_HIGHLIGHT = cast(GlyphAttribute)(1 << 17); } enum GlyphAttribute ATTR_BOLD_FAINT = GlyphAttribute.BOLD | GlyphAttribute.FAINT; static if (isPatchEnabled!"SIXEL_PATCH") { struct ImageList { ImageList* next, prev; ubyte* pixels; void* pixmap; void* clipmask; int width; int height; int x; int y; static if (isPatchEnabled!"REFLOW_PATCH") { int reflow_y; } int cols; int cw; int ch; int transparent; } } static if (isPatchEnabled!"WIDE_GLYPHS_PATCH") { enum DrawingMode { NONE = 0, BG = 1 << 0, FG = 1 << 1, } } enum Screen { PRI = -1, ALL = 0, ALT = 1 } enum SelectionMode { IDLE = 0, EMPTY = 1, READY = 2 } enum SelectionType { REGULAR = 1, RECTANGULAR = 2 } enum SelectionSnap { WORD = 1, LINE = 2 } alias Rune = uint_least32_t; struct Glyph { Rune u; uint mode; uint fg; uint bg; static if (isPatchEnabled!"UNDERCURL_PATCH") { int ustyle; int[3] ucolor; } this(Rune u, uint mode, uint fg, uint bg) { this.u = u; this.mode = mode; this.fg = fg; this.bg = bg; } } alias Line = Glyph*; static if (isPatchEnabled!"LIGATURES_PATCH") { struct GlyphFontSeq { int ox; int charlen; int numspecs; Glyph base; } } struct TCursor { Glyph attr; int x; int y; char state = CursorState.DEFAULT; } struct Term { int row; int col; static if (isPatchEnabled!"COLUMNS_PATCH") { int maxcol; } Line* line; Line* alt; static if (isPatchEnabled!"REFLOW_PATCH" || isPatchEnabled!"SCROLLBACK_PATCH") { Line[HISTSIZE] hist; int histi; static if (isPatchEnabled!"REFLOW_PATCH") { int histf; int[2] wrapcwidth; } else { int histn; } int scr; } int* dirty; TCursor c; int ocx; int ocy; int top; int bot; int mode; int esc; char[4] trantbl; int charset; int icharset; int* tabs; static if (isPatchEnabled!"SIXEL_PATCH") { ImageList* images; ImageList* images_alt; } Rune lastc; } union Arg { int i; uint ui; float f; const(void)* v; const(char)* s; } // Scrollback support Line TLINE(int y) { static if (isPatchEnabled!"SCROLLBACK_PATCH") { if (y < term.scr) { int idx = (y + term.histi - term.scr + HISTSIZE + 1) % HISTSIZE; return term.hist[idx]; } else { int idx = y - term.scr; if (idx < 0 || idx >= term.row) { import std.logger : error; error("TLINE: invalid line index ", idx, " (y=", y, ", term.scr=", term.scr, ", term.row=", term.row, ")"); return term.line[0]; // Return first line as fallback } return term.line[idx]; } } else { return term.line[y]; } } static if (isPatchEnabled!"SCROLLBACK_PATCH" || isPatchEnabled!"SCROLLBACK_MOUSE_PATCH" || isPatchEnabled!"SCROLLBACK_MOUSE_ALTSCREEN_PATCH") { extern(C) void kscrolldown(const(Arg)* a) { int n = a.i; if (n < 0) n = term.row + n; if (n > term.scr) n = term.scr; if (term.scr > 0) { term.scr -= n; selscroll(0, -n); tfulldirt(); } static if (isPatchEnabled!"OPENURLONCLICK_PATCH") { if (n > 0) restoremousecursor(); } } extern(C) void kscrollup(const(Arg)* a) { int n = a.i; if (n < 0) n = term.row + n; static if (isPatchEnabled!"REFLOW_PATCH") { if (term.scr + n > term.histf) n = term.histf - term.scr; } else { if (term.scr + n > term.histn) n = term.histn - term.scr; } if (!n) return; if (term.scr <= HISTSIZE - n) { term.scr += n; selscroll(0, n); tfulldirt(); } static if (isPatchEnabled!"OPENURLONCLICK_PATCH") { if (n > 0) restoremousecursor(); } } } // Stub for openurlonclick patch static if (isPatchEnabled!"OPENURLONCLICK_PATCH") { void restoremousecursor() { // TODO: Implement when needed } } struct TermWindow { int tw, th; int w, h; static if (isPatchEnabled!"BACKGROUND_IMAGE_PATCH") { int x, y; } static if (isPatchEnabled!"ANYSIZE_PATCH") { int hborderpx, vborderpx; } int ch; int cw; static if (isPatchEnabled!"VERTCENTER_PATCH") { int cyo; } int mode; int cursor; } struct XWindow { Display* dpy; Colormap cmap; Window win; Drawable buf; GlyphFontSpec* specbuf; static if (isPatchEnabled!"LIGATURES_PATCH") { GlyphFontSeq* specseq; } Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid; static if (isPatchEnabled!"FULLSCREEN_PATCH") { Atom netwmstate, netwmfullscreen; } static if (isPatchEnabled!"NETWMICON_PATCH" || isPatchEnabled!"NETWMICON_LEGACY_PATCH" || isPatchEnabled!"NETWMICON_FF_PATCH") { Atom netwmicon; } struct Ime { XIM xim; XIC xic; XPoint spot; XVaNestedList spotlist; } Ime ime; Draw draw; static if (isPatchEnabled!"BACKGROUND_IMAGE_PATCH") { GC bggc; } Visual* vis; XSetWindowAttributes attrs; static if (isPatchEnabled!"HIDECURSOR_PATCH" || isPatchEnabled!"OPENURLONCLICK_PATCH") { Cursor vpointer, bpointer; int pointerisvisible; } static if (isPatchEnabled!"OPENURLONCLICK_PATCH") { Cursor upointer; } int scr; int isfixed; static if (isPatchEnabled!"ALPHA_PATCH") { int depth; } int l, t; int gm; } struct XSelection { Atom xtarget; char* primary, clipboard; timespec tclick1; timespec tclick2; } struct Shortcut { uint mod; uint keysym; extern(C) void function(const(Arg)*) func; Arg arg; int screen; } struct MouseShortcut { uint mod; uint button; extern(C) void function(const(Arg)*) func; Arg arg; uint release; int screen; } struct Key { uint k; uint mask; char* s; byte appkey; byte appcursor; } struct Font { int height; int width; int ascent; int descent; int badslant; int badweight; short lbearing; short rbearing; XftFont* match; FcFontSet* set; FcPattern* pattern; } struct DC { Color* col; size_t collen; Font font, bfont, ifont, ibfont; GC gc; } // Function implementations extern(C) void die(const(char)* errstr, ...) { import core.stdc.stdio : vfprintf, stderr; import core.stdc.stdlib : exit; import core.stdc.stdarg; va_list ap; va_start(ap, errstr); vfprintf(stderr, errstr, ap); va_end(ap); exit(1); } // Terminal functions implementation extern(C) void tnew(int cols, int rows) { term = Term.init; tresize(cols, rows); treset(); // Initialize history buffer static if (isPatchEnabled!"SCROLLBACK_PATCH" || isPatchEnabled!"REFLOW_PATCH") { for (int i = 0; i < HISTSIZE; i++) { term.hist[i] = cast(Line)xmalloc(cols * Glyph.sizeof); // Initialize with empty glyphs for (int j = 0; j < cols; j++) { term.hist[i][j] = Glyph(' ', 0, defaultfg, defaultbg); } } } // Force full redraw tfulldirt(); } void tresize(int col, int row) { int i; int minrow = min(row, term.row); int mincol = min(col, term.col); int* bp; TCursor c; if (col < 1 || row < 1) { return; } // Backup cursor c = term.c; // Ensure cursor is within bounds if (term.c.x >= col) term.c.x = col - 1; if (term.c.y >= row) term.c.y = row - 1; // Resize to new width static if (isPatchEnabled!"COLUMNS_PATCH") { term.maxcol = max(col, term.maxcol); col = term.maxcol; } // Handle shrinking height - need to save lines to scrollback if (row < term.row) { tcursor(CursorMovement.SAVE); tsetscroll(0, term.row - 1); // Handle both screens - swap between them for (int screen = 0; screen < 2; screen++) { if (term.c.y >= row) { static if (isPatchEnabled!"SCROLLBACK_PATCH") { tscrollup(0, term.c.y - row + 1, !IS_SET(TermMode.ALTSCREEN)); } else { tscrollup(0, term.c.y - row + 1); } } // Free the lines that are now outside the new size for (i = row; i < term.row; i++) { free(term.line[i]); } // Swap screens and restore cursor tswapscreen(); tcursor(CursorMovement.LOAD); } } // Resize buffers - do this AFTER freeing lines when shrinking term.line = cast(Line*)xrealloc(term.line, row * (Line*).sizeof); term.alt = cast(Line*)xrealloc(term.alt, row * (Line*).sizeof); term.dirty = cast(int*)xrealloc(term.dirty, row * int.sizeof); term.tabs = cast(int*)xrealloc(term.tabs, col * int.sizeof); // Resize each row to new width, zero-pad if needed for (i = 0; i < minrow; i++) { term.line[i] = cast(Line)xrealloc(term.line[i], col * Glyph.sizeof); term.alt[i] = cast(Line)xrealloc(term.alt[i], col * Glyph.sizeof); // Clear newly allocated cells if width increased if (col > term.col) { for (int j = term.col; j < col; j++) { term.line[i][j] = Glyph(' ', 0, defaultfg, defaultbg); term.alt[i][j] = Glyph(' ', 0, defaultfg, defaultbg); } } } // Allocate new rows for (/* i = minrow */; i < row; i++) { term.line[i] = cast(Line)xmalloc(col * Glyph.sizeof); term.alt[i] = cast(Line)xmalloc(col * Glyph.sizeof); // Initialize new rows with empty cells for (int j = 0; j < col; j++) { term.line[i][j] = Glyph(' ', 0, defaultfg, defaultbg); term.alt[i][j] = Glyph(' ', 0, defaultfg, defaultbg); } } if (col > term.col) { bp = term.tabs + term.col; memset(bp, 0, (col - term.col) * int.sizeof); while (--bp > term.tabs && !*bp) {} for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) *bp = 1; } // Initialize dirty flags for new rows if (row > term.row) { memset(term.dirty + term.row, 0, (row - term.row) * int.sizeof); } // Resize history buffer if needed static if (isPatchEnabled!"SCROLLBACK_PATCH" || isPatchEnabled!"REFLOW_PATCH") { if (col != term.col) { for (i = 0; i < HISTSIZE; i++) { if (term.hist[i] !is null) { term.hist[i] = cast(Line)xrealloc(term.hist[i], col * Glyph.sizeof); // Initialize new columns if expanding if (col > term.col) { for (int j = term.col; j < col; j++) { term.hist[i][j] = Glyph(' ', 0, defaultfg, defaultbg); } } } else { // Allocate if not already allocated term.hist[i] = cast(Line)xmalloc(col * Glyph.sizeof); for (int j = 0; j < col; j++) { term.hist[i][j] = Glyph(' ', 0, defaultfg, defaultbg); } } } } } // Check if we're expanding horizontally and have scrollback to pull from static if (isPatchEnabled!"SCROLLBACK_PATCH" && isPatchEnabled!"REFLOW_PATCH") { if (col > term.col && term.scr > 0) { // When expanding horizontally, lines that were wrapped might now fit on fewer lines // This means we can pull more content from scrollback int lines_to_pull = 0; // Count wrapped lines that might unwrap with more width // Check current visible lines for ATTR_WRAP int wrapped_lines = 0; for (i = 0; i < term.row - 1; i++) { // Check if last character has ATTR_WRAP set if (term.line[i] && term.col > 0 && (term.line[i][term.col - 1].mode & ATTR_WRAP)) { wrapped_lines++; } } // Estimate how many lines we can pull based on wrapped lines if (wrapped_lines > 0) { // Rough estimate: if doubling width, half the wrapped lines might unwrap if (col >= term.col * 2) { lines_to_pull = min(term.scr, wrapped_lines / 2); } else { // For smaller increases, proportionally fewer lines float ratio = cast(float)(col - term.col) / term.col; lines_to_pull = min(term.scr, cast(int)(wrapped_lines * ratio * 0.5)); } } // Also add some lines just for the width increase lines_to_pull += min(term.scr - lines_to_pull, (col - term.col) / 10); if (lines_to_pull > 0) { // Scroll down to make room for lines from history for (i = 0; i < lines_to_pull && term.scr > 0; i++) { // Decrease scroll position term.scr--; // Shift existing lines down Line temp = term.line[term.row - 1]; for (int j = term.row - 1; j > 0; j--) { term.line[j] = term.line[j - 1]; } // Pull line from history int hist_idx = (term.histi - term.scr + HISTSIZE) % HISTSIZE; // The old bottom line goes to history (it was already saved earlier) // Just free it since we're not maintaining that free(temp); // Allocate new line for first position term.line[0] = cast(Line)xmalloc(col * Glyph.sizeof); // Copy from history and expand to new width Line hist_line = term.hist[hist_idx]; if (hist_line) { for (int k = 0; k < min(term.col, col); k++) { term.line[0][k] = hist_line[k]; } } // Clear any new columns for (int k = term.col; k < col; k++) { term.line[0][k] = Glyph(' ', 0, defaultfg, defaultbg); } } // Update selection if needed if (lines_to_pull > 0) { selscroll(0, lines_to_pull); // Mark all lines as dirty to force redraw tfulldirt(); } } } } // Update terminal size term.col = col; term.row = row; // Fix scroll region tsetscroll(0, row - 1); // Restore cursor if applicable if (c.x >= col) { c.state &= ~CursorState.WRAPNEXT; c.x = col - 1; } term.c = c; } void treset() { uint i; term.c = TCursor(); term.c.attr = Glyph(' ', 0, defaultfg, defaultbg); term.c.x = 0; term.c.y = 0; term.c.state = CursorState.DEFAULT; memset(term.tabs, 0, term.col * int.sizeof); for (i = tabspaces; i < cast(uint)term.col; i += tabspaces) term.tabs[i] = 1; term.top = 0; term.bot = term.row - 1; term.mode = TermMode.WRAP | TermMode.UTF8; memset(term.trantbl.ptr, Charset.USA, term.trantbl.sizeof); term.charset = 0; // Clear both primary and alternate screens for (i = 0; i < 2; i++) { tmoveto(0, 0); tcursor(CursorMovement.SAVE); static if (isPatchEnabled!"COLUMNS_PATCH") { tclearregion(0, 0, term.maxcol-1, term.row-1); } else { tclearregion(0, 0, term.col-1, term.row-1); } tswapscreen(); } static if (isPatchEnabled!"REFLOW_PATCH") { treflow(-1, -1); } } uint tcellattr() { return term.c.attr.mode & ( GlyphAttribute.BOLD | GlyphAttribute.FAINT | GlyphAttribute.ITALIC | GlyphAttribute.UNDERLINE | GlyphAttribute.BLINK | GlyphAttribute.REVERSE | GlyphAttribute.INVISIBLE | GlyphAttribute.STRUCK ); } static if (isPatchEnabled!"REFLOW_PATCH") { void treflow(int col, int row) { // Stub for reflow patch } } extern(C) void selinit() { // Initialize selection xsel.primary = null; xsel.clipboard = null; // Note: xsel.xtarget is initialized in xinit(), don't reset it here } extern(C) void* xmalloc(size_t size) { import core.stdc.stdlib : malloc; void* p = malloc(size); if (!p) die("malloc: %s\n".ptr, "out of memory".ptr); return p; } extern(C) void* xrealloc(void* p, size_t size) { import core.stdc.stdlib : realloc; p = realloc(p, size); if (!p) die("realloc: %s\n".ptr, "out of memory".ptr); return p; } extern(C) char* xstrdup(const(char)* s) { import core.stdc.string : strlen, strcpy; size_t len = strlen(s) + 1; char* p = cast(char*)xmalloc(len); strcpy(p, s); return p; } // TTY functions extern(C) int ttynew(const(char)* line, char* cmd, const(char)* outfile, char** args) { int m, s; if (outfile) { term.mode |= TermMode.PRINT; iofd = (strcmp(outfile, "-") == 0) ? 1 : open(outfile, O_WRONLY | O_CREAT | O_TRUNC, octal!666); if (iofd < 0) { fprintf(stderr, "Error opening %s:%s\n", outfile, strerror(errno)); } } if (line) { cmdfd = open(line, O_RDWR); if (cmdfd < 0) die("open line '%s' failed: %s\n", line, strerror(errno)); dup2(cmdfd, 0); stty(args); return cmdfd; } if (openpty(&m, &s, null, null, null) < 0) die("openpty failed: %s\n", strerror(errno)); switch (pid = fork()) { case -1: die("fork failed: %s\n", strerror(errno)); break; case 0: close(iofd); close(m); setsid(); dup2(s, 0); dup2(s, 1); dup2(s, 2); if (ioctl(s, TIOCSCTTY, null) < 0) die("ioctl TIOCSCTTY failed: %s\n", strerror(errno)); if (s > 2) close(s); version(Posix) { import core.sys.posix.signal : signal, SIG_DFL; signal(SIGCHLD, SIG_DFL); signal(SIGHUP, SIG_DFL); signal(SIGINT, SIG_DFL); signal(SIGQUIT, SIG_DFL); signal(SIGTERM, SIG_DFL); signal(SIGALRM, SIG_DFL); } execsh(cmd, args); break; default: static if (isPatchEnabled!"EXTERNALPIPEIN_PATCH" && isPatchEnabled!"EXTERNALPIPE_PATCH") { csdfd = s; } else { close(s); } cmdfd = m; version(Posix) { import core.sys.posix.signal : signal; signal(SIGCHLD, &sigchld); } } return cmdfd; } void stty(char** args) { char[4096] cmd; // Use larger buffer like _POSIX_ARG_MAX char* p = cmd.ptr; char* s; size_t n, siz; n = strlen(stty_args); if (n > cmd.sizeof - 1) die("incorrect stty parameters\n"); memcpy(cmd.ptr, stty_args, n); p = cmd.ptr + n; siz = cmd.sizeof - n; for (char** ap = args; ap && *ap; ap++) { s = *ap; n = strlen(s); if (n > siz - 1) die("stty parameter length too long\n"); *p++ = ' '; memcpy(p, s, n); p += n; siz -= n + 1; } *p = '\0'; if (system(cmd.ptr) != 0) perror("Couldn't call stty"); } void execsh(char* cmd, char** args) { import core.sys.posix.pwd : passwd, getpwuid; import core.sys.posix.unistd : getuid; char* sh; char* prog; char* arg; const(passwd)* pw; errno = 0; pw = getpwuid(getuid()); if (pw is null) { if (errno) die("getpwuid: %s\n", strerror(errno)); else die("who are you?\n"); } string shellEnv = environment.get("SHELL", null); if (shellEnv !is null) { sh = cast(char*)shellEnv.ptr; } else { if (pw.pw_shell[0]) sh = cast(char*)pw.pw_shell; else sh = cast(char*)"/bin/sh"; // fallback to /bin/sh } if (args) { prog = args[0]; arg = null; } else if (scroll) { prog = scroll; arg = utmp ? utmp : sh; } else if (utmp) { prog = utmp; arg = null; } else { prog = sh; arg = null; } char*[3] argv; argv[0] = prog; argv[1] = arg; argv[2] = null; // DEFAULT(args, argv) - if args is null, use our constructed argv if (!args) args = argv.ptr; environment.remove("COLUMNS"); environment.remove("LINES"); environment.remove("TERMCAP"); import std.string : fromStringz; environment["LOGNAME"] = fromStringz(pw.pw_name).idup; environment["USER"] = fromStringz(pw.pw_name).idup; environment["SHELL"] = fromStringz(sh).idup; environment["HOME"] = fromStringz(pw.pw_dir).idup; environment["TERM"] = fromStringz(termname).idup; environment["COLORTERM"] = "truecolor"; execvp(prog, args); _exit(1); } extern(C) void sigchld(int a) nothrow @nogc { int stat; pid_t p; p = wait(&stat); if (pid != p) return; if (WIFEXITED(stat) && WEXITSTATUS(stat)) _exit(1); else if (WIFSIGNALED(stat)) _exit(1); _exit(0); } extern(C) size_t ttyread() { static char[BUFSIZ] buf; static int buflen = 0; ssize_t ret; // Read from tty ret = read(cmdfd, buf.ptr + buflen, buf.length - buflen); switch (ret) { case 0: return 0; case -1: die("couldn't read from shell: %s\n", strerror(errno)); break; default: buflen += cast(int)ret; break; } twrite(buf.ptr, buflen, 0); buflen = 0; return cast(size_t)ret; } void twrite(const(char)* buf, int n, int show_ctrl) { int charsize; Rune u; int len; for (int i = 0; i < n; i += charsize) { if (IS_SET(TermMode.UTF8)) { // UTF-8 decoding charsize = utf8decode(buf + i, &u, n - i); if (charsize == 0) { charsize = 1; u = buf[i] & 0x7f; } } else { charsize = 1; u = buf[i] & 0xff; } if (show_ctrl && ISCONTROL(u)) { if (u & 0x80) { u &= 0x7f; tputc('^'); tputc('['); } else if (u != '\n' && u != '\r' && u != '\t') { u ^= 0x40; tputc('^'); } } tputc(u); } } void tputc(Rune u) { char[UTF_SIZ] c; int width; size_t len; bool control; control = ISCONTROL(u); if (u < 127 || !IS_SET(TermMode.UTF8)) { c[0] = cast(char)u; width = len = 1; } else { len = utf8encode(u, c.ptr); if (!control && (width = wcwidth(u)) == -1) width = 1; } /* * STR sequence must be checked before anything else * because it uses all following characters until it * receives a ESC, a SUB, a ST or any other C1 control * character. */ if (term.esc & ESC_STR) { if (u == '\a' || u == octal!"30" || u == octal!"32" || u == octal!"33" || ISCONTROLC1(u)) { term.esc &= ~(ESC_START|ESC_STR); term.esc |= ESC_STR_END; goto check_control_code; } if (strescseq.len+len >= strescseq.siz) { /* * Here is a bug in terminals. If the user never sends * some code to stop the str or esc command, then st * will stop responding. But this is better than * silently failing with unknown characters. At least * then users will report back. * * In the case users ever get fixed, here is the code: */ /* * term.esc = 0; * strhandle(); */ if (strescseq.siz > (size_t.max - UTF_SIZ) / 2) return; strescseq.siz *= 2; strescseq.buf = cast(char*)xrealloc(strescseq.buf, strescseq.siz); } memmove(&strescseq.buf[strescseq.len], c.ptr, len); strescseq.len += len; return; } check_control_code: /* * Actions of control codes must be performed as soon they arrive * because they can be embedded inside a control sequence, and * they must not cause conflicts with sequences. */ if (control) { /* in UTF-8 mode ignore handling C1 control characters */ if (IS_SET(TermMode.UTF8) && ISCONTROLC1(u)) return; tcontrolcode(u); /* * control codes are not shown ever */ if (!term.esc) term.lastc = 0; return; } else if (term.esc & ESC_START) { if (term.esc & ESC_CSI) { csiescseq.buf[csiescseq.len++] = cast(char)u; if (between(u, 0x40, 0x7E) || csiescseq.len >= ESC_BUF_SIZ-1) { // Removed verbose CSI sequence trace term.esc = 0; csiparse(); csihandle(); } return; } else if (term.esc & ESC_ALTCHARSET) { tdeftran(cast(char)u); } else if (term.esc & ESC_TEST) { tdectest(cast(char)u); } else { if (!eschandle(u)) return; /* sequence already finished */ } term.esc = 0; /* * All characters which form part of a sequence are not * printed */ return; } // Handle normal character if (IS_SET(TermMode.WRAP) && term.c.state & CursorState.WRAPNEXT) { term.line[term.c.y][term.c.x].mode |= GlyphAttribute.WRAP; tnewline(1); } if (IS_SET(TermMode.INSERT) && term.c.x + width < term.col) { memmove(&term.line[term.c.y][term.c.x + width], &term.line[term.c.y][term.c.x], (term.col - term.c.x - width) * Glyph.sizeof); } if (term.c.x + width > term.col) { if (IS_SET(TermMode.WRAP)) tnewline(1); else tmoveto(term.col - width, term.c.y); } tsetchar(u, &term.c.attr, term.c.x, term.c.y); term.dirty[term.c.y] = 1; // Mark line as dirty if (width == 2) { term.line[term.c.y][term.c.x].mode |= GlyphAttribute.WIDE; if (term.c.x + 1 < term.col) { term.line[term.c.y][term.c.x + 1].u = 0; term.line[term.c.y][term.c.x + 1].mode |= GlyphAttribute.WDUMMY; } } if (term.c.x + width < term.col) { tmoveto(term.c.x + width, term.c.y); } else { term.c.state |= CursorState.WRAPNEXT; } } void tcontrolcode(uint ascii) { switch (ascii) { case '\t': // HT tputtab(1); return; case '\b': // BS tmoveto(term.c.x - 1, term.c.y); return; case '\r': // CR tmoveto(0, term.c.y); return; case '\f': // LF case '\v': // VT case '\n': // LF tnewline(IS_SET(TermMode.CRLF)); return; case '\a': // BEL if (term.esc & ESC_STR_END) { term.esc = 0; } else { xbell(); } return; case '\033': // ESC csireset(); term.esc &= ~(ESC_CSI | ESC_ALTCHARSET | ESC_TEST); term.esc |= ESC_START; return; case '\016': // SO (LS1 -- Locking shift 1) case '\017': // SI (LS0 -- Locking shift 0) // charset handling return; case '\032': // SUB tsetchar('?', &term.c.attr, term.c.x, term.c.y); goto case; case '\030': // CAN csireset(); break; case '\005': // ENQ (IGNORED) case '\000': // NUL (IGNORED) case '\021': // XON (IGNORED) case '\023': // XOFF (IGNORED) case octal!"177": // DEL (IGNORED) return; case 0x80: // TODO: PAD case 0x81: // TODO: HOP case 0x82: // TODO: BPH case 0x83: // TODO: NBH case 0x84: // TODO: IND break; case 0x85: // NEL -- Next line tnewline(1); break; case 0x86: // TODO: SSA case 0x87: // TODO: ESA break; case 0x88: // HTS -- Horizontal tab stop term.tabs[term.c.x] = 1; break; case 0x89: // TODO: HTJ case 0x8a: // TODO: VTS case 0x8b: // TODO: PLD case 0x8c: // TODO: PLU case 0x8d: // TODO: RI case 0x8e: // TODO: SS2 case 0x8f: // TODO: SS3 case 0x91: // TODO: PU1 case 0x92: // TODO: PU2 case 0x93: // TODO: STS case 0x94: // TODO: CCH case 0x95: // TODO: MW case 0x96: // TODO: SPA case 0x97: // TODO: EPA case 0x98: // TODO: SOS case 0x99: // TODO: SGCI break; case 0x9a: // DECID -- Identify Terminal ttywrite(vtiden, cast(int)strlen(vtiden), 0); break; case 0x9b: // TODO: CSI case 0x9c: // TODO: ST break; case 0x90: // DCS -- Device Control String case 0x9d: // OSC -- Operating System Command case 0x9e: // PM -- Privacy Message case 0x9f: // APC -- Application Program Command tstrsequence(ascii); return; default: break; } term.esc &= ~(ESC_STR_END | ESC_STR); } int utf8decode(const(char)* c, Rune* u, size_t clen) { ubyte[4] seq; size_t len; if (clen == 0) return 0; seq[0] = cast(ubyte)c[0]; // Determine UTF-8 sequence length if ((seq[0] & 0x80) == 0) { *u = seq[0]; return 1; } else if ((seq[0] & 0xE0) == 0xC0) { len = 2; } else if ((seq[0] & 0xF0) == 0xE0) { len = 3; } else if ((seq[0] & 0xF8) == 0xF0) { len = 4; } else { *u = UTF_INVALID; return 1; } if (clen < len) { *u = UTF_INVALID; return 1; } for (size_t i = 1; i < len; i++) { seq[i] = cast(ubyte)c[i]; if ((seq[i] & 0xC0) != 0x80) { *u = UTF_INVALID; return 1; } } // Decode UTF-8 sequence switch (len) { case 2: *u = ((seq[0] & 0x1F) << 6) | (seq[1] & 0x3F); break; case 3: *u = ((seq[0] & 0x0F) << 12) | ((seq[1] & 0x3F) << 6) | (seq[2] & 0x3F); break; case 4: *u = ((seq[0] & 0x07) << 18) | ((seq[1] & 0x3F) << 12) | ((seq[2] & 0x3F) << 6) | (seq[3] & 0x3F); break; default: return 1; } // Validate the decoded codepoint if (*u > 0x10FFFF || (*u >= 0xD800 && *u <= 0xDFFF)) { *u = UTF_INVALID; } return cast(int)len; } extern(C) void ttywrite(const(char)* s, size_t n, int may_echo) { fd_set wfd, rfd; ssize_t r; size_t lim = 256; if (IS_SET(TermMode.ECHO) && may_echo) { twrite(s, cast(int)n, 1); } if (!IS_SET(TermMode.CRLF)) { ttywriteraw(s, n); return; } // Handle CRLF conversion // Stub implementation ttywriteraw(s, n); } void ttywriteraw(const(char)* s, size_t n) { fd_set wfd; fd_set* rfd; ssize_t r; size_t lim = 256; if (n > lim) { // Handle flow control n = lim; } // Write to tty for (;;) { r = write(cmdfd, s, n); //printf("ttywriteraw: write returned %zd\n", r); //fflush(stdout); if (r < 0) { if (errno == EAGAIN || errno == EINTR) { continue; } die("write error on tty: %s\n", strerror(errno)); } if (r < cast(ssize_t)n) { s += r; n -= r; } else { break; } } } extern(C) void ttyresize(int tw, int th) { winsize w; w.ws_row = cast(ushort)term.row; w.ws_col = cast(ushort)term.col; w.ws_xpixel = cast(ushort)tw; w.ws_ypixel = cast(ushort)th; if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0) fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno)); } // Function declarations extern(C): void redraw(); void draw(); void drawregion(int, int, int, int); void tfulldirt() { tsetdirt(0, term.row-1); } void printscreen(const(Arg)*); void printsel(const(Arg)*); void sendbreak(const(Arg)*); void toggleprinter(const(Arg)*); int tattrset(int); int tisaltscr(); void tsetdirtattr(int); void ttyhangup(); extern(C) void resettitle(); extern(C) int tisaltscr() { return IS_SET(TermMode.ALTSCREEN) ? 1 : 0; } void selclear(); void selremove(); void selstart(int, int, int); void selextend(int, int, int, int); int selected(int x, int y) { if (sel.mode == SelMode.EMPTY || sel.ob.x == -1 || sel.alt != IS_SET(TermMode.ALTSCREEN)) { return 0; } int result; if (sel.type == SelType.RECTANGULAR) { result = between(y, sel.nb.y, sel.ne.y) && between(x, sel.nb.x, sel.ne.x); } else { result = between(y, sel.nb.y, sel.ne.y) && (y != sel.nb.y || x >= sel.nb.x) && (y != sel.ne.y || x <= sel.ne.x); } return result; } char* getsel() { char* str; char* ptr; int y, bufsize, lastx, linelen; const(Glyph)* gp; const(Glyph)* last; if (sel.ob.x == -1) return null; bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; ptr = str = cast(char*)xmalloc(bufsize); /* append every set & selected glyph to the selection */ for (y = sel.nb.y; y <= sel.ne.y; y++) { if ((linelen = tlinelen(y)) == 0) { *ptr++ = '\n'; continue; } if (sel.type == SelType.RECTANGULAR) { gp = &TLINE(y)[sel.nb.x]; lastx = sel.ne.x; } else { gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0]; lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; } last = &TLINE(y)[min(lastx, linelen-1)]; while (last >= gp && last.u == ' ') --last; for ( ; gp <= last; ++gp) { if (gp.mode & GlyphAttribute.WDUMMY) continue; ptr += utf8encode(gp.u, ptr); } /* * Copy and pasting of line endings is inconsistent * in the inconsistent terminal and GUI world. * The best solution seems like to produce '\n' when * something is copied from st and convert '\n' to * '\r', when something to be pasted is received by * st. * FIXME: Fix the computer world. */ if ((y < sel.ne.y || lastx >= linelen) && (!(last.mode & GlyphAttribute.WRAP) || sel.type == SelType.RECTANGULAR)) *ptr++ = '\n'; } *ptr = 0; return str; } size_t utf8encode(Rune, char*); int xgetcolor(int x, ubyte* r, ubyte* g, ubyte* b); // UTF-8 constants __gshared immutable ubyte[UTF_SIZ + 1] utfbyte = [0x80, 0, 0xC0, 0xE0, 0xF0]; __gshared immutable ubyte[UTF_SIZ + 1] utfmask = [0xC0, 0x80, 0xE0, 0xF0, 0xF8]; __gshared immutable Rune[UTF_SIZ + 1] utfmin = [0, 0, 0x80, 0x800, 0x10000]; __gshared immutable Rune[UTF_SIZ + 1] utfmax = [0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF]; // Helper macro for scrollback static if (isPatchEnabled!"BOXDRAW_PATCH") { int isboxdraw(Rune); ushort boxdrawindex(const(Glyph)*); void boxdraw_xinit(Display*, Colormap, XftDraw*, Visual*); void drawboxes(int, int, int, int, XftColor*, XftColor*, const(XftGlyphFontSpec)*, int); } // Global variables - defined in config module extern(C) __gshared { extern char* utmp; extern char* scroll; extern char* stty_args; extern char* vtiden; extern wchar* worddelimiters; static if (isPatchEnabled!"KEYBOARDSELECT_PATCH" && isPatchEnabled!"REFLOW_PATCH") { extern wchar* kbds_sdelim; extern wchar* kbds_ldelim; } extern int allowaltscreen; extern int allowwindowops; extern char* termname; extern uint tabspaces; extern uint defaultfg; extern uint defaultbg; extern uint defaultcs; static if (isPatchEnabled!"EXTERNALPIPE_PATCH") { int extpipeactive; } static if (isPatchEnabled!"BOXDRAW_PATCH") { extern const int boxdraw, boxdraw_bold, boxdraw_braille; } static if (isPatchEnabled!"ALPHA_PATCH") { extern float alpha; static if (isPatchEnabled!"ALPHA_FOCUS_HIGHLIGHT_PATCH") { extern float alphaUnfocused; } } DC dc; XWindow xw; XSelection xsel; TermWindow win; Term term; } // Additional functions needed by x.d extern(C) int tattrset(int attr) { // Check if any terminal cells have the given attribute for (int i = 0; i < term.row; i++) { for (int j = 0; j < term.col; j++) { if (term.line[i][j].mode & attr) return 1; } } return 0; } extern(C) void tsetdirtattr(int attr) { // Mark lines dirty that contain the given attribute for (int i = 0; i < term.row; i++) { for (int j = 0; j < term.col; j++) { if (term.line[i][j].mode & attr) { term.dirty[i] = 1; break; } } } } extern(C) void ttyhangup() { // Send SIGHUP to shell import core.sys.posix.signal : kill, SIGHUP; if (pid != 0) { kill(pid, SIGHUP); } } // Helper functions void tmoveto(int x, int y) { int miny, maxy; if (term.c.state & CursorState.ORIGIN) { miny = term.top; maxy = term.bot; } else { miny = 0; maxy = term.row - 1; } term.c.state &= ~CursorState.WRAPNEXT; term.c.x = clamp(x, 0, term.col-1); term.c.y = clamp(y, miny, maxy); } void tsetchar(Rune u, Glyph* attr, int x, int y) { if (term.line[y][x].mode & GlyphAttribute.WIDE) { if (x+1 < term.col) { term.line[y][x+1].u = ' '; term.line[y][x+1].mode &= ~GlyphAttribute.WDUMMY; } } else if (term.line[y][x].mode & GlyphAttribute.WDUMMY) { term.line[y][x-1].u = ' '; term.line[y][x-1].mode &= ~GlyphAttribute.WIDE; } term.dirty[y] = 1; term.line[y][x] = *attr; term.line[y][x].u = u; static if (isPatchEnabled!"BOXDRAW_PATCH") { import patch.boxdraw : isboxdraw; if (isboxdraw(u)) term.line[y][x].mode |= ATTR_BOXDRAW; } } void tnewline(int first_col) { int y = term.c.y; if (y == term.bot) { static if (isPatchEnabled!"SCROLLBACK_PATCH") { tscrollup(term.top, 1, 1); // Save to history } else { tscrollup(term.top, 1); } } else { y++; } tmoveto(first_col ? 0 : term.c.x, y); } void tputtab(int n) { uint x = term.c.x; if (n > 0) { while (x < cast(uint)term.col && n--) for (++x; x < cast(uint)term.col && !term.tabs[x]; ++x) {} } else if (n < 0) { while (x > 0 && n++) for (--x; x > 0 && !term.tabs[x]; --x) {} } term.c.x = min(x, cast(uint)term.col - 1); } static if (isPatchEnabled!"SCROLLBACK_PATCH") { void tscrollup(int orig, int n, int copyhist = 0) { int i; Line temp; trace("tscrollup (SCROLLBACK): orig=", orig, " n=", n, " copyhist=", copyhist, " term.bot=", term.bot, " term.top=", term.top); static if (isPatchEnabled!"OPENURLONCLICK_PATCH") { restoremousecursor(); } n = clamp(n, 0, term.bot-orig+1); if (copyhist && !IS_SET(TermMode.ALTSCREEN)) { for (i = 0; i < n; i++) { term.histi = (term.histi + 1) % HISTSIZE; temp = term.hist[term.histi]; term.hist[term.histi] = term.line[orig+i]; term.line[orig+i] = temp; } static if (isPatchEnabled!"REFLOW_PATCH") { term.histf = min(term.histf + n, HISTSIZE); } else { term.histn = min(term.histn + n, HISTSIZE); } if (term.scr > 0 && term.scr < HISTSIZE) term.scr = min(term.scr + n, HISTSIZE-1); } tclearregion(0, orig, term.col-1, orig+n-1); tsetdirt(orig+n, term.bot); for (i = orig; i <= term.bot-n; i++) { temp = term.line[i]; term.line[i] = term.line[i+n]; term.line[i+n] = temp; } selscroll(orig, -n); } } else { void tscrollup(int orig, int n) { int i; Line temp; n = clamp(n, 0, term.bot-orig+1); tclearregion(0, orig, term.col-1, orig+n-1); tsetdirt(orig+n, term.bot); for (i = orig; i <= term.bot-n; i++) { temp = term.line[i]; term.line[i] = term.line[i+n]; term.line[i+n] = temp; } selscroll(orig, -n); } } void tscrolldown(int orig, int n) { int i; Line temp; n = clamp(n, 0, term.bot-orig+1); tsetdirt(orig, term.bot-n); tclearregion(0, term.bot-n+1, term.col-1, term.bot); for (i = term.bot; i >= orig+n; i--) { temp = term.line[i]; term.line[i] = term.line[i-n]; term.line[i-n] = temp; } selscroll(orig, n); } void csireset() { memset(&csiescseq, 0, CSIEscape.sizeof); } void strreset() { strescseq.buf = cast(char*)xrealloc(strescseq.buf, STR_BUF_SIZ); strescseq.siz = STR_BUF_SIZ; strescseq.len = 0; strescseq.type = ' '; strescseq.narg = 0; } void tstrsequence(uint c) { switch (c) { case 0x90: // DCS -- Device Control String c = 'P'; break; case 0x9f: // APC -- Application Program Command c = '_'; break; case 0x9e: // PM -- Privacy Message c = '^'; break; case 0x9d: // OSC -- Operating System Command c = ']'; break; default: return; } strreset(); strescseq.type = cast(char)c; term.esc |= ESC_STR; } void selscroll(int orig, int n) { if (sel.ob.x == -1 || sel.alt != IS_SET(TermMode.ALTSCREEN)) return; if (between(sel.nb.y, orig, term.bot) != between(sel.ne.y, orig, term.bot)) { selclear(); } else if (between(sel.nb.y, orig, term.bot)) { sel.ob.y += n; sel.oe.y += n; if (sel.ob.y < term.top || sel.ob.y > term.bot || sel.oe.y < term.top || sel.oe.y > term.bot) { selclear(); } else { selnormalize(); } } } void selstart(int col, int row, int snap) { selclear(); sel.mode = SelMode.EMPTY; sel.type = SelType.REGULAR; sel.alt = IS_SET(TermMode.ALTSCREEN); sel.snap = snap; sel.oe.x = sel.ob.x = col; sel.oe.y = sel.ob.y = row; selnormalize(); if (sel.snap != 0) sel.mode = SelMode.READY; tsetdirt(sel.nb.y, sel.ne.y); } void selextend(int col, int row, int type, int done) { int oldey, oldex, oldsby, oldsey, oldtype; if (sel.mode == SelMode.IDLE) { return; } if (done && sel.mode == SelMode.EMPTY) { selclear(); return; } oldey = sel.oe.y; oldex = sel.oe.x; oldsby = sel.nb.y; oldsey = sel.ne.y; oldtype = sel.type; sel.oe.x = col; sel.oe.y = row; sel.type = type; selnormalize(); if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SelMode.EMPTY) tsetdirt(min(sel.nb.y, oldsby), max(sel.ne.y, oldsey)); sel.mode = done ? SelMode.IDLE : SelMode.READY; } void selclear() { if (sel.ob.x == -1) return; sel.mode = SelMode.IDLE; sel.ob.x = -1; tsetdirt(sel.nb.y, sel.ne.y); } void selnormalize() { int i; if (sel.type == SelType.REGULAR && sel.ob.y != sel.oe.y) { sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x; sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x; } else { sel.nb.x = min(sel.ob.x, sel.oe.x); sel.ne.x = max(sel.ob.x, sel.oe.x); } sel.nb.y = min(sel.ob.y, sel.oe.y); sel.ne.y = max(sel.ob.y, sel.oe.y); selsnap(&sel.nb.x, &sel.nb.y, -1); selsnap(&sel.ne.x, &sel.ne.y, +1); /* expand selection over line breaks */ if (sel.type == SelType.RECTANGULAR) return; i = tlinelen(sel.nb.y); if (i < sel.nb.x) sel.nb.x = i; if (tlinelen(sel.ne.y) <= sel.ne.x) sel.ne.x = term.col - 1; } void selsnap(int* x, int* y, int direction) { // Simple implementation - could be enhanced for word/line selection switch (sel.snap) { case SnapMode.WORD: // Implement word snapping wchar_t prevdelim = direction > 0 ? ' ' : '\0'; wchar_t curdelim; for (;;) { if (direction < 0 && *x <= 0) { if (*y > 0 && term.line[*y - 1][term.col - 1].mode & ATTR_WRAP) { *y -= 1; *x = term.col - 1; } else { break; } } if (direction > 0 && *x >= term.col - 1) { if (*y < term.row - 1 && term.line[*y][term.col - 1].mode & ATTR_WRAP) { *y += 1; *x = 0; } else { break; } } if (term.line[*y][*x].mode & GlyphAttribute.WDUMMY) { *x += direction; continue; } curdelim = term.line[*y][*x].u; if (ISDELIM(curdelim) != ISDELIM(prevdelim)) break; *x += direction; prevdelim = curdelim; } break; case SnapMode.LINE: *x = (direction < 0) ? 0 : term.col - 1; break; default: break; } } void tsetdirt(int top, int bot) { int i; top = clamp(top, 0, term.row-1); bot = clamp(bot, 0, term.row-1); for (i = top; i <= bot; i++) term.dirty[i] = 1; } // Implementation of missing functions extern(C) ptrdiff_t xwrite(int fd, const(char)* s, size_t len) { size_t aux = len; ptrdiff_t r; while (len > 0) { r = write(fd, s, len); if (r < 0) return r; len -= r; s += r; } return aux; } extern(C) int tlinelen(int y) { int i = term.col; static if (isPatchEnabled!"SCROLLBACK_PATCH") { if (TLINE(y)[i - 1].mode & GlyphAttribute.WRAP) return i; while (i > 0 && TLINE(y)[i - 1].u == ' ') --i; } else { if (term.line[y][i - 1].mode & GlyphAttribute.WRAP) return i; while (i > 0 && term.line[y][i - 1].u == ' ') --i; } return i; } char utf8encodebyte(Rune u, size_t i) { return cast(char)(utfbyte[i] | (u & ~utfmask[i])); } size_t utf8validate(Rune* u, size_t i) { if (!between(*u, utfmin[i], utfmax[i]) || between(*u, 0xD800, 0xDFFF)) *u = UTF_INVALID; for (i = 1; *u > utfmax[i]; ++i) {} return i; } extern(C) size_t utf8encode(Rune u, char* c) { size_t len, i; len = utf8validate(&u, 0); if (len > UTF_SIZ) return 0; for (i = len - 1; i != 0; --i) { c[i] = utf8encodebyte(u, 0); u >>= 6; } c[0] = utf8encodebyte(u, len); return len; } // Terminal manipulation functions void tmoveato(int x, int y) { int actual_y = y + ((term.c.state & CursorState.ORIGIN) ? term.top : 0); trace("tmoveato: x=", x, " y=", y, " actual_y=", actual_y, " term.row=", term.row, " term.bot=", term.bot); tmoveto(x, actual_y); } void tclearregion(int x1, int y1, int x2, int y2) { int x, y, temp; if (x1 > x2) { temp = x1; x1 = x2; x2 = temp; } if (y1 > y2) { temp = y1; y1 = y2; y2 = temp; } x1 = clamp(x1, 0, term.col-1); x2 = clamp(x2, 0, term.col-1); y1 = clamp(y1, 0, term.row-1); y2 = clamp(y2, 0, term.row-1); for (y = y1; y <= y2; y++) { term.dirty[y] = 1; for (x = x1; x <= x2; x++) { Glyph* gp = &term.line[y][x]; if (selected(x, y)) selclear(); gp.fg = term.c.attr.fg; gp.bg = term.c.attr.bg; gp.mode = 0; gp.u = ' '; } } } void tdeletechar(int n) { int dst, src, size; Glyph* line; n = clamp(n, 0, term.col - term.c.x); dst = term.c.x; src = term.c.x + n; size = term.col - src; line = term.line[term.c.y]; memmove(&line[dst], &line[src], size * Glyph.sizeof); tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); } void tinsertblank(int n) { int dst, src, size; Glyph* line; n = clamp(n, 0, term.col - term.c.x); dst = term.c.x + n; src = term.c.x; size = term.col - dst; line = term.line[term.c.y]; memmove(&line[dst], &line[src], size * Glyph.sizeof); tclearregion(src, term.c.y, dst - 1, term.c.y); } void tinsertblankline(int n) { if (between(term.c.y, term.top, term.bot)) tscrolldown(term.c.y, n); } void tdeleteline(int n) { if (between(term.c.y, term.top, term.bot)) tscrollup(term.c.y, n); } void tsetattr(const(int)* attr, int l) { int i; uint idx; // Handle CSI m with no parameters (reset) if (l == 0) { term.c.attr.mode &= ~(GlyphAttribute.BOLD | GlyphAttribute.FAINT | GlyphAttribute.ITALIC | GlyphAttribute.UNDERLINE | GlyphAttribute.BLINK | GlyphAttribute.REVERSE | GlyphAttribute.INVISIBLE | GlyphAttribute.STRUCK); term.c.attr.fg = defaultfg; term.c.attr.bg = defaultbg; return; } for (i = 0; i < l; i++) { switch (attr[i]) { case 0: term.c.attr.mode &= ~(GlyphAttribute.BOLD | GlyphAttribute.FAINT | GlyphAttribute.ITALIC | GlyphAttribute.UNDERLINE | GlyphAttribute.BLINK | GlyphAttribute.REVERSE | GlyphAttribute.INVISIBLE | GlyphAttribute.STRUCK); term.c.attr.fg = defaultfg; term.c.attr.bg = defaultbg; break; case 1: term.c.attr.mode |= GlyphAttribute.BOLD; break; case 2: term.c.attr.mode |= GlyphAttribute.FAINT; break; case 3: term.c.attr.mode |= GlyphAttribute.ITALIC; break; case 4: term.c.attr.mode |= GlyphAttribute.UNDERLINE; break; case 5: /* slow blink */ case 6: /* rapid blink */ term.c.attr.mode |= GlyphAttribute.BLINK; break; case 7: term.c.attr.mode |= GlyphAttribute.REVERSE; break; case 8: term.c.attr.mode |= GlyphAttribute.INVISIBLE; break; case 9: term.c.attr.mode |= GlyphAttribute.STRUCK; break; case 22: term.c.attr.mode &= ~(GlyphAttribute.BOLD | GlyphAttribute.FAINT); break; case 23: term.c.attr.mode &= ~GlyphAttribute.ITALIC; break; case 24: term.c.attr.mode &= ~GlyphAttribute.UNDERLINE; break; case 25: term.c.attr.mode &= ~GlyphAttribute.BLINK; break; case 27: term.c.attr.mode &= ~GlyphAttribute.REVERSE; break; case 28: term.c.attr.mode &= ~GlyphAttribute.INVISIBLE; break; case 29: term.c.attr.mode &= ~GlyphAttribute.STRUCK; break; case 38: if (i + 2 < l && attr[i + 1] == 5) { term.c.attr.fg = attr[i + 2]; i += 2; } else if (i + 4 < l && attr[i + 1] == 2) { idx = attr[i + 2] << 16 | attr[i + 3] << 8 | attr[i + 4]; term.c.attr.fg = TRUECOLOR(cast(ubyte)attr[i + 2], cast(ubyte)attr[i + 3], cast(ubyte)attr[i + 4]); i += 4; } break; case 39: term.c.attr.fg = defaultfg; break; case 48: if (i + 2 < l && attr[i + 1] == 5) { term.c.attr.bg = attr[i + 2]; i += 2; } else if (i + 4 < l && attr[i + 1] == 2) { idx = attr[i + 2] << 16 | attr[i + 3] << 8 | attr[i + 4]; term.c.attr.bg = TRUECOLOR(cast(ubyte)attr[i + 2], cast(ubyte)attr[i + 3], cast(ubyte)attr[i + 4]); i += 4; } break; case 49: term.c.attr.bg = defaultbg; break; default: if (between(attr[i], 30, 37)) { term.c.attr.fg = attr[i] - 30; } else if (between(attr[i], 40, 47)) { term.c.attr.bg = attr[i] - 40; } else if (between(attr[i], 90, 97)) { term.c.attr.fg = attr[i] - 90 + 8; } else if (between(attr[i], 100, 107)) { term.c.attr.bg = attr[i] - 100 + 8; } else { fprintf(stderr, "erresc: unhandled CSI SGR %d\n", attr[i]); } break; } } } void tsetscroll(int t, int b) { int temp; t = clamp(t, 0, term.row-1); b = clamp(b, 0, term.row-1); if (t > b) { temp = t; t = b; b = temp; } term.top = t; term.bot = b; } void tdump() { int i; for (i = 0; i < term.row; ++i) tdumpline(i); } void tdumpline(int n) { int i; Glyph* line; char[UTF_SIZ] buf; int len; line = term.line[n]; for (i = 0; i < term.col; ++i) { if (line[i].u == ' ') continue; len = cast(int)utf8encode(line[i].u, buf.ptr); write(1, buf.ptr, len); } printf("\n"); } void tdumpsel() { char* ptr; if ((ptr = getsel()) !is null) { printf("%s", ptr); free(ptr); } } void csidump() { size_t i; uint c; fprintf(stderr, "ESC["); for (i = 0; i < csiescseq.len; i++) { c = csiescseq.buf[i] & 0xff; if (isprint(c)) { putc(c, stderr); } else if (c == '\n') { fprintf(stderr, "(\\n)"); } else if (c == '\r') { fprintf(stderr, "(\\r)"); } else if (c == 0x1b) { fprintf(stderr, "(\\e)"); } else { fprintf(stderr, "(%02x)", c); } } putc('\n', stderr); } void tswapscreen() { Line* tmp = term.line; term.line = term.alt; term.alt = tmp; term.mode ^= TermMode.ALTSCREEN; tfulldirt(); } void tcursor(int mode) { static TCursor[2] c; int alt = IS_SET(TermMode.ALTSCREEN) ? 1 : 0; if (mode == CursorMovement.SAVE) { c[alt] = term.c; } else if (mode == CursorMovement.LOAD) { term.c = c[alt]; tmoveto(c[alt].x, c[alt].y); } } void tsetmode(int priv, int set, const(int)* args, int narg) { int* lim; int mode; int alt; for (lim = cast(int*)args + narg; args < lim; ++args) { if (priv) { switch (*args) { case 1: /* DECCKM -- Cursor key */ xsetmode(set, WinMode.APPCURSOR); break; case 5: /* DECSCNM -- Reverse video */ mode = IS_SET(WinMode.REVERSE) ? 1 : 0; if (mode != set) { xsetmode(set, WinMode.REVERSE); redraw(); } break; case 6: /* DECOM -- Origin */ term.c.state = set ? (term.c.state | CursorState.ORIGIN) : (term.c.state & ~CursorState.ORIGIN); tmoveato(0, 0); break; case 7: /* DECAWM -- Auto wrap */ term.mode = set ? (term.mode | TermMode.WRAP) : (term.mode & ~TermMode.WRAP); break; case 0: /* Error (IGNORED) */ case 2: /* DECANM -- ANSI/VT52 (IGNORED) */ case 3: /* DECCOLM -- Column (IGNORED) */ case 4: /* DECSCLM -- Scroll (IGNORED) */ case 8: /* DECARM -- Auto repeat (IGNORED) */ case 18: /* DECPFF -- Printer feed (IGNORED) */ case 19: /* DECPEX -- Printer extent (IGNORED) */ case 42: /* DECNRCM -- National characters (IGNORED) */ case 12: /* att610 -- Start blinking cursor (IGNORED) */ break; case 25: /* DECTCEM -- Text Cursor Enable Mode */ xsetmode(!set, WinMode.HIDE); break; case 9: /* X10 mouse compatibility mode */ xsetpointermotion(0); xsetmode(0, MOUSE); xsetmode(set, WinMode.MOUSEX10); break; case 1000: /* 1000: report button press */ xsetpointermotion(0); xsetmode(0, MOUSE); xsetmode(set, WinMode.MOUSEBTN); break; case 1002: /* 1002: report motion on button press */ xsetpointermotion(0); xsetmode(0, MOUSE); xsetmode(set, WinMode.MOUSEMOTION); break; case 1003: /* 1003: enable all mouse motions */ xsetpointermotion(set); xsetmode(0, MOUSE); xsetmode(set, WinMode.MOUSEMANY); break; case 1004: /* 1004: send focus events to tty */ xsetmode(set, WinMode.FOCUS); break; case 1006: /* 1006: extended reporting mode */ xsetmode(set, WinMode.MOUSESGR); break; case 1034: xsetmode(set, WinMode.MODE_8BIT); break; case 1049: /* swap screen & set/restore cursor as xterm */ if (!allowaltscreen) break; tcursor((set) ? CursorMovement.SAVE : CursorMovement.LOAD); goto case 47; case 47: /* swap screen */ case 1047: if (!allowaltscreen) break; alt = IS_SET(TermMode.ALTSCREEN) ? 1 : 0; if (alt) { tclearregion(0, 0, term.col-1, term.row-1); } if (set ^ alt) /* set is always 1 or 0 */ tswapscreen(); if (*args != 1049) break; goto case 1048; case 1048: tcursor((set) ? CursorMovement.SAVE : CursorMovement.LOAD); break; case 2004: /* 2004: bracketed paste mode */ xsetmode(set, WinMode.BRCKTPASTE); break; /* Not implemented mouse modes. See comments there. */ case 1001: /* mouse highlight mode; can hang the terminal by design when implemented. */ case 1005: /* UTF-8 mouse mode; will confuse applications not supporting UTF-8 and luit. */ case 1015: /* urxvt mangled mouse mode; incompatible and can be mistaken for other control codes. */ case 7727: /* Application escape key mode (rxvt-unicode specific) */ break; default: fprintf(stderr, "erresc: unknown private set/reset mode %d\n", *args); break; } } else { switch (*args) { case 0: /* Error (IGNORED) */ break; case 2: term.mode = set ? (term.mode | TermMode.INSERT) : (term.mode & ~TermMode.INSERT); break; case 4: /* IRM -- Insertion-replacement */ term.mode = set ? (term.mode | TermMode.INSERT) : (term.mode & ~TermMode.INSERT); break; case 12: /* SRM -- Send/Receive */ term.mode = set ? (term.mode | TermMode.ECHO) : (term.mode & ~TermMode.ECHO); break; case 20: /* LNM -- Linefeed/new line */ term.mode = set ? (term.mode | TermMode.CRLF) : (term.mode & ~TermMode.CRLF); break; default: fprintf(stderr, "erresc: unknown set/reset mode %d\n", *args); break; } } } } void csihandle() { char[40] buffer; int n = 0; // Debug log CSI sequence trace("csihandle: mode='", cast(char)csiescseq.mode[0], "' (0x", to!string(cast(uint)csiescseq.mode[0], 16), ") priv=", csiescseq.priv, " narg=", csiescseq.narg); for (int i = 0; i < csiescseq.narg; i++) { trace(" arg[", i, "]=", csiescseq.arg[i]); } switch (csiescseq.mode[0]) { default: unknown: fprintf(stderr, "erresc: unknown csi "); csidump(); break; case '@': /* ICH -- Insert blank char */ DEFAULT(csiescseq.arg[0], 1); tinsertblank(csiescseq.arg[0]); break; case 'A': /* CUU -- Cursor Up */ DEFAULT(csiescseq.arg[0], 1); tmoveto(term.c.x, term.c.y-csiescseq.arg[0]); break; case 'B': /* CUD -- Cursor Down */ case 'e': /* VPR --Cursor Down */ DEFAULT(csiescseq.arg[0], 1); tmoveto(term.c.x, term.c.y+csiescseq.arg[0]); break; case 'i': /* MC -- Media Copy */ switch (csiescseq.arg[0]) { case 0: tdump(); break; case 1: tdumpline(term.c.y); break; case 2: tdumpsel(); break; case 4: term.mode &= ~TermMode.PRINT; break; case 5: term.mode |= TermMode.PRINT; break; default: break; } break; case 'c': /* DA -- Device Attributes */ if (csiescseq.arg[0] == 0) { if (csiescseq.priv) { /* Secondary DA -- ignore for now */ break; } ttywrite(vtiden, strlen(vtiden), 0); } break; case 'q': /* DECSCUSR and other 'q' sequences */ if (csiescseq.priv) { /* Tertiary DA or other private 'q' sequences -- ignore */ break; } /* Other 'q' sequences */ goto unknown; case 'b': /* REP -- if last char is printable print it more times */ DEFAULT(csiescseq.arg[0], 1); if (csiescseq.arg[0] > 0) csiescseq.arg[0] = min(csiescseq.arg[0], 65535); if (term.lastc) while (csiescseq.arg[0]-- > 0) tputc(term.lastc); break; case 'C': /* CUF -- Cursor Forward */ case 'a': /* HPR -- Cursor Forward */ DEFAULT(csiescseq.arg[0], 1); tmoveto(term.c.x+csiescseq.arg[0], term.c.y); break; case 'D': /* CUB -- Cursor Backward */ DEFAULT(csiescseq.arg[0], 1); tmoveto(term.c.x-csiescseq.arg[0], term.c.y); break; case 'E': /* CNL -- Cursor Down and first col */ DEFAULT(csiescseq.arg[0], 1); tmoveto(0, term.c.y+csiescseq.arg[0]); break; case 'F': /* CPL -- Cursor Up and first col */ DEFAULT(csiescseq.arg[0], 1); tmoveto(0, term.c.y-csiescseq.arg[0]); break; case 'g': /* TBC -- Tabulation clear */ switch (csiescseq.arg[0]) { case 0: /* clear current tab stop */ term.tabs[term.c.x] = 0; break; case 3: /* clear all the tabs */ memset(term.tabs, 0, term.col * int.sizeof); break; default: goto unknown; } break; case 'G': /* CHA -- Move to */ case '`': /* HPA */ DEFAULT(csiescseq.arg[0], 1); tmoveto(csiescseq.arg[0]-1, term.c.y); break; case 'H': /* CUP -- Move to */ case 'f': /* HVP */ DEFAULT(csiescseq.arg[0], 1); DEFAULT(csiescseq.arg[1], 1); tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1); break; case 'I': /* CHT -- Cursor Forward Tabulation tab stops */ DEFAULT(csiescseq.arg[0], 1); tputtab(csiescseq.arg[0]); break; case 'J': /* ED -- Clear screen */ switch (csiescseq.arg[0]) { case 0: /* below */ tclearregion(term.c.x, term.c.y, term.col-1, term.c.y); if (term.c.y < term.row-1) tclearregion(0, term.c.y+1, term.col-1, term.row-1); break; case 1: /* above */ if (term.c.y > 0) tclearregion(0, 0, term.col-1, term.c.y-1); tclearregion(0, term.c.y, term.c.x, term.c.y); break; case 2: /* all */ tclearregion(0, 0, term.col-1, term.row-1); break; default: goto unknown; } break; case 'K': /* EL -- Clear line */ switch (csiescseq.arg[0]) { case 0: /* right */ tclearregion(term.c.x, term.c.y, term.col-1, term.c.y); break; case 1: /* left */ tclearregion(0, term.c.y, term.c.x, term.c.y); break; case 2: /* all */ tclearregion(0, term.c.y, term.col-1, term.c.y); break; default: break; } break; case 'S': /* SU -- Scroll line up */ DEFAULT(csiescseq.arg[0], 1); tscrollup(term.top, csiescseq.arg[0]); break; case 'T': /* SD -- Scroll line down */ DEFAULT(csiescseq.arg[0], 1); tscrolldown(term.top, csiescseq.arg[0]); break; case 'L': /* IL -- Insert blank lines */ DEFAULT(csiescseq.arg[0], 1); tinsertblankline(csiescseq.arg[0]); break; case 'l': /* RM -- Reset Mode */ tsetmode(csiescseq.priv, 0, csiescseq.arg.ptr, csiescseq.narg); break; case 'M': /* DL -- Delete lines */ DEFAULT(csiescseq.arg[0], 1); tdeleteline(csiescseq.arg[0]); break; case 'X': /* ECH -- Erase char */ DEFAULT(csiescseq.arg[0], 1); tclearregion(term.c.x, term.c.y, term.c.x + csiescseq.arg[0] - 1, term.c.y); break; case 'P': /* DCH -- Delete char */ DEFAULT(csiescseq.arg[0], 1); tdeletechar(csiescseq.arg[0]); break; case 'Z': /* CBT -- Cursor Backward Tabulation tab stops */ DEFAULT(csiescseq.arg[0], 1); tputtab(-csiescseq.arg[0]); break; case 'd': /* VPA -- Move to */ DEFAULT(csiescseq.arg[0], 1); tmoveato(term.c.x, csiescseq.arg[0]-1); break; case 'h': /* SM -- Set terminal mode */ tsetmode(csiescseq.priv, 1, csiescseq.arg.ptr, csiescseq.narg); break; case 'm': /* SGR -- Terminal attribute (color) */ tsetattr(csiescseq.arg.ptr, csiescseq.narg); break; case 'n': /* DSR – Device Status Report (cursor position) */ if (csiescseq.arg[0] == 6) { n = snprintf(buffer.ptr, buffer.sizeof, "\033[%d;%dR", term.c.y+1, term.c.x+1); ttywrite(buffer.ptr, n, 0); } break; case 'p': /* DECRQM -- Request Mode */ if (csiescseq.priv) { /* Mode status request - respond with mode not recognized */ /* Format: CSI ? mode ; value $ y where value is: 0 - not recognized 1 - set 2 - reset 3 - permanently set 4 - permanently reset */ DEFAULT(csiescseq.arg[0], 0); n = snprintf(buffer.ptr, buffer.sizeof, "\033[?%d;0$y", csiescseq.arg[0]); ttywrite(buffer.ptr, n, 0); } break; case 'r': /* DECSTBM -- Set Scrolling Region */ if (csiescseq.priv) { goto unknown; } else { DEFAULT(csiescseq.arg[0], 1); DEFAULT(csiescseq.arg[1], term.row); tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1); tmoveto(0, 0); } break; case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ tcursor(CursorMovement.SAVE); break; case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ tcursor(CursorMovement.LOAD); break; case ' ': switch (csiescseq.mode[1]) { case 'q': /* DECSCUSR -- Set Cursor Style */ if (xsetcursor(csiescseq.arg[0])) goto unknown; break; default: goto unknown; } break; } } void csiparse() { char* p = csiescseq.buf.ptr; char* np; long v; csiescseq.narg = 0; if (*p == '?') { csiescseq.priv = 1; p++; } else if (*p == '>') { csiescseq.priv = 1; p++; } csiescseq.buf[csiescseq.len] = '\0'; while (p < csiescseq.buf.ptr + csiescseq.len) { np = null; v = strtol(p, &np, 10); if (np == p) v = 0; if (v == long.max || v < 0) v = 0; csiescseq.arg[csiescseq.narg++] = cast(int)v; p = np; if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ) { // Check for '$' separator used in mode status requests if (*p == '$' && p + 1 < csiescseq.buf.ptr + csiescseq.len) { p++; // Skip the '$' } break; } p++; } csiescseq.mode[0] = *p++; csiescseq.mode[1] = (p < csiescseq.buf.ptr + csiescseq.len) ? *p : '\0'; // Removed verbose CSI parsed trace } void strparse() { int c; char* p = strescseq.buf; strescseq.narg = 0; strescseq.buf[strescseq.len] = '\0'; if (*p == '\0') return; while (strescseq.narg < STR_ARG_SIZ) { strescseq.args[strescseq.narg++] = p; while ((c = *p) != ';' && c != '\0') ++p; if (c == '\0') return; *p++ = '\0'; } } void strhandle() { char* p = null; int j, narg, par; term.esc &= ~(ESC_STR_END | ESC_STR); strparse(); narg = strescseq.narg; par = narg ? atoi(strescseq.args[0]) : 0; switch (strescseq.type) { case ']': /* OSC -- Operating System Command */ switch (par) { case 0: case 1: case 2: if (narg > 1) { import x : xsettitle; xsettitle(strescseq.args[1]); } return; case 4: /* color set */ if (narg < 3) break; p = strescseq.args[2]; /* FALLTHROUGH */ goto case; case 104: /* color reset */ j = (narg > 1) ? atoi(strescseq.args[1]) : -1; if (xsetcolorname(j, p)) { import x : xloadcols; xloadcols(); redraw(); } return; default: break; } break; case 'k': /* old title set compatibility */ import x : xsettitle; xsettitle(strescseq.args[0]); return; case 'P': /* DCS -- Device Control String */ case '_': /* APC -- Application Program Command */ case '^': /* PM -- Privacy Message */ return; default: break; } fprintf(stderr, "erresc: unknown str "); strdump(); } void strdump() { int i; uint c; fprintf(stderr, "ESC%c", strescseq.type); for (i = 0; i < strescseq.len; i++) { c = strescseq.buf[i] & 0xff; if (c == '\0') { putc('\n', stderr); return; } else if (isprint(c)) { putc(c, stderr); } else if (c == '\n') { fprintf(stderr, "(\\n)"); } else if (c == '\r') { fprintf(stderr, "(\\r)"); } else if (c == 0x1b) { fprintf(stderr, "(\\e)"); } else { fprintf(stderr, "(%02x)", c); } } fprintf(stderr, "ESC\\\n"); } int xsetcolorname(int x, char* name) { // Stub implementation - would set color palette return 0; } void tdeftran(char ascii) { static immutable char[] CS_GRAPHIC0 = "0"; static immutable char[] CS_UK = "A"; static immutable char[] CS_USA = "B"; static immutable char[] CS_MULTI = "5"; static immutable char[] CS_GER = "7"; static immutable char[] CS_FIN = "C"; const(char)* p = strchr(CS_GRAPHIC0.ptr, ascii); if (p) term.trantbl[term.icharset] = cast(char)(Charset.GRAPHIC0 + (p - CS_GRAPHIC0.ptr)); else if ((p = strchr(CS_UK.ptr, ascii)) != null) term.trantbl[term.icharset] = cast(char)Charset.UK; else if ((p = strchr(CS_USA.ptr, ascii)) != null) term.trantbl[term.icharset] = cast(char)Charset.USA; else if ((p = strchr(CS_MULTI.ptr, ascii)) != null) term.trantbl[term.icharset] = cast(char)Charset.MULTI; else if ((p = strchr(CS_GER.ptr, ascii)) != null) term.trantbl[term.icharset] = cast(char)Charset.GER; else if ((p = strchr(CS_FIN.ptr, ascii)) != null) term.trantbl[term.icharset] = cast(char)Charset.FIN; else fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); } void tdectest(char c) { int x, y; if (c == '8') { /* DEC screen alignment test. */ for (x = 0; x < term.col; ++x) { for (y = 0; y < term.row; ++y) tsetchar('E', &term.c.attr, x, y); } } } int eschandle(uint ascii) { switch (ascii) { case '[': // CSI -- Control Sequence Introducer term.esc |= ESC_CSI; return 0; case '#': // DEC test term.esc |= ESC_TEST; return 0; case '%': // charset term.esc |= ESC_UTF8; return 0; case 'P': // DCS -- Device Control String case '_': // APC -- Application Program Command case '^': // PM -- Privacy Message case ']': // OSC -- Operating System Command case 'k': // old title set compatibility tstrsequence(ascii); return 0; case 'n': // LS2 -- Locking shift 2 case 'o': // LS3 -- Locking shift 3 term.charset = 2 + (ascii - 'n'); break; case '(': // GZD4 -- set primary charset G0 case ')': // G1D4 -- set secondary charset G1 case '*': // G2D4 -- set tertiary charset G2 case '+': // G3D4 -- set quaternary charset G3 term.icharset = ascii - '('; term.esc |= ESC_ALTCHARSET; return 0; case 'D': // IND -- Linefeed if (term.c.y == term.bot) { static if (isPatchEnabled!"SCROLLBACK_PATCH") { tscrollup(term.top, 1, 1); // Save to history } else { tscrollup(term.top, 1); } } else { tmoveto(term.c.x, term.c.y+1); } break; case 'E': // NEL -- Next line tnewline(1); break; case 'H': // HTS -- Horizontal tab stop term.tabs[term.c.x] = 1; break; case 'M': // RI -- Reverse index if (term.c.y == term.top) { tscrolldown(term.top, 1); } else { tmoveto(term.c.x, term.c.y-1); } break; case 'Z': // DECID -- Identify Terminal ttywrite(vtiden, strlen(vtiden), 0); break; case 'c': // RIS -- Reset to initial state treset(); resettitle(); import x : xloadcols; xloadcols(); break; case '=': // DECPAM -- Application keypad xsetmode(1, WinMode.APPKEYPAD); break; case '>': // DECPNM -- Normal keypad xsetmode(0, WinMode.APPKEYPAD); break; case '7': // DECSC -- Save Cursor tcursor(CursorMovement.SAVE); break; case '8': // DECRC -- Restore Cursor tcursor(CursorMovement.LOAD); break; case '\\': // ST -- String Terminator if (term.esc & ESC_STR_END) { strescseq.term = cast(char*)(STR_TERM_ST ~ "\0").ptr; strhandle(); } break; case '1': // Possible application cursor keys or other mode // Some terminals use ESC 1 for various purposes // Just consume it silently to avoid display corruption break; default: fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", cast(ubyte)ascii, isprint(ascii) ? cast(char)ascii : '.'); break; } return 1; }