diff options
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | adapter/img/sixel.nim | 202 | ||||
-rw-r--r-- | res/urimethodmap | 1 | ||||
-rw-r--r-- | src/local/container.nim | 16 | ||||
-rw-r--r-- | src/local/pager.nim | 82 | ||||
-rw-r--r-- | src/local/term.nim | 184 |
6 files changed, 355 insertions, 134 deletions
diff --git a/Makefile b/Makefile index e5b94be4..48483754 100644 --- a/Makefile +++ b/Makefile @@ -51,7 +51,7 @@ all: $(OUTDIR_BIN)/cha $(OUTDIR_BIN)/mancha $(OUTDIR_CGI_BIN)/http \ $(OUTDIR_CGI_BIN)/cha-finger $(OUTDIR_CGI_BIN)/about \ $(OUTDIR_CGI_BIN)/file $(OUTDIR_CGI_BIN)/ftp \ $(OUTDIR_CGI_BIN)/man $(OUTDIR_CGI_BIN)/spartan \ - $(OUTDIR_CGI_BIN)/stbi $(OUTDIR_CGI_BIN)/jebp \ + $(OUTDIR_CGI_BIN)/stbi $(OUTDIR_CGI_BIN)/jebp $(OUTDIR_CGI_BIN)/sixel \ $(OUTDIR_LIBEXEC)/urldec $(OUTDIR_LIBEXEC)/urlenc \ $(OUTDIR_LIBEXEC)/md2html $(OUTDIR_LIBEXEC)/ansi2html ln -sf "$(OUTDIR)/$(TARGET)/bin/cha" cha @@ -164,7 +164,7 @@ manpages = $(manpages1) $(manpages5) .PHONY: manpage manpage: $(manpages:%=doc/%) -protocols = http about file ftp gopher gmifetch cha-finger man spartan stbi jebp +protocols = http about file ftp gopher gmifetch cha-finger man spartan stbi jebp sixel converters = gopher2html md2html ansi2html gmi2html tools = urlenc diff --git a/adapter/img/sixel.nim b/adapter/img/sixel.nim new file mode 100644 index 00000000..02bb356e --- /dev/null +++ b/adapter/img/sixel.nim @@ -0,0 +1,202 @@ +# Sixel codec. I'm lazy, so no decoder yet. +# +# "Regular" mode just encodes the image as a sixel image, with +# Cha-Image-Sixel-Palette colors. (TODO: maybe adjust this based on quality?) +# The encoder also has a "half-dump" mode, where the output is modified as +# follows: +# +# * DCS q set-raster-attributes is omitted. +# * 32-bit binary number in header indicates length of following palette. +# * A lookup table is appended to the file end, which includes (height + 5) / 6 +# 32-bit binary numbers indicating the start index of every 6th row. +# +# This way, the image can be vertically cropped in ~constant time. + +import std/options +import std/os +import std/strutils + +import types/color +import utils/sandbox +import utils/twtstr + +const DCSSTART = "\eP" +const ST = "\e\\" + +# data is binary 0..63; the output is the final ASCII form. +proc compressSixel(data: openArray[uint8]; c: uint8): string = + var outs = newStringOfCap(data.len div 4 + 3) + outs &= '#' + outs &= $c + var n = 0 + var c = char(0) + for u in data: + let cc = char(u + 0x3F) + if c != cc: + if n > 3: + outs &= '!' & $n & c + else: # for char(0) n is also 0, so it is ignored. + outs &= c.repeat(n) + c = cc + n = 0 + inc n + if n > 3: + outs &= '!' & $n & c + else: + outs &= c.repeat(n) + return outs + +type SixelBand = object + c: uint8 + data: seq[uint8] + +func find(bands: seq[SixelBand]; c: uint8): int = + for i, band in bands: + if band.c == c: + return i + -1 + +proc setU32BE(s: var string; n: uint32) = + s[0] = char(n and 0xFF) + s[1] = char((n shr 8) and 0xFF) + s[2] = char((n shr 16) and 0xFF) + s[3] = char((n shr 24) and 0xFF) + +proc putU32BE(s: var string; n: uint32) = + s &= char(n and 0xFF) + s &= char((n shr 8) and 0xFF) + s &= char((n shr 16) and 0xFF) + s &= char((n shr 24) and 0xFF) + +proc encode(s: string; width, height, offx, offy, cropw: int; halfdump: bool; + bgcolor: ARGBColor; palette: int) = + if width == 0 or height == 0: + return # done... + # prelude + var outs = "" + if halfdump: # reserve size for prelude + outs &= "\0\0\0\0" + else: + outs &= DCSSTART & 'q' + # set raster attributes + outs &= "\"1;1;" & $width & ';' & $height + for b in 16 ..< 256: + # laziest possible color register allocation scheme + #TODO obviously this produces sub-optimal results + let rgb = EightBitColor(b).toRGB() + let rgbq = RGBColor(uint32(rgb).fastmul(100)) + let n = b - 15 + # 2 is RGB + outs &= '#' & $n & ";2;" & $rgbq.r & ';' & $rgbq.g & ';' & $rgbq.b + if halfdump: + # prepend prelude size + let L = outs.len - 4 # subtract length field + outs.setU32BE(uint32(L)) + stdout.write(outs) + let W = width * 4 + let H = W * height + var n = offy * W + var ymap = "" + var totalLen = 0 + while n < H: + if halfdump: + ymap.putU32BE(uint32(totalLen)) + var bands = newSeq[SixelBand]() + for i in 0 ..< 6: + if n >= H: + break + let mask = 1u8 shl i + let realw = cropw - offx + for j in 0 ..< realw: + let m = n + (j + offx) * 4 + let r = uint8(s[m]) + let g = uint8(s[m + 1]) + let b = uint8(s[m + 2]) + let a = uint8(s[m + 3]) + var c0 = RGBAColorBE(r: r, g: g, b: b, a: a) + if c0.a != 255: + let c1 = bgcolor.blend(c0) + c0 = RGBAColorBE(r: c1.r, g: c1.g, b: c1.b, a: c1.a) + let c = uint8(c0.toEightBit()) + if (let k = bands.find(c); k != -1): + bands[k].data[j] = bands[k].data[j] or mask + else: + bands.add(SixelBand(c: c, data: newSeq[uint8](realw))) + bands[^1].data[^1] = mask + n += W + outs.setLen(0) + for i in 0 ..< bands.high: + outs &= bands[i].data.compressSixel(bands[i].c - 15) & '$' + outs &= bands[^1].data.compressSixel(bands[^1].c - 15) + if n >= H: + outs &= ST + else: + outs &= '-' + totalLen += outs.len + stdout.write(outs) + if halfdump: + ymap.putU32BE(uint32(totalLen)) + stdout.write(ymap) + +proc parseDimensions(s: string): (int, int) = + let s = s.split('x') + if s.len != 2: + stdout.writeLine("Cha-Control: ConnectionError 1 wrong dimensions") + return + let w = parseUInt32(s[0], allowSign = false) + let h = parseUInt32(s[1], allowSign = false) + if w.isNone or w.isNone: + stdout.writeLine("Cha-Control: ConnectionError 1 wrong dimensions") + return + return (int(w.get), int(h.get)) + +proc main() = + enterNetworkSandbox() + let scheme = getEnv("MAPPED_URI_SCHEME") + let f = scheme.after('+') + if f != "x-sixel": + stdout.writeLine("Cha-Control: ConnectionError 1 unknown format " & f) + return + case getEnv("MAPPED_URI_PATH") + of "decode": + stdout.writeLine("Cha-Control: ConnectionError 1 not implemented") + of "encode": + let headers = getEnv("REQUEST_HEADERS") + var width = 0 + var height = 0 + var offx = 0 + var offy = 0 + var halfdump = false + var palette = -1 + var bgcolor = rgb(0, 0, 0) + var cropw = -1 + for hdr in headers.split('\n'): + let s = hdr.after(':').strip() + case hdr.until(':') + of "Cha-Image-Dimensions": + (width, height) = parseDimensions(s) + of "Cha-Image-Offset": + (offx, offy) = parseDimensions(s) + of "Cha-Image-Crop-Width": + let q = parseUInt32(s, allowSign = false) + if q.isNone: + stdout.writeLine("Cha-Control: ConnectionError 1 wrong palette") + return + cropw = int(q.get) + of "Cha-Image-Sixel-Halfdump": + halfdump = true + of "Cha-Image-Sixel-Palette": + let q = parseUInt16(s, allowSign = false) + if q.isNone: + stdout.writeLine("Cha-Control: ConnectionError 1 wrong palette") + return + palette = int(q.get) + of "Cha-Image-Background-Color": + bgcolor = parseLegacyColor0(s) + if cropw == -1: + cropw = width + let s = stdin.readAll() + stdout.write("Cha-Image-Dimensions: " & $width & 'x' & $height & "\n\n") + s.encode(width, height, offx, offy, cropw, halfdump, bgcolor, palette) + +main() diff --git a/res/urimethodmap b/res/urimethodmap index 21e01546..b75dc2b0 100644 --- a/res/urimethodmap +++ b/res/urimethodmap @@ -21,3 +21,4 @@ img-codec+gif: cgi-bin:stbi img-codec+bmp: cgi-bin:stbi img-codec+x-unknown: cgi-bin:stbi img-codec+webp: cgi-bin:jebp +img-codec+x-sixel: cgi-bin:sixel diff --git a/src/local/container.nim b/src/local/container.nim index 046e2bc8..ebfc3940 100644 --- a/src/local/container.nim +++ b/src/local/container.nim @@ -106,6 +106,16 @@ type height*: int data*: Blob bmp*: NetworkBitmap + # Following variables are always 0 in kitty mode; they exist to support + # sixel cropping. + # We can easily crop images where we just have to exclude some lines prior + # to/after the image, but we must re-encode if + # * offx > 0, dispw < width or + # * offy % 6 != previous offy % 6 (currently only happens when cell height + # is not a multiple of 6). + offx*: int # same as CanvasImage.offx + dispw*: int # same as CanvasImage.dispw + erry*: int # same as CanvasImage.offy % 6 Container* = ref object # note: this is not the same as source.request.url (but should be synced @@ -2058,11 +2068,13 @@ proc highlightMarks*(container: Container; display: var FixedGrid; hlformat.bgcolor = hlcolor display[y * display.width + x].format = hlformat -func findCachedImage*(container: Container; image: PosBitmap): CachedImage = +func findCachedImage*(container: Container; image: PosBitmap; + offx, erry, dispw: int): CachedImage = let imageId = NetworkBitmap(image.bmp).imageId for it in container.cachedImages: if it.bmp.imageId == imageId and it.width == image.width and - it.height == image.height: + it.height == image.height and it.offx == offx and it.erry == erry and + it.dispw == dispw: return it return nil diff --git a/src/local/pager.nim b/src/local/pager.nim index 7c2dde27..d81d4986 100644 --- a/src/local/pager.nim +++ b/src/local/pager.nim @@ -482,14 +482,18 @@ proc redraw(pager: Pager) {.jsfunc.} = if pager.container.select != nil: pager.container.select.redraw = true -proc loadCachedImage(pager: Pager; container: Container; image: PosBitmap) = +proc loadCachedImage(pager: Pager; container: Container; image: PosBitmap; + offx, erry, dispw: int) = let bmp = NetworkBitmap() bmp[] = NetworkBitmap(image.bmp)[] let request = newRequest(newURL("cache:" & $bmp.cacheId).get) let cachedImage = CachedImage( bmp: bmp, width: image.width, - height: image.height + height: image.height, + offx: offx, + erry: erry, + dispw: dispw ) pager.loader.shareCachedItem(bmp.cacheId, pager.loader.clientPid, container.process) @@ -523,38 +527,44 @@ proc loadCachedImage(pager: Pager; container: Container; image: PosBitmap) = # take target sizes bmp.width = uint64(image.width) bmp.height = uint64(image.height) + let headers = newHeaders({ + "Cha-Image-Dimensions": $image.width & 'x' & $image.height + }) + var url: URL = nil case imageMode of imSixel: - #TODO we should only cache the final output in memory, not the full - # bitmap. - response.saveToBitmap(bmp).then(proc() = - container.redraw = true - cachedImage.loaded = true - pager.loader.removeCachedItem(bmp.cacheId) - ) + url = newURL("img-codec+x-sixel:encode").get + headers.add("Cha-Image-Sixel-Halfdump", "1") + headers.add("Cha-Image-Sixel-Palette", $pager.term.sixelRegisterNum) + headers.add("Cha-Image-Background-Color", $pager.term.defaultBackground) + headers.add("Cha-Image-Offset", $offx & 'x' & $erry) + headers.add("Cha-Image-Crop-Width", $dispw) of imKitty: - let headers = newHeaders({ - "Cha-Image-Dimensions": $image.width & 'x' & $image.height - }) - let request = newRequest( - newURL("img-codec+png:encode").get, - httpMethod = hmPost, - headers = headers, - body = RequestBody(t: rbtOutput, outputId: response.outputId), - ) - let r = pager.loader.fetch(request) - response.resume() - response.unregisterFun() - response.body.sclose() - r.then(proc(res: JSResult[Response]): Promise[JSResult[Blob]] = - return res.get.blob() - ).then(proc(res: JSResult[Blob]) = + url = newURL("img-codec+png:encode").get + of imNone: assert false + let request = newRequest( + url, + httpMethod = hmPost, + headers = headers, + body = RequestBody(t: rbtOutput, outputId: response.outputId), + ) + let r = pager.loader.fetch(request) + response.resume() + response.unregisterFun() + response.body.sclose() + r.then(proc(res: JSResult[Response]): Promise[JSResult[Blob]] = + if res.isNone: + let p = newPromise[JSResult[Blob]]() + p.resolve(JSResult[Blob].err(res.error)) + return p + return res.get.blob() + ).then(proc(res: JSResult[Blob]) = + if res.isSome: container.redraw = true cachedImage.data = res.get cachedImage.loaded = true - pager.loader.removeCachedItem(bmp.cacheId) - ) - of imNone: assert false + pager.loader.removeCachedItem(bmp.cacheId) + ) ) container.cachedImages.add(cachedImage) @@ -564,12 +574,22 @@ proc initImages(pager: Pager; container: Container) = var imageId = -1 var data: Blob = nil var bmp0 = image.bmp + var erry = 0 + var offx = 0 + var dispw = 0 if image.bmp of NetworkBitmap: let bmp = NetworkBitmap(image.bmp) - let cached = container.findCachedImage(image) + if pager.term.imageMode == imSixel: + let xpx = (image.x - container.fromx) * pager.attrs.ppc + offx = -min(xpx, 0) + let maxwpx = pager.bufWidth * pager.attrs.ppc + dispw = min(int(image.width) + xpx, maxwpx) - xpx + let ypx = (image.y - container.fromy) * pager.attrs.ppl + erry = -min(ypx, 0) mod 6 + let cached = container.findCachedImage(image, offx, erry, dispw) imageId = bmp.imageId if cached == nil: - pager.loadCachedImage(container, image) + pager.loadCachedImage(container, image, offx, erry, dispw) continue bmp0 = cached.bmp data = cached.data @@ -580,7 +600,7 @@ proc initImages(pager: Pager; container: Container) = inc pager.imageId let canvasImage = pager.term.loadImage(bmp0, data, container.process, imageId, image.x - container.fromx, image.y - container.fromy, - image.x, image.y, pager.bufWidth, pager.bufHeight) + image.x, image.y, pager.bufWidth, pager.bufHeight, erry, offx, dispw) if canvasImage != nil: newImages.add(canvasImage) pager.term.clearImages(pager.bufHeight) diff --git a/src/local/term.nim b/src/local/term.nim index 71d32343..3b2b8050 100644 --- a/src/local/term.nim +++ b/src/local/term.nim @@ -67,6 +67,8 @@ type marked*: bool kittyId: int bmp: Bitmap + # 0 if kitty + erry: int # absolute x, y in container rx: int ry: int @@ -92,10 +94,10 @@ type stdinUnblocked: bool stdinWasUnblocked: bool origTermios: Termios - defaultBackground: RGBColor + defaultBackground*: RGBColor defaultForeground: RGBColor ibuf*: string # buffer for chars when we can't process them - sixelRegisterNum: int + sixelRegisterNum*: int sixelMaxWidth: int sixelMaxHeight: int kittyId: int # counter for kitty image (*not* placement) ids. @@ -212,21 +214,16 @@ when TermcapFound: func cap(term: Terminal; c: TermcapCap): string = $term.tc.caps[c] func ccap(term: Terminal; c: TermcapCap): cstring = term.tc.caps[c] - var goutfile: File - proc putc(c: char): cint {.cdecl.} = - goutfile.write(c) +proc write(term: Terminal; s: openArray[char]) = + # write() calls $ on s, so we must writeBuffer + if s.len > 0: + discard term.outfile.writeBuffer(unsafeAddr s[0], s.len) - proc write(term: Terminal; s: cstring) = - discard tputs(s, 1, putc) +proc write(term: Terminal; s: string) = + term.outfile.write(s) - proc write(term: Terminal; s: string) = - if term.tc != nil: - term.write(cstring(s)) - else: - term.outfile.write(s) -else: - proc write(term: Terminal; s: string) = - term.outfile.write(s) +proc write(term: Terminal; s: cstring) = + term.outfile.write(s) proc readChar*(term: Terminal): char = if term.ibuf.len == 0: @@ -645,12 +642,14 @@ proc outputGrid*(term: Terminal) = term.cursorx = -1 term.cursory = -1 -func findImage(term: Terminal; pid, imageId: int; bmp: Bitmap; rx, ry: int): - CanvasImage = +func findImage(term: Terminal; pid, imageId: int; bmp: Bitmap; + rx, ry, erry, offx, dispw: int): CanvasImage = for it in term.canvasImages: if it.pid == pid and it.imageId == imageId and it.bmp.width == bmp.width and it.bmp.height == bmp.height and - it.rx == rx and it.ry == ry: + it.rx == rx and it.ry == ry and + (term.imageMode != imSixel or it.erry == erry and it.dispw == dispw and + it.offx == offx): return it return nil @@ -700,8 +699,9 @@ proc clearImages*(term: Terminal; maxh: int) = image.marked = false proc loadImage*(term: Terminal; bmp: Bitmap; data: Blob; pid, imageId, - x, y, rx, ry, maxw, maxh: int): CanvasImage = - if (let image = term.findImage(pid, imageId, bmp, rx, ry); image != nil): + x, y, rx, ry, maxw, maxh, erry, offx, dispw: int): CanvasImage = + if (let image = term.findImage(pid, imageId, bmp, rx, ry, erry, offx, dispw); + image != nil): # reuse image on screen if image.x != x or image.y != y: # only clear sixels; with kitty we just move the existing image @@ -729,45 +729,23 @@ proc loadImage*(term: Terminal; bmp: Bitmap; data: Blob; pid, imageId, imageId: imageId, data: data, rx: rx, - ry: ry + ry: ry, + erry: erry ) if term.positionImage(image, x, y, maxw, maxh): return image # no longer on screen return nil -# data is binary 0..63; the output is the final ASCII form. -proc compressSixel(data: openArray[uint8]): string = - var outs = newStringOfCap(data.len div 4) - var n = 0 - var c = char(0) - for u in data: - let cc = char(u + 0x3F) - if c != cc: - if n > 3: - outs &= '!' & $n & c - else: # for char(0) n is also 0, so it is ignored. - outs &= c.repeat(n) - c = cc - n = 0 - inc n - if n > 3: - outs &= '!' & $n & c - else: - outs &= c.repeat(n) - return outs - -type SixelBand = object - c: uint8 - data: seq[uint8] - -func find(bands: seq[SixelBand]; c: uint8): int = - for i, band in bands: - if band.c == c: - return i - -1 +func getOffYIdx(data: openArray[char]; y, starti: int): int32 = + let i = starti + (y div 6) * 4 + return int32(data[i]) or + (int32(data[i + 1]) shl 8) or + (int32(data[i + 2]) shl 16) or + (int32(data[i + 3]) shl 24) -proc outputSixelImage(term: Terminal; x, y: int; image: CanvasImage) = +proc outputSixelImage(term: Terminal; x, y: int; image: CanvasImage; + data: openArray[char]) = let offx = image.offx let offy = image.offy let dispw = image.dispw @@ -776,53 +754,62 @@ proc outputSixelImage(term: Terminal; x, y: int; image: CanvasImage) = var outs = term.cursorGoto(x, y) outs &= DCSSTART & 'q' # set raster attributes - outs &= "\"1;1;" & $(dispw - offx) & ';' & $(disph - offy) - for b in 16 ..< 256: - # laziest possible register allocation scheme - #TODO obviously this produces sub-optimal results - let rgb = EightBitColor(b).toRGB() - let rgbq = RGBColor(uint32(rgb).fastmul(100)) - let n = b - 15 - # 2 is RGB - outs &= '#' & $n & ";2;" & $rgbq.r & ';' & $rgbq.g & ';' & $rgbq.b - var n = offy * int(bmp.width) # start at offy - let H = disph * int(bmp.width) # end at disph - var m = y * term.attrs.width * term.attrs.ppl # track absolute y let realw = dispw - offx - let cx0 = x * term.attrs.ppc - while n < H: - var bands = newSeq[SixelBand]() - for i in 0 ..< 6: - if n >= H: - break - let mask = 1u8 shl i - let my = m div term.attrs.ppl - for bx in 0 ..< realw: - let cx = (cx0 + bx) div term.attrs.ppc - var c0 = bmp.px[n + bx + offx] - if c0.a != 255: - let bgcolor0 = term.canvas[my + cx].format.bgcolor - let bgcolor = term.getRGB(bgcolor0, term.defaultBackground) - let c1 = bgcolor.blend(c0) - c0 = RGBAColorBE(r: c1.r, g: c1.g, b: c1.b, a: c1.a) - let c = uint8(c0.toEightBit()) - if (let j = bands.find(c); j != -1): - bands[j].data[bx] = bands[j].data[bx] or mask - else: - bands.add(SixelBand(c: c, data: newSeq[uint8](realw))) - bands[^1].data[^1] = mask - n += int(bmp.width) - m += term.attrs.width - term.write(outs) - outs = "" - for i, band in bands: - let t = if i != bands.high: '$' else: '-' - let n = band.c - 15 - outs &= '#' & $n & band.data.compressSixel() & t - if outs.len > 0 and outs[^1] == '-': - outs.setLen(outs.len - 1) - outs &= ST + var realh = disph - offy + #if disph < int(bmp.height): + # realh -= image.erry + outs &= "\"1;1;" & $realw & ';' & $realh term.write(outs) + let sraLen = uint32(data[0]) or + (uint32(data[1]) shl 8) or + (uint32(data[2]) shl 16) or + (uint32(data[3]) shl 24) + let preludeLen = int(sraLen + 4) + term.write(data.toOpenArray(4, 4 + int(sraLen) - 1)) + let lookupTableLen = ((int(bmp.height) + 5) div 6 + 1) * 4 + let L = data.len - lookupTableLen + # Note: we only crop images when it is possible to do so in near constant + # time. Otherwise, the image is re-coded in a cropped form. + if realh == int(bmp.height): + term.write(data.toOpenArray(preludeLen, L - 1)) + else: + let offyi = data.getOffYIdx(offy, L) + var e = disph + if disph < int(bmp.height): + e -= image.erry + let endyi = data.getOffYIdx(e, L) + if endyi <= offyi: + return + let si = preludeLen + int(offyi) + let ei = preludeLen + int(endyi) - 1 + assert offyi < endyi + assert ei <= data.len - lookupTableLen + term.write(data.toOpenArray(si, ei - 1)) + var ndash = 0 + for c in data.toOpenArray(si, ei - 1): + if c == '-': + inc ndash + let herry = realh - (realh div 6) * 6 + if herry > 0 and disph < int(bmp.height): + # can't write out the last row completely; mask off the bottom part. + let mask = (1u8 shl herry) - 1 + var s = "-" + var i = ei + 1 + inc ndash + while i < L and (let c = data[i]; c notin {'-', '\e'}): # newline or ST + let u = uint8(c) - 0x3F # may underflow, but that's no problem + if u < 0x40: + s &= char((u and mask) + 0x3F) + else: + s &= c + inc i + term.write(s) + term.write(ST) + +proc outputSixelImage(term: Terminal; x, y: int; image: CanvasImage) = + var p = cast[ptr UncheckedArray[char]](image.data.buffer) + let H = int(image.data.size - 1) + term.outputSixelImage(x, y, image, p.toOpenArray(0, H)) proc outputKittyImage(term: Terminal; x, y: int; image: CanvasImage) = var outs = term.cursorGoto(x, y) & @@ -944,8 +931,6 @@ when TermcapFound: if res == 0: # retry as dosansi res = tgetent(cast[cstring](addr tc.bp), "dosansi") if res > 0: # success - assert goutfile == nil - goutfile = term.outfile term.tc = tc for id in TermcapCap: tc.caps[id] = tgetstr(cstring($id), cast[ptr cstring](addr tc.funcstr)) @@ -1213,6 +1198,7 @@ proc detectTermAttributes(term: Terminal; windowOnly: bool): TermStartResult = for (n, rgb) in r.colorMap: term.colorMap[n] = rgb else: + term.sixelRegisterNum = 256 # something went horribly wrong. set result to DA1 fail, pager will # alert the user res = tsrDA1Fail |