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 | ||||
| 
 | ||||
| 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 | ||||
| 
 | ||||
| pub enum KeyCode { | ||||
|  | @ -164,11 +167,11 @@ pub struct Context { | |||
| pub: | ||||
| 	cfg 		  Config | ||||
| mut: | ||||
| 	termios       C.termios | ||||
| 	read_buf      []byte | ||||
| 	print_buf     []byte | ||||
| 	paused        bool | ||||
| 	enable_su     bool | ||||
| 	enable_rgb    bool | ||||
| pub mut: | ||||
| 	frame_count   u64 | ||||
| 	window_width  int | ||||
|  | @ -191,6 +194,7 @@ pub struct Config { | |||
| 	hide_cursor          bool | ||||
| 	capture_events       bool | ||||
| 	use_alternate_buffer bool = true | ||||
| 	// All kill signals
 | ||||
| 	skip_init_checks     bool | ||||
| 	// 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 | ||||
| 
 | ||||
| const ( | ||||
|  | @ -28,12 +31,12 @@ pub fn (mut ctx Context) load_title() { | |||
|     print('\x1b[23;0t') | ||||
| } | ||||
| 
 | ||||
| pub fn (mut ctx Context) run() { | ||||
| pub fn (mut ctx Context) run() ? { | ||||
| 	if ctx.cfg.use_x11 { | ||||
| 		ctx.fail('error: x11 backend not implemented yet') | ||||
| 		exit(1) | ||||
| 	} else { | ||||
| 		ctx.termios_setup() | ||||
| 		ctx.termios_setup()? | ||||
| 		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 | ||||
| 
 | ||||
| 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 | ||||
| 
 | ||||
| import os | ||||
|  | @ -51,10 +54,14 @@ fn restore_terminal_state() { | |||
| 	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
 | ||||
| 	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() | ||||
| 
 | ||||
| 	if ctx.cfg.capture_events { | ||||
|  | @ -75,12 +82,16 @@ fn (mut ctx Context) termios_setup() { | |||
| 		print('\x1b]0;$ctx.cfg.window_title\x07') | ||||
| 	} | ||||
| 
 | ||||
| 	if !ctx.cfg.skip_init_checks { | ||||
| 		// prevent blocking during the feature detections, but allow enough time for the terminal
 | ||||
| 		// to send back the relevant input data
 | ||||
| 		termios.c_cc[C.VTIME] = 1 | ||||
| 		termios.c_cc[C.VMIN] = 0 | ||||
| 		C.tcsetattr(C.STDIN_FILENO, C.TCSAFLUSH, &termios) | ||||
| 		// feature-test the SU spec
 | ||||
| 		sx, sy := get_cursor_position() | ||||
| 		print('$bsu$esu') | ||||
| 		ex, ey := get_cursor_position() | ||||
| 
 | ||||
| 		if sx == ex && sy == ey { | ||||
| 			// the terminal either ignored or handled the sequence properly, enable SU
 | ||||
| 			ctx.enable_su = true | ||||
|  | @ -89,6 +100,10 @@ fn (mut ctx Context) termios_setup() { | |||
| 			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
 | ||||
| 	termios.c_cc[C.VTIME] = 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
 | ||||
| 		print('\x1b[2J\x1b[3J\x1b[1;1H') | ||||
| 	} | ||||
| 	ctx.termios = termios | ||||
| 	ctx.window_height, ctx.window_width = get_terminal_size() | ||||
| 
 | ||||
| 	// Reset console on exit
 | ||||
|  | @ -179,6 +193,61 @@ fn get_cursor_position() (int, int) { | |||
| 	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() { | ||||
| 	C.tcsetattr(C.STDIN_FILENO, C.TCSAFLUSH /* C.TCSANOW ?? */, &termios_at_startup) | ||||
| 	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 | ||||
| 
 | ||||
| import strings | ||||
|  | @ -57,12 +60,20 @@ pub fn (mut ctx Context) set_cursor_position(x int, y int) { | |||
| 
 | ||||
| [inline] | ||||
| pub fn (mut ctx Context) set_color(c Color) { | ||||
| 	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] | ||||
| pub fn (mut ctx Context) set_bg_color(c Color) { | ||||
| 	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] | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue