diff options
-rw-r--r-- | README.md | 21 | ||||
-rw-r--r-- | doc/config.md | 20 | ||||
-rw-r--r-- | res/config.toml | 5 | ||||
-rw-r--r-- | src/config/config.nim | 5 | ||||
-rw-r--r-- | src/display/term.nim | 200 | ||||
-rw-r--r-- | src/local/pager.nim | 5 |
6 files changed, 208 insertions, 48 deletions
diff --git a/README.md b/README.md index 0d5d1196..ea8717a7 100644 --- a/README.md +++ b/README.md @@ -98,16 +98,17 @@ want to try more established ones: ### Why does Chawan use strange/incorrect/ugly colors? -Chawan assumes the terminal's default background/foreground colors are -black and white. If this is not true for your terminal, make sure to set -the display.default-background-color and display.default-foreground-color -properties in your Chawan configuration file. - -Also, by default, Chawan uses the eight ANSI colors to display colored -text. To use true colors, either export COLORTERM=truecolor or set the -display.color-mode to "24bit". To use 256 colors, set display.color-mode to -"8bit" instead. (You can also turn off colors and/or styling altogether in -the configuration; please consult [doc/config.md](doc/config.md) for details.) +Chawan's display capabilities depend on what your terminal reports. In +particular: + +* if it does not respond to querying XTGETTCAP, and the COLORTERM environment + variable is not set, then Chawan falls back to ANSI colors +* if it does not respond to querying the background color, then Chawan's color + contrast correction will likely malfunction + +You can fix this manually by adjusting the display.default-background-color, +display.default-foreground-color, and display.color-mode variables. See +[doc/config.md](doc/config.md) for details. ### Can I view Markdown files using Chawan? diff --git a/doc/config.md b/doc/config.md index 8ecf54a4..361323ee 100644 --- a/doc/config.md +++ b/doc/config.md @@ -398,14 +398,26 @@ black background, etc).</td> <tr> <td>default-background-color</td> -<td>color</td> -<td>Sets the assumed background color of the terminal.</td> +<td>"auto" / color</td> +<td>Overrides the assumed background color of the terminal. "auto" leaves +background color detection to Chawan.</td> </tr> <tr> <td>default-foreground-color</td> -<td>color</td> -<td>Sets the assumed foreground color of the terminal.</td> +<td>"auto" / color</td> +<td>Sets the assumed foreground color of the terminal. "auto" leaves foreground +color detection to Chawan.</td> +</tr> + +<tr> +<td>query-da1</td> +<td>bool</td> +<td>Enable/disable querying Primary Device Attributes, and with it, all +"dynamic" terminal querying.<br> +It is highly recommended not to alter the default value (which is true), or the +output will most likely look horrible. (Except, obviously, if your terminal does +not support Primary Device Attributes.)</td> </tr> </table> diff --git a/res/config.toml b/res/config.toml index 307e974b..54f74729 100644 --- a/res/config.toml +++ b/res/config.toml @@ -63,8 +63,9 @@ double-width-ambiguous = false minimum-contrast = 100 force-clear = false set-title = true -default-background-color = "#000000" -default-foreground-color = "#FFFFFF" +default-background-color = "auto" +default-foreground-color = "auto" +query-da1 = true [[omnirule]] match = '^ddg:' diff --git a/src/config/config.nim b/src/config/config.nim index c66e7883..678f7f7b 100644 --- a/src/config/config.nim +++ b/src/config/config.nim @@ -117,8 +117,9 @@ type minimum_contrast* {.jsgetset.}: int32 force_clear* {.jsgetset.}: bool set_title* {.jsgetset.}: bool - default_background_color* {.jsgetset.}: RGBColor - default_foreground_color* {.jsgetset.}: RGBColor + default_background_color* {.jsgetset.}: Opt[RGBColor] + default_foreground_color* {.jsgetset.}: Opt[RGBColor] + query_da1* {.jsgetset.}: bool Config* = ref ConfigObj ConfigObj* = object diff --git a/src/display/term.nim b/src/display/term.nim index a4c91e61..fd6271f3 100644 --- a/src/display/term.nim +++ b/src/display/term.nim @@ -36,11 +36,10 @@ type us # start underline mode mr # start reverse mode mb # start blink mode + ZH # start italic mode ue # end underline mode se # end standout mode me # end all formatting modes - LE # cursor left %1 characters - RI # cursor right %1 characters vs # enhance cursor vi # make cursor invisible ve # reset cursor to normal @@ -70,11 +69,23 @@ type orig_flags: cint orig_flags2: cint orig_termios: Termios + defaultBackground: RGBColor + defaultForeground: RGBColor # control sequence introducer template CSI(s: varargs[string, `$`]): string = "\e[" & s.join(';') +# primary device attributes +const DA1 = CSI("c") + +# device control string +template DCS(a, b: char, s: varargs[string]): string = + "\eP" & a & b & s.join(';') & "\e\\" + +template XTGETTCAP(s: varargs[string, `$`]): string = + DCS('+', 'q', s) + # OS command template OSC(s: varargs[string, `$`]): string = "\e]" & s.join(';') & '\a' @@ -82,6 +93,9 @@ template OSC(s: varargs[string, `$`]): string = template XTERM_TITLE(s: string): string = OSC(0, s) +const XTGETFG = OSC(10, "?") # get foreground color +const XTGETBG = OSC(11, "?") # get background color + when not termcap_found: # DEC set template DECSET(s: varargs[string, `$`]): string = @@ -174,6 +188,7 @@ proc startFormat(term: Terminal, flag: FormatFlags): string = of FLAG_UNDERLINE: return term.cap us of FLAG_REVERSE: return term.cap mr of FLAG_BLINK: return term.cap mb + of FLAG_ITALIC: return term.cap ZH else: discard return SGR(FormatCodes[flag].s) @@ -200,12 +215,6 @@ proc disableAltScreen(term: Terminal): string = else: return RMCUP -func defaultBackground(term: Terminal): RGBColor = - return term.config.display.default_background_color - -func defaultForeground(term: Terminal): RGBColor = - return term.config.display.default_foreground_color - func mincontrast(term: Terminal): int32 = return term.config.display.minimum_contrast @@ -490,10 +499,9 @@ proc applyConfig(term: Terminal) = if term.config.display.color_mode.isSome: term.colormode = term.config.display.color_mode.get elif term.isatty(): - term.colormode = ANSI let colorterm = getEnv("COLORTERM") - case colorterm - of "24bit", "truecolor": term.colormode = TRUE_COLOR + if colorterm in ["24bit", "truecolor"]: + term.colormode = TRUE_COLOR if term.config.display.format_mode.isSome: term.formatmode = term.config.display.format_mode.get for fm in FormatFlags: @@ -503,6 +511,10 @@ proc applyConfig(term: Terminal) = if term.config.display.alt_screen.isSome: term.smcup = term.config.display.alt_screen.get term.set_title = term.config.display.set_title + if term.config.display.default_background_color.isSome: + 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 if term.config.encoding.display_charset.isSome: term.cs = term.config.encoding.display_charset.get else: @@ -596,35 +608,163 @@ when termcap_found: else: raise newException(Defect, "Failed to load termcap description for terminal " & term.tname) -proc detectTermAttributes(term: Terminal) = +type + QueryAttrs = enum + qaAnsiColor, qaRGB, qaSixel + + QueryResult = object + success: bool + attrs: set[QueryAttrs] + fgcolor: Option[RGBColor] + bgcolor: Option[RGBColor] + +proc queryAttrs(term: Terminal): QueryResult = + const tcapRGB = 0x524742 # RGB supported? + const outs = + XTGETFG & + XTGETBG & + XTGETTCAP("524742") & + DA1 + term.outfile.write(outs) + result = QueryResult(success: false, attrs: {}) + while true: + template consume(term: Terminal): char = term.infile.readChar() + template fail = break + template expect(term: Terminal, c: char) = + if term.consume != c: + fail + template expect(term: Terminal, s: string) = + for c in s: + term.expect c + template skip_until(term: Terminal, c: char) = + while (let cc = term.consume; cc != c): + discard + term.expect '\e' + 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 + of ']': + # OSC + term.expect '1' + let c = term.consume + if c notin {'0', '1'}: fail + term.expect ';' + if term.consume == 'r' and term.consume == 'g' and term.consume == 'b': + term.expect ':' + template eat_color(tc: char): uint8 = + var val = 0u8 + var i = 0 + while (let c = term.consume; c != tc): + let v0 = hexValue(c) + if i > 4 or v0 == -1: fail # wat + let v = uint8(v0) + if i == 0: # 1st place + val = (v shl 4) or v + elif i == 1: # 2nd place + val = (val xor 0xF) or v + # all other places are irrelevant + inc i + val + let r = eat_color '/' + let g = eat_color '/' + let b = eat_color '\a' + if c == '0': + result.fgcolor = some(rgb(r, g, b)) + else: + result.bgcolor = some(rgb(r, g, b)) + else: + # not RGB, give up + term.skip_until '\a' + of 'P': + # DCS + let c = term.consume + if c notin {'0', '1'}: + fail + term.expect "+r" + if c == '1': + var id = 0 + while (let c = term.consume; c != '='): + if c notin AsciiHexDigit: + fail + id *= 0x10 + id += hexValue(c) + term.skip_until '\e' # ST (1) + if id == tcapRGB: + result.attrs.incl(qaRGB) + else: # 0 + term.expect '\e' # ST (1) + term.expect '\\' # ST (2) + else: + fail + +type TermStartResult* = enum + tsrSuccess, tsrDA1Fail + +proc detectTermAttributes(term: Terminal): TermStartResult = + result = tsrSuccess term.tname = getEnv("TERM") if term.tname == "": term.tname = "dosansi" - when termcap_found: - if term.isatty(): + 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"]: + term.colormode = TRUE_COLOR + when termcap_found: term.loadTermcap() if term.tc != nil: term.smcup = term.hascap(ti) - term.formatmode = {FLAG_ITALIC, FLAG_OVERLINE, FLAG_STRIKE} - 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: - if term.isatty(): + 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)} - term.applyConfig() -proc start*(term: Terminal, infile: File) = +proc start*(term: Terminal, infile: File): TermStartResult = term.infile = infile if term.isatty(): term.enableRawMode() - term.detectTermAttributes() + result = term.detectTermAttributes() + term.applyConfig() if term.smcup: term.write(term.enableAltScreen()) @@ -643,7 +783,9 @@ proc newTerminal*(outfile: File, config: Config, attrs: WindowAttributes): Terminal = let term = Terminal( outfile: outfile, - config: config + config: config, + defaultBackground: ColorsRGB["black"], + defaultForeground: ColorsRGB["white"] ) term.windowChange(attrs) return term diff --git a/src/local/pager.nim b/src/local/pager.nim index ac241d73..aa546082 100644 --- a/src/local/pager.nim +++ b/src/local/pager.nim @@ -260,7 +260,10 @@ proc newPager*(config: Config, attrs: WindowAttributes, forkserver: ForkServer, return pager proc launchPager*(pager: Pager, infile: File) = - pager.term.start(infile) + case pager.term.start(infile) + of tsrSuccess: discard + of tsrDA1Fail: + pager.alert("Failed to query DA1, please set display.query-da1 = false") func infile*(pager: Pager): File = return pager.term.infile |