term.ui: approximate colors into ansi if rgb isn't supported (#6951)
parent
258f8f6af9
commit
e03ae19372
|
@ -1,3 +1,6 @@
|
||||||
|
// Copyright (c) 2020 Raúl Hernández. All rights reserved.
|
||||||
|
// Use of this source code is governed by an MIT license
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
module main
|
module main
|
||||||
|
|
||||||
import term.ui as tui
|
import term.ui as tui
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
// radare - LGPL - Copyright 2013-2020 - pancake, xarkes
|
||||||
|
// ansi 256 color extension for r_cons
|
||||||
|
// https://en.wikipedia.org/wiki/ANSI_color
|
||||||
|
|
||||||
|
module ui
|
||||||
|
|
||||||
|
const (
|
||||||
|
value_range = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]!!
|
||||||
|
color_table = init_color_table()
|
||||||
|
)
|
||||||
|
|
||||||
|
[direct_array_access]
|
||||||
|
fn init_color_table() []int {
|
||||||
|
mut color_table := []int{len: 256}
|
||||||
|
// ansi colors
|
||||||
|
color_table[0] = 0x000000
|
||||||
|
color_table[1] = 0x800000
|
||||||
|
color_table[2] = 0x008000
|
||||||
|
color_table[3] = 0x808000
|
||||||
|
color_table[4] = 0x000080
|
||||||
|
color_table[5] = 0x800080
|
||||||
|
color_table[6] = 0x008080
|
||||||
|
color_table[7] = 0xc0c0c0
|
||||||
|
color_table[8] = 0x808080
|
||||||
|
color_table[9] = 0xff0000
|
||||||
|
color_table[10] = 0x00ff00
|
||||||
|
color_table[11] = 0xffff00
|
||||||
|
color_table[12] = 0x0000ff
|
||||||
|
color_table[13] = 0xff00ff
|
||||||
|
color_table[14] = 0x00ffff
|
||||||
|
color_table[15] = 0xffffff
|
||||||
|
// color palette
|
||||||
|
for i in 0 .. 216 {
|
||||||
|
r := value_range[(i / 36) % 6]
|
||||||
|
g := value_range[(i / 6) % 6]
|
||||||
|
b := value_range[i % 6]
|
||||||
|
color_table[i + 16] = ((r << 16) & 0xffffff) + ((g << 8) & 0xffff) + (b & 0xff)
|
||||||
|
}
|
||||||
|
// grayscale
|
||||||
|
for i in 0 .. 24 {
|
||||||
|
r := 8 + (i * 10)
|
||||||
|
color_table[i + 232] = ((r << 16) & 0xffffff) + ((r << 8) & 0xffff) + (r & 0xff)
|
||||||
|
}
|
||||||
|
return color_table
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clamp(x int, y int, z int) int {
|
||||||
|
if x < y {
|
||||||
|
return y
|
||||||
|
}
|
||||||
|
if x > z {
|
||||||
|
return z
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
fn approximate_rgb(r int, g int, b int) int {
|
||||||
|
grey := r > 0 && r < 255 && r == g && r == b
|
||||||
|
if grey {
|
||||||
|
return 232 + int(f64(r) / (255 / 24.1))
|
||||||
|
}
|
||||||
|
k := int(256.0 / 6)
|
||||||
|
r2 := clamp(r / k, 0, 5)
|
||||||
|
g2 := clamp(g / k, 0, 5)
|
||||||
|
b2 := clamp(b / k, 0, 5)
|
||||||
|
return 16 + (r2 * 36) + (g2 * 6) + b2
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lookup_rgb(r int, g int, b int) int {
|
||||||
|
color := (r << 16) + (g << 8) + b
|
||||||
|
// lookup extended colors only, coz non-extended can be changed by users.
|
||||||
|
for i in 16 .. 256 {
|
||||||
|
if color_table[i] == color {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// converts an RGB color to an ANSI 256-color, approximating it to the nearest available color
|
||||||
|
// if an exact match is not found
|
||||||
|
fn rgb2ansi(r int, g int, b int) int {
|
||||||
|
c := lookup_rgb(r, g, b)
|
||||||
|
if c == -1 {
|
||||||
|
return approximate_rgb(r, g, b)
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
|
@ -1,3 +1,6 @@
|
||||||
|
// Copyright (c) 2020 Raúl Hernández. All rights reserved.
|
||||||
|
// Use of this source code is governed by an MIT license
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
module ui
|
module ui
|
||||||
|
|
||||||
pub enum KeyCode {
|
pub enum KeyCode {
|
||||||
|
@ -164,11 +167,11 @@ pub struct Context {
|
||||||
pub:
|
pub:
|
||||||
cfg Config
|
cfg Config
|
||||||
mut:
|
mut:
|
||||||
termios C.termios
|
|
||||||
read_buf []byte
|
read_buf []byte
|
||||||
print_buf []byte
|
print_buf []byte
|
||||||
paused bool
|
paused bool
|
||||||
enable_su bool
|
enable_su bool
|
||||||
|
enable_rgb bool
|
||||||
pub mut:
|
pub mut:
|
||||||
frame_count u64
|
frame_count u64
|
||||||
window_width int
|
window_width int
|
||||||
|
@ -176,21 +179,22 @@ pub mut:
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
user_data voidptr
|
user_data voidptr
|
||||||
init_fn fn(voidptr)
|
init_fn fn(voidptr)
|
||||||
frame_fn fn(voidptr)
|
frame_fn fn(voidptr)
|
||||||
cleanup_fn fn(voidptr)
|
cleanup_fn fn(voidptr)
|
||||||
event_fn fn(&Event, voidptr)
|
event_fn fn(&Event, voidptr)
|
||||||
fail_fn fn(string)
|
fail_fn fn(string)
|
||||||
|
|
||||||
buffer_size int = 256
|
buffer_size int = 256
|
||||||
frame_rate int = 30
|
frame_rate int = 30
|
||||||
use_x11 bool
|
use_x11 bool
|
||||||
|
|
||||||
window_title string
|
window_title string
|
||||||
hide_cursor bool
|
hide_cursor bool
|
||||||
capture_events bool
|
capture_events bool
|
||||||
use_alternate_buffer bool = true
|
use_alternate_buffer bool = true
|
||||||
// All kill signals
|
skip_init_checks bool
|
||||||
reset []int = [1, 2, 3, 4, 6, 7, 8, 9, 11, 13, 14, 15, 19]
|
// All kill signals to set up exit listeners on
|
||||||
|
reset []int = [1, 2, 3, 4, 6, 7, 8, 9, 11, 13, 14, 15, 19]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
// Copyright (c) 2020 Raúl Hernández. All rights reserved.
|
||||||
|
// Use of this source code is governed by an MIT license
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
module ui
|
module ui
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -28,12 +31,12 @@ pub fn (mut ctx Context) load_title() {
|
||||||
print('\x1b[23;0t')
|
print('\x1b[23;0t')
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn (mut ctx Context) run() {
|
pub fn (mut ctx Context) run() ? {
|
||||||
if ctx.cfg.use_x11 {
|
if ctx.cfg.use_x11 {
|
||||||
ctx.fail('error: x11 backend not implemented yet')
|
ctx.fail('error: x11 backend not implemented yet')
|
||||||
exit(1)
|
exit(1)
|
||||||
} else {
|
} else {
|
||||||
ctx.termios_setup()
|
ctx.termios_setup()?
|
||||||
ctx.termios_loop()
|
ctx.termios_loop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
// Copyright (c) 2020 Raúl Hernández. All rights reserved.
|
||||||
|
// Use of this source code is governed by an MIT license
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
module ui
|
module ui
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
// Copyright (c) 2020 Raúl Hernández. All rights reserved.
|
||||||
|
// Use of this source code is governed by an MIT license
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
module ui
|
module ui
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
@ -51,10 +54,14 @@ fn restore_terminal_state() {
|
||||||
os.flush()
|
os.flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut ctx Context) termios_setup() {
|
fn (mut ctx Context) termios_setup() ? {
|
||||||
// store the current title, so restore_terminal_state can get it back
|
// store the current title, so restore_terminal_state can get it back
|
||||||
ctx.save_title()
|
ctx.save_title()
|
||||||
|
|
||||||
|
if !ctx.cfg.skip_init_checks && !(is_atty(C.STDIN_FILENO) != 0 && is_atty(C.STDOUT_FILENO) != 0) {
|
||||||
|
return error('not running under a TTY')
|
||||||
|
}
|
||||||
|
|
||||||
mut termios := get_termios()
|
mut termios := get_termios()
|
||||||
|
|
||||||
if ctx.cfg.capture_events {
|
if ctx.cfg.capture_events {
|
||||||
|
@ -75,20 +82,28 @@ fn (mut ctx Context) termios_setup() {
|
||||||
print('\x1b]0;$ctx.cfg.window_title\x07')
|
print('\x1b]0;$ctx.cfg.window_title\x07')
|
||||||
}
|
}
|
||||||
|
|
||||||
C.tcsetattr(C.STDIN_FILENO, C.TCSAFLUSH, &termios)
|
if !ctx.cfg.skip_init_checks {
|
||||||
// feature-test the SU spec
|
// prevent blocking during the feature detections, but allow enough time for the terminal
|
||||||
sx, sy := get_cursor_position()
|
// to send back the relevant input data
|
||||||
print('$bsu$esu')
|
termios.c_cc[C.VTIME] = 1
|
||||||
ex, ey := get_cursor_position()
|
termios.c_cc[C.VMIN] = 0
|
||||||
|
C.tcsetattr(C.STDIN_FILENO, C.TCSAFLUSH, &termios)
|
||||||
if sx == ex && sy == ey {
|
// feature-test the SU spec
|
||||||
// the terminal either ignored or handled the sequence properly, enable SU
|
sx, sy := get_cursor_position()
|
||||||
ctx.enable_su = true
|
print('$bsu$esu')
|
||||||
} else {
|
ex, ey := get_cursor_position()
|
||||||
ctx.draw_line(sx, sy, ex, ey)
|
if sx == ex && sy == ey {
|
||||||
ctx.set_cursor_position(sx, sy)
|
// the terminal either ignored or handled the sequence properly, enable SU
|
||||||
ctx.flush()
|
ctx.enable_su = true
|
||||||
|
} else {
|
||||||
|
ctx.draw_line(sx, sy, ex, ey)
|
||||||
|
ctx.set_cursor_position(sx, sy)
|
||||||
|
ctx.flush()
|
||||||
|
}
|
||||||
|
// feature-test rgb (truecolor) support
|
||||||
|
ctx.enable_rgb = supports_truecolor()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent stdin from blocking by making its read time 0
|
// Prevent stdin from blocking by making its read time 0
|
||||||
termios.c_cc[C.VTIME] = 0
|
termios.c_cc[C.VTIME] = 0
|
||||||
termios.c_cc[C.VMIN] = 0
|
termios.c_cc[C.VMIN] = 0
|
||||||
|
@ -101,7 +116,6 @@ fn (mut ctx Context) termios_setup() {
|
||||||
// clear the terminal and set the cursor to the origin
|
// clear the terminal and set the cursor to the origin
|
||||||
print('\x1b[2J\x1b[3J\x1b[1;1H')
|
print('\x1b[2J\x1b[3J\x1b[1;1H')
|
||||||
}
|
}
|
||||||
ctx.termios = termios
|
|
||||||
ctx.window_height, ctx.window_width = get_terminal_size()
|
ctx.window_height, ctx.window_width = get_terminal_size()
|
||||||
|
|
||||||
// Reset console on exit
|
// Reset console on exit
|
||||||
|
@ -179,6 +193,61 @@ fn get_cursor_position() (int, int) {
|
||||||
return x, y
|
return x, y
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn supports_truecolor() bool {
|
||||||
|
// set the bg color to some arbirtrary value (#010203), assumed not to be the default
|
||||||
|
print('\x1b[48:2:1:2:3m')
|
||||||
|
|
||||||
|
sx, sy := get_cursor_position()
|
||||||
|
// sequence to query the current cursor position
|
||||||
|
print('\x1bP\$qm\x1b\\')
|
||||||
|
color := get_current_bg_color()
|
||||||
|
ex, ey := get_cursor_position()
|
||||||
|
// if the terminal doesn't understand the "get current color",
|
||||||
|
// assume it doesn't support truecolor either
|
||||||
|
if !(sx == ex && sy == ey) {
|
||||||
|
println('>>> different pos: ($sx, $sy) -> ($ex, $ey)')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// TODO: iTerm emits a different sequence, but it's compatible anyways, so
|
||||||
|
if color !in [0x010203, 0x01010203] {
|
||||||
|
println('>>> no match: $color')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_current_bg_color() int {
|
||||||
|
mut res := 0
|
||||||
|
mut colon_cnt := 0
|
||||||
|
mut cur_val := 0
|
||||||
|
|
||||||
|
for i := 0; i < 50 ; i++ {
|
||||||
|
ch := int(C.getchar())
|
||||||
|
b := byte(ch)
|
||||||
|
|
||||||
|
if b in [0, 255] {
|
||||||
|
return -1
|
||||||
|
} else if b == `m` {
|
||||||
|
if colon_cnt > 1 {
|
||||||
|
res = (res << 8) | cur_val
|
||||||
|
cur_val = 0
|
||||||
|
}
|
||||||
|
break
|
||||||
|
} else if b in [`:`, `;`] {
|
||||||
|
if colon_cnt > 1 {
|
||||||
|
res = (res << 8) | cur_val
|
||||||
|
cur_val = 0
|
||||||
|
}
|
||||||
|
colon_cnt++
|
||||||
|
} else if b.is_digit() {
|
||||||
|
if colon_cnt > 1 {
|
||||||
|
cur_val = cur_val * 10 + b - byte(`0`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
fn termios_reset() {
|
fn termios_reset() {
|
||||||
C.tcsetattr(C.STDIN_FILENO, C.TCSAFLUSH /* C.TCSANOW ?? */, &termios_at_startup)
|
C.tcsetattr(C.STDIN_FILENO, C.TCSAFLUSH /* C.TCSANOW ?? */, &termios_at_startup)
|
||||||
print('\x1b[?1003l\x1b[?1006l\x1b[?25h')
|
print('\x1b[?1003l\x1b[?1006l\x1b[?25h')
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
// Copyright (c) 2020 Raúl Hernández. All rights reserved.
|
||||||
|
// Use of this source code is governed by an MIT license
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
module ui
|
module ui
|
||||||
|
|
||||||
import strings
|
import strings
|
||||||
|
@ -57,12 +60,20 @@ pub fn (mut ctx Context) set_cursor_position(x int, y int) {
|
||||||
|
|
||||||
[inline]
|
[inline]
|
||||||
pub fn (mut ctx Context) set_color(c Color) {
|
pub fn (mut ctx Context) set_color(c Color) {
|
||||||
ctx.write('\x1b[38;2;${int(c.r)};${int(c.g)};${int(c.b)}m')
|
if ctx.enable_rgb {
|
||||||
|
ctx.write('\x1b[38;2;${int(c.r)};${int(c.g)};${int(c.b)}m')
|
||||||
|
} else {
|
||||||
|
ctx.write('\x1b[38;5;${rgb2ansi(c.r, c.g, c.b)}m')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[inline]
|
[inline]
|
||||||
pub fn (mut ctx Context) set_bg_color(c Color) {
|
pub fn (mut ctx Context) set_bg_color(c Color) {
|
||||||
ctx.write('\x1b[48;2;${int(c.r)};${int(c.g)};${int(c.b)}m')
|
if ctx.enable_rgb {
|
||||||
|
ctx.write('\x1b[48;2;${int(c.r)};${int(c.g)};${int(c.b)}m')
|
||||||
|
} else {
|
||||||
|
ctx.write('\x1b[48;5;${rgb2ansi(c.r, c.g, c.b)}m')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[inline]
|
[inline]
|
||||||
|
|
Loading…
Reference in New Issue