about summary refs log tree commit diff stats
path: root/src/display
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2024-02-25 20:25:04 +0100
committerbptato <nincsnevem662@gmail.com>2024-02-25 20:25:04 +0100
commit92fbf479f8bf6db6ad0655e5b71a0b58a45a2213 (patch)
tree47b7f2c23ebabbf94e48476615014b52762542f3 /src/display
parent4df71520f39ed7992f78484701467e513a34c5dc (diff)
downloadchawan-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.nim2
-rw-r--r--src/display/term.nim240
-rw-r--r--src/display/winattrs.nim58
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
-  )