diff options
author | bptato <nincsnevem662@gmail.com> | 2024-02-25 20:25:04 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2024-02-25 20:25:04 +0100 |
commit | 92fbf479f8bf6db6ad0655e5b71a0b58a45a2213 (patch) | |
tree | 47b7f2c23ebabbf94e48476615014b52762542f3 /src/display | |
parent | 4df71520f39ed7992f78484701467e513a34c5dc (diff) | |
download | chawan-92fbf479f8bf6db6ad0655e5b71a0b58a45a2213.tar.gz |
term: improve pixels-per-column/line detection
Some terminal emulators (AKA vte) refuse to set ws_xpixel and ws_ypixel in the TIOCGWINSZ ioctl, so we now query for CSI 14 t as well. (Also CSI 18 t for good measure, just in case we can't ioctl for some reason.) Also added some fallback (optionally forced) config values for width, height, ppc, and ppl. (This is especially useful in dump mode.)
Diffstat (limited to 'src/display')
-rw-r--r-- | src/display/lineedit.nim | 2 | ||||
-rw-r--r-- | src/display/term.nim | 240 | ||||
-rw-r--r-- | src/display/winattrs.nim | 58 |
3 files changed, 165 insertions, 135 deletions
diff --git a/src/display/lineedit.nim b/src/display/lineedit.nim index 6969171e..a69465a6 100644 --- a/src/display/lineedit.nim +++ b/src/display/lineedit.nim @@ -2,7 +2,7 @@ import std/strutils import std/unicode import bindings/quickjs -import display/winattrs +import display/term import js/javascript import types/cell import types/opt diff --git a/src/display/term.nim b/src/display/term.nim index 588951cd..a782fe99 100644 --- a/src/display/term.nim +++ b/src/display/term.nim @@ -9,7 +9,6 @@ import std/unicode import bindings/termcap import config/config -import display/winattrs import types/cell import types/color import types/opt @@ -20,8 +19,6 @@ import chagashi/charset import chagashi/encoder import chagashi/validator -export isatty - #TODO switch from termcap... type @@ -49,6 +46,14 @@ type funcstr: array[256, uint8] caps: array[TermcapCap, cstring] + WindowAttributes* = object + width*: int + height*: int + ppc*: int # cell width + ppl*: int # cell height + width_px*: int + height_px*: int + Terminal* = ref TerminalObj TerminalObj = object cs*: Charset @@ -79,6 +84,12 @@ template CSI(s: varargs[string, `$`]): string = # primary device attributes const DA1 = CSI("c") +# report xterm text area size in pixels +const GEOMPIXEL = CSI(14, "t") + +# report window size in chars +const GEOMCELL = CSI(18, "t") + # device control string template DCS(a, b: char, s: varargs[string]): string = "\eP" & a & b & s.join(';') & "\e\\" @@ -175,6 +186,10 @@ proc clearDisplay(term: Terminal): string = else: return ED() +proc isatty(fd: FileHandle): cint {.importc: "isatty", header: "<unistd.h>".} +proc isatty*(f: File): bool = + return isatty(f.getFileHandle()) != 0 + proc isatty(term: Terminal): bool = term.infile != nil and term.infile.isatty() and term.outfile.isatty() @@ -381,11 +396,6 @@ proc processFormat*(term: Terminal, format: var Format, cellf: Format): string = discard # nothing to do format = cellf -proc windowChange*(term: Terminal, attrs: WindowAttributes) = - term.attrs = attrs - term.canvas = newFixedGrid(attrs.width, attrs.height) - term.cleared = false - proc setTitle*(term: Terminal, title: string) = if term.set_title: let title = if Controls in title: @@ -504,7 +514,21 @@ proc writeGrid*(term: Terminal, grid: FixedGrid, x = 0, y = 0) = cell.format.fgcolor = grid[i].format.fgcolor j += cell[].width() +proc applyConfigDimensions(term: Terminal) = + # screen dimensions + if term.attrs.width == 0 or term.config.display.force_columns: + term.attrs.width = int(term.config.display.columns) + if term.attrs.height == 0 or term.config.display.force_lines: + term.attrs.height = int(term.config.display.lines) + if term.attrs.ppc == 0 or term.config.display.force_pixels_per_column: + term.attrs.ppc = int(term.config.display.pixels_per_column) + if term.attrs.ppl == 0 or term.config.display.force_pixels_per_line: + term.attrs.ppl = int(term.config.display.pixels_per_line) + term.attrs.width_px = term.attrs.ppc * term.attrs.width + term.attrs.height_px = term.attrs.ppl * term.attrs.height + proc applyConfig(term: Terminal) = + # colors, formatting if term.config.display.color_mode.isSome: term.colormode = term.config.display.color_mode.get elif term.isatty(): @@ -524,6 +548,7 @@ proc applyConfig(term: Terminal) = term.defaultBackground = term.config.display.default_background_color.get if term.config.display.default_foreground_color.isSome: term.defaultForeground = term.config.display.default_foreground_color.get + # charsets if term.config.encoding.display_charset.isSome: term.cs = term.config.encoding.display_charset.get else: @@ -536,10 +561,9 @@ proc applyConfig(term: Terminal) = if cs != CHARSET_UNKNOWN: term.cs = cs break + term.applyConfigDimensions() proc outputGrid*(term: Terminal) = - if term.config.display.force_clear: - term.applyConfig() term.outfile.write(term.resetFormat()) let samesize = term.canvas.width == term.pcanvas.width and term.canvas.height == term.pcanvas.height @@ -626,19 +650,32 @@ type attrs: set[QueryAttrs] fgcolor: Option[RGBColor] bgcolor: Option[RGBColor] + widthPx: int + heightPx: int + width: int + height: int -proc queryAttrs(term: Terminal): QueryResult = +proc queryAttrs(term: Terminal, windowOnly: bool): QueryResult = const tcapRGB = 0x524742 # RGB supported? - const outs = - XTGETFG & - XTGETBG & - XTGETTCAP("524742") & - DA1 - term.outfile.write(outs) + if not windowOnly: + const outs = + XTGETFG & + XTGETBG & + GEOMPIXEL & + GEOMCELL & + XTGETTCAP("524742") & + DA1 + term.outfile.write(outs) + else: + const outs = + GEOMPIXEL & + GEOMCELL & + DA1 + term.outfile.write(outs) result = QueryResult(success: false, attrs: {}) while true: template consume(term: Terminal): char = term.infile.readChar() - template fail = break + template fail = return template expect(term: Terminal, c: char) = if term.consume != c: fail @@ -652,20 +689,44 @@ proc queryAttrs(term: Terminal): QueryResult = case term.consume of '[': # CSI - term.expect '?' - var n = 0 - while (let c = term.consume; c != 'c'): - if c == ';': - case n - of 4: result.attrs.incl(qaSixel) - of 22: result.attrs.incl(qaAnsiColor) - else: discard - n = 0 - else: - n *= 10 - n += decValue(c) - result.success = true - break # DA1 returned; done + case (let c = term.consume; c) + of '?': # DA1 + var n = 0 + while (let c = term.consume; c != 'c'): + if c == ';': + case n + of 4: result.attrs.incl(qaSixel) + of 22: result.attrs.incl(qaAnsiColor) + else: discard + n = 0 + else: + n *= 10 + n += decValue(c) + result.success = true + break # DA1 returned; done + of '4', '8': # GEOMPIXEL, GEOMCELL + term.expect ';' + var height = 0 + var width = 0 + while (let c = term.consume; c != ';'): + if (let x = decValue(c); x != -1): + height *= 10 + height += x + else: + fail + while (let c = term.consume; c != 't'): + if (let x = decValue(c); x != -1): + width *= 10 + width += x + else: + fail + if c == '4': # GEOMSIZE + result.widthPx = width + result.heightPx = height + if c == '8': # GEOMCELL + result.width = width + result.height = height + else: fail of ']': # OSC term.expect '1' @@ -723,57 +784,87 @@ proc queryAttrs(term: Terminal): QueryResult = type TermStartResult* = enum tsrSuccess, tsrDA1Fail -proc detectTermAttributes(term: Terminal): TermStartResult = +# when windowOnly, only refresh window size. +proc detectTermAttributes(term: Terminal, windowOnly: bool): TermStartResult = result = tsrSuccess term.tname = getEnv("TERM") if term.tname == "": term.tname = "dosansi" - if term.isatty(): - if term.config.display.query_da1: - let r = term.queryAttrs() - if r.success: # DA1 success - if qaAnsiColor in r.attrs: - term.colormode = ANSI - if qaRGB in r.attrs: - term.colormode = TRUE_COLOR - # just assume the terminal doesn't choke on these. - term.formatmode = {FLAG_STRIKE, FLAG_OVERLINE} - if r.bgcolor.isSome: - term.defaultBackground = r.bgcolor.get - if r.fgcolor.isSome: - term.defaultForeground = r.fgcolor.get - else: - # something went horribly wrong. set result to DA1 fail, pager will - # alert the user - result = tsrDA1Fail - if term.colormode != TRUE_COLOR: - let colorterm = getEnv("COLORTERM") - if colorterm in ["24bit", "truecolor"]: + if not term.isatty(): + return + let fd = term.infile.getFileHandle() + var win: IOctl_WinSize + if ioctl(cint(fd), TIOCGWINSZ, addr win) != -1: + term.attrs.width = int(win.ws_col) + term.attrs.height = int(win.ws_row) + term.attrs.ppc = int(win.ws_xpixel) div term.attrs.width + term.attrs.ppl = int(win.ws_ypixel) div term.attrs.height + if term.config.display.query_da1: + let r = term.queryAttrs(windowOnly) + if r.success: # DA1 success + if r.width != 0: + term.attrs.width = r.width + if r.widthPx != 0: + term.attrs.ppc = r.widthPx div r.width + if r.height != 0: + term.attrs.height = r.height + if r.heightPx != 0: + term.attrs.ppl = r.heightPx div r.height + if windowOnly: + return + if qaAnsiColor in r.attrs: + term.colormode = ANSI + if qaRGB in r.attrs: term.colormode = TRUE_COLOR - when termcap_found: - term.loadTermcap() - if term.tc != nil: - term.smcup = term.hascap(ti) - if term.hascap(ZH): - term.formatmode.incl(FLAG_ITALIC) - if term.hascap(us): - term.formatmode.incl(FLAG_UNDERLINE) - if term.hascap(md): - term.formatmode.incl(FLAG_BOLD) - if term.hascap(mr): - term.formatmode.incl(FLAG_REVERSE) - if term.hascap(mb): - term.formatmode.incl(FLAG_BLINK) + # just assume the terminal doesn't choke on these. + term.formatmode = {FLAG_STRIKE, FLAG_OVERLINE} + if r.bgcolor.isSome: + term.defaultBackground = r.bgcolor.get + if r.fgcolor.isSome: + term.defaultForeground = r.fgcolor.get else: - term.smcup = true - term.formatmode = {low(FormatFlags)..high(FormatFlags)} + # something went horribly wrong. set result to DA1 fail, pager will + # alert the user + result = tsrDA1Fail + if windowOnly: + return + if term.colormode != TRUE_COLOR: + let colorterm = getEnv("COLORTERM") + if colorterm in ["24bit", "truecolor"]: + term.colormode = TRUE_COLOR + when termcap_found: + term.loadTermcap() + if term.tc != nil: + term.smcup = term.hascap(ti) + if term.hascap(ZH): + term.formatmode.incl(FLAG_ITALIC) + if term.hascap(us): + term.formatmode.incl(FLAG_UNDERLINE) + if term.hascap(md): + term.formatmode.incl(FLAG_BOLD) + if term.hascap(mr): + term.formatmode.incl(FLAG_REVERSE) + if term.hascap(mb): + term.formatmode.incl(FLAG_BLINK) + else: + term.smcup = true + term.formatmode = {low(FormatFlags)..high(FormatFlags)} + +proc windowChange*(term: Terminal) = + discard term.detectTermAttributes(windowOnly = true) + term.applyConfigDimensions() + term.canvas = newFixedGrid(term.attrs.width, term.attrs.height) + term.cleared = false proc start*(term: Terminal, infile: File): TermStartResult = term.infile = infile if term.isatty(): term.enableRawMode() - result = term.detectTermAttributes() + result = term.detectTermAttributes(windowOnly = false) + if result == tsrDA1Fail: + term.config.display.query_da1 = false term.applyConfig() + term.canvas = newFixedGrid(term.attrs.width, term.attrs.height) if term.smcup: term.write(term.enableAltScreen()) @@ -788,13 +879,10 @@ proc restart*(term: Terminal) = if term.smcup: term.write(term.enableAltScreen()) -proc newTerminal*(outfile: File, config: Config, attrs: WindowAttributes): - Terminal = - let term = Terminal( +proc newTerminal*(outfile: File, config: Config): Terminal = + return Terminal( outfile: outfile, config: config, defaultBackground: ColorsRGB["black"], defaultForeground: ColorsRGB["white"] ) - term.windowChange(attrs) - return term diff --git a/src/display/winattrs.nim b/src/display/winattrs.nim deleted file mode 100644 index 1fe07080..00000000 --- a/src/display/winattrs.nim +++ /dev/null @@ -1,58 +0,0 @@ -when defined(posix): - import std/termios - -type - WindowAttributes* = object - width*: int - height*: int - ppc*: int # cell width - ppl*: int # cell height - width_px*: int - height_px*: int - -proc isatty(fd: FileHandle): cint {.importc: "isatty", header: "<unistd.h>".} -proc isatty*(f: File): bool = - return isatty(f.getFileHandle()) != 0 - -proc getWindowAttributes*(tty: File): WindowAttributes = - if tty.isatty(): - var win: IOctl_WinSize - if ioctl(cint(getOsFileHandle(tty)), TIOCGWINSZ, addr win) != -1: - var cols = int(win.ws_col) - var rows = int(win.ws_row) - if cols == 0: - cols = 80 - if rows == 0: - rows = 24 - var ppc = int(win.ws_xpixel) div cols - var ppl = int(win.ws_ypixel) div rows - # some terminal emulators (aka vte) don't set ws_xpixel or ws_ypixel. - # solution: use xterm. - if ppc == 0: - ppc = 9 - if ppl == 0: - ppl = 18 - # Filling the last row without raw mode breaks things. However, - # not supporting Windows means we can always have raw mode, so we can - # use all available columns. - return WindowAttributes( - width: cols, - height: rows, - ppc: ppc, - ppl: ppl, - width_px: cols * ppc, - height_px: rows * ppl - ) - # Fallback for ioctl failure - let height = 24 - let width = 80 - let ppc = 9 - let ppl = 18 - return WindowAttributes( - width: width, - height: height, - ppc: ppc, - ppl: ppl, - width_px: ppc * width, - height_px: ppl * height - ) |