about summary refs log tree commit diff stats
path: root/src/local/term.nim
diff options
context:
space:
mode:
Diffstat (limited to 'src/local/term.nim')
-rw-r--r--src/local/term.nim97
1 files changed, 85 insertions, 12 deletions
diff --git a/src/local/term.nim b/src/local/term.nim
index eee53039..0da700f5 100644
--- a/src/local/term.nim
+++ b/src/local/term.nim
@@ -8,7 +8,9 @@ import std/unicode
 
 import bindings/termcap
 import config/config
+import img/bitmap
 import io/posixstream
+import js/base64
 import types/cell
 import types/color
 import types/opt
@@ -64,6 +66,7 @@ type
     attrs*: WindowAttributes
     colormode: ColorMode
     formatmode: FormatMode
+    imagemode: ImageMode
     smcup: bool
     tc: Termcap
     tname: string
@@ -128,6 +131,15 @@ const RMCUP = DECRST(1049)
 const SGRMOUSEBTNON = DECSET(1002, 1006)
 const SGRMOUSEBTNOFF = DECRST(1002, 1006)
 
+# application program command
+
+# This is only used in kitty images, and join()'ing kilobytes of base64
+# is rather inefficient so we don't use a template.
+const APC = "\e_"
+const ST = "\e\\"
+
+const KITTYQUERY = APC & "Gi=1,a=q;" & ST
+
 when not termcap_found:
   const CNORM = DECSET(25)
   const CIVIS = DECRST(25)
@@ -555,15 +567,13 @@ proc applyConfig(term: Terminal) =
   # colors, formatting
   if term.config.display.color_mode.isSome:
     term.colormode = term.config.display.color_mode.get
-  elif term.isatty():
-    let colorterm = getEnv("COLORTERM")
-    if colorterm in ["24bit", "truecolor"]:
-      term.colormode = cmTrueColor
   if term.config.display.format_mode.isSome:
     term.formatmode = term.config.display.format_mode.get
   for fm in FormatFlags:
     if fm in term.config.display.no_format_mode:
       term.formatmode.excl(fm)
+  if term.config.display.image_mode.isSome:
+    term.imagemode = term.config.display.image_mode.get
   if term.isatty():
     if term.config.display.alt_screen.isSome:
       term.smcup = term.config.display.alt_screen.get
@@ -603,6 +613,50 @@ proc outputGrid*(term: Terminal) =
   for i in 0 ..< term.canvas.cells.len:
     term.pcanvas[i] = term.canvas[i]
 
+proc clearImages*(term: Terminal) =
+  if term.imagemode == imKitty:
+    term.write(APC & "Ga=d" & ST)
+
+proc outputImage*(term: Terminal; bmp: Bitmap; x, y, maxw, maxh: int) =
+  case term.imagemode
+  of imNone: discard
+  of imSixel:
+    discard #TODO
+  of imKitty:
+    # max 4096 bytes, base encoded
+    const MaxPixels = ((4096 div 4) * 3) div 3
+    let offx = if x < 0: -(x * term.attrs.ppc) else: 0
+    let offy = if y < 0: -(y * term.attrs.ppl) else: 0
+    let w = int(bmp.width)
+    let h = int(bmp.height)
+    var dispw = w
+    if x + dispw div term.attrs.ppc > maxw:
+      dispw = (maxw - x) * term.attrs.ppc
+    var disph = h
+    if y + disph div term.attrs.ppl > maxh:
+      disph = (maxh - y) * term.attrs.ppl
+    var outs = term.cursorGoto(max(x, 0), max(y, 0))
+    outs &= APC & "Gf=24,m=1,a=T,C=1,s=" & $w & ",v=" & $h &
+      ",x=" & $offx & ",y=" & $offy & ",w=" & $dispw & ",h=" & $disph & ';'
+    var buf = newStringOfCap(MaxPixels * 4)
+    var i = 0
+    # transcode to RGB
+    while i < bmp.px.len: # max is 4096
+      if i > 0 and i mod MaxPixels == 0:
+        outs &= btoa(buf)
+        outs &= ST
+        term.write(outs)
+        buf.setLen(0)
+        outs = APC & "Gm=1;"
+      buf &= char(bmp.px[i].r)
+      buf &= char(bmp.px[i].g)
+      buf &= char(bmp.px[i].b)
+      inc i
+    outs = APC & "Gm=0;"
+    outs &= btoa(buf)
+    outs &= ST
+    term.write(outs)
+
 proc clearCanvas*(term: Terminal) =
   term.cleared = false
 
@@ -665,7 +719,7 @@ when termcap_found:
 
 type
   QueryAttrs = enum
-    qaAnsiColor, qaRGB, qaSixel
+    qaAnsiColor, qaRGB, qaSixel, qaKittyImage
 
   QueryResult = object
     success: bool
@@ -683,6 +737,7 @@ proc queryAttrs(term: Terminal; windowOnly: bool): QueryResult =
     const outs =
       XTGETFG &
       XTGETBG &
+      KITTYQUERY &
       GEOMPIXEL &
       GEOMCELL &
       XTGETTCAP("524742") &
@@ -764,12 +819,15 @@ proc queryAttrs(term: Terminal; windowOnly: bool): QueryResult =
       term.expect ';'
       if term.consume == 'r' and term.consume == 'g' and term.consume == 'b':
         term.expect ':'
-        template eat_color(tc: char): uint8 =
+        var was_esc = false
+        template eat_color(tc: set[char]): uint8 =
           var val = 0u8
           var i = 0
-          while (let c = term.consume; c != tc):
+          var c = char(0)
+          while (c = term.consume; c notin tc):
             let v0 = hexValue(c)
-            if i > 4 or v0 == -1: fail # wat
+            if i > 4 or v0 == -1:
+              fail # wat
             let v = uint8(v0)
             if i == 0: # 1st place
               val = (v shl 4) or v
@@ -777,10 +835,14 @@ proc queryAttrs(term: Terminal; windowOnly: bool): QueryResult =
               val = (val xor 0xF) or v
             # all other places are irrelevant
             inc i
+          was_esc = c == '\e'
           val
-        let r = eat_color '/'
-        let g = eat_color '/'
-        let b = eat_color '\a'
+        let r = eat_color {'/'}
+        let g = eat_color {'/'}
+        let b = eat_color {'\a', '\e'}
+        if was_esc:
+          # we got ST, not BEL; at least kitty does this
+          term.expect '\\'
         if c == '0':
           result.fgcolor = some(rgb(r, g, b))
         else:
@@ -805,7 +867,14 @@ proc queryAttrs(term: Terminal; windowOnly: bool): QueryResult =
         if id == tcapRGB:
           result.attrs.incl(qaRGB)
       else: # 0
-        term.expect '\e' # ST (1)
+        # pure insanity: kitty returns P0, but also +r524742 after. please
+        # make up your mind!
+        term.skip_until '\e' # ST (1)
+      term.expect '\\' # ST (2)
+    of '_': # APC
+      term.expect 'G'
+      result.attrs.incl(qaKittyImage)
+      term.skip_until '\e' # ST (1)
       term.expect '\\' # ST (2)
     else:
       fail
@@ -844,6 +913,10 @@ proc detectTermAttributes(term: Terminal; windowOnly: bool): TermStartResult =
         term.colormode = cmANSI
       if qaRGB in r.attrs:
         term.colormode = cmTrueColor
+      if qaSixel in r.attrs:
+        term.imagemode = imSixel
+      if qaKittyImage in r.attrs:
+        term.imagemode = imKitty
       # just assume the terminal doesn't choke on these.
       term.formatmode = {ffStrike, ffOverline}
       if r.bgcolor.isSome: