def __init__(self, argv, rows=25, columns=80, scrollback=100, cell_width=10, cell_height=20, cwd=None, env=None): if isinstance(argv, str): argv = shlex.split(argv) pid, self.master_fd = fork() self.is_child = pid == CHILD self.write_buf = b'' if self.is_child: while read_screen_size().width != columns * cell_width: time.sleep(0.01) if cwd: os.chdir(cwd) os.execvpe(argv[0], argv, env or os.environ) os.set_blocking(self.master_fd, False) self.cell_width = cell_width self.cell_height = cell_height self.set_window_size(rows=rows, columns=columns) self.callbacks = Callbacks(self) self.screen = Screen(self.callbacks, rows, columns, scrollback, cell_width, cell_height, 0, self.callbacks) self.received_bytes = b''
def _draw_right_status(screen: Screen, is_last: bool) -> int: if not is_last: return 0 draw_attributed_string(Formatter.reset, screen) date = datetime.datetime.now().strftime(" %H:%M") utc_date = datetime.datetime.now( datetime.timezone.utc).strftime(" (UTC %H:%M)") right_status_length = calc_draw_spaces(date + " " + utc_date + " ") draw_spaces = screen.columns - screen.cursor.x - right_status_length if draw_spaces > 0: screen.draw(" " * draw_spaces) cells = [ (Color(135, 192, 149), date), (Color(113, 115, 116), utc_date), ] screen.cursor.fg = 0 for color, status in cells: screen.cursor.fg = as_rgb(color_as_int(color)) screen.draw(status) screen.cursor.bg = 0 if screen.columns - screen.cursor.x > right_status_length: screen.cursor.x = screen.columns - right_status_length return screen.cursor.x
def shape_string( text: str = "abcd", family: str = 'monospace', size: float = 11.0, dpi: float = 96.0, path: Optional[str] = None ) -> List[Tuple[int, int, int, Tuple[int, ...]]]: with setup_for_testing(family, size, dpi) as (sprites, cell_width, cell_height): s = Screen(None, 1, len(text)*2) line = s.line(0) s.draw(text) return test_shape(line, path)
def shape_string(text="abcd", family='monospace', size=11.0, dpi=96.0, path=None): try: sprites, cell_width, cell_height = setup_for_testing(family, size, dpi) s = Screen(None, 1, len(text)*2) line = s.line(0) s.draw(text) return test_shape(line, path) finally: set_send_sprite_to_gpu(None)
def _draw_left_icon(screen: Screen, draw_data: DrawData) -> int: orig_fg, orig_bg = screen.cursor.fg, screen.cursor.bg screen.cursor.fg = orig_bg screen.cursor.bg = draw_data.default_bg screen.draw('') screen.cursor.fg, screen.cursor.bg = orig_fg, orig_bg screen.cursor.x = 1 return screen.cursor.x
def _draw_icon(screen: Screen, index: int, symbol: str = "") -> int: if index != 1: return 0 fg, bg = screen.cursor.fg, screen.cursor.bg screen.cursor.fg = as_rgb(color_as_int(Color(255, 250, 205))) screen.cursor.bg = as_rgb(color_as_int(Color(60, 71, 77))) screen.draw(symbol) screen.cursor.fg, screen.cursor.bg = fg, bg screen.cursor.x = len(symbol) return screen.cursor.x
def shape_string(text="abcd", family='monospace', size=11.0, dpi=96.0, path=None): with setup_for_testing(family, size, dpi) as (sprites, cell_width, cell_height): s = Screen(None, 1, len(text) * 2) line = s.line(0) s.draw(text) return test_shape(line, path)
def _draw_left_status( draw_data: DrawData, screen: Screen, tab: TabBarData, before: int, max_title_length: int, index: int, is_last: bool, extra_data: ExtraData, ) -> int: if draw_data.leading_spaces: screen.draw(" " * draw_data.leading_spaces) draw_title(draw_data, screen, tab, index) trailing_spaces = min(max_title_length - 1, draw_data.trailing_spaces) max_title_length -= trailing_spaces extra = screen.cursor.x - before - max_title_length if extra > 0: screen.cursor.x -= extra + 1 screen.draw("…") if trailing_spaces: screen.draw(" " * trailing_spaces) end = screen.cursor.x screen.cursor.bold = screen.cursor.italic = False screen.cursor.fg = 0 if not is_last: screen.cursor.bg = as_rgb(color_as_int(draw_data.inactive_bg)) screen.draw(draw_data.sep) screen.cursor.bg = 0 return end
def render_string(text: str, family: str = 'monospace', size: float = 11.0, dpi: float = 96.0) -> Tuple[int, int, List[bytes]]: with setup_for_testing(family, size, dpi) as (sprites, cell_width, cell_height): s = Screen(None, 1, len(text)*2) line = s.line(0) s.draw(text) test_render_line(line) cells = [] found_content = False for i in reversed(range(s.columns)): sp = list(line.sprite_at(i)) sp[2] &= 0xfff tsp = sp[0], sp[1], sp[2] if tsp == (0, 0, 0) and not found_content: continue found_content = True cells.append(sprites[tsp]) return cell_width, cell_height, list(reversed(cells))
def create_screen(self, cols=5, lines=5, scrollback=5, cell_width=10, cell_height=20, options=None): final_options = {'scrollback_pager_history_size': 1024, 'click_interval': 0.5} if options: final_options.update(options) options = Options(merge_configs(defaults._asdict(), final_options)) set_options(options) c = Callbacks() return Screen(c, lines, cols, scrollback, cell_width, cell_height, 0, c)
def render_string(text, family='monospace', size=11.0, dpi=96.0): with setup_for_testing(family, size, dpi) as (sprites, cell_width, cell_height): s = Screen(None, 1, len(text) * 2) line = s.line(0) s.draw(text) test_render_line(line) cells = [] found_content = False for i in reversed(range(s.columns)): sp = list(line.sprite_at(i)) sp[2] &= 0xfff sp = tuple(sp) if sp == (0, 0, 0) and not found_content: continue found_content = True cells.append(sprites[sp]) return cell_width, cell_height, list(reversed(cells))
def create_screen(self, cols=5, lines=5, scrollback=5, cell_width=10, cell_height=20): c = Callbacks() return Screen(c, lines, cols, scrollback, cell_width, cell_height, 0, c)
def create_screen(self, cols=5, lines=5, scrollback=5, cell_width=10, cell_height=20, options=None): opts = self.set_options(options) c = Callbacks(opts) s = Screen(c, lines, cols, scrollback, cell_width, cell_height, 0, c) return s
def create_screen(self, cols=5, lines=5, scrollback=5, cell_width=10, cell_height=20, options={}): options = Options(merge_configs(defaults._asdict(), options)) set_options(options) c = Callbacks() return Screen(c, lines, cols, scrollback, cell_width, cell_height, 0, c)
def create_screen(self, cols=5, lines=5, scrollback=5, cell_width=10, cell_height=20, options=None): if options is None: options = {'scrollback_pager_history_size': 1024} options = Options(merge_configs(defaults._asdict(), options)) set_options(options) c = Callbacks() return Screen(c, lines, cols, scrollback, cell_width, cell_height, 0, c)
def _draw_left_status( draw_data: DrawData, screen: Screen, tab: TabBarData, before: int, max_title_length: int, index: int, is_last: bool, extra_data: ExtraData, ) -> int: print(extra_data) if draw_data.leading_spaces: screen.draw(" " * draw_data.leading_spaces) # TODO: https://github.com/kovidgoyal/kitty/discussions/4447#discussioncomment-2463083 # tm = get_boss().active_tab_manager # if tm is not None: # w = tm.active_window # if w is not None: # cwd = w.cwd_of_child or '' # log_error(cwd) draw_title(draw_data, screen, tab, index) trailing_spaces = min(max_title_length - 1, draw_data.trailing_spaces) max_title_length -= trailing_spaces extra = screen.cursor.x - before - max_title_length if extra > 0: screen.cursor.x -= extra + 1 screen.draw("…") if trailing_spaces: screen.draw(" " * trailing_spaces) end = screen.cursor.x screen.cursor.bold = screen.cursor.italic = False screen.cursor.fg = 0 if not is_last: screen.cursor.bg = as_rgb(color_as_int(draw_data.inactive_bg)) screen.draw(draw_data.sep) screen.cursor.bg = 0 return end
def create_screen(self, cols=5, lines=5, scrollback=5): c = Callbacks() return Screen(c, lines, cols, scrollback, 0, c)
class PTY: def __init__(self, argv, rows=25, columns=80, scrollback=100, cell_width=10, cell_height=20, cwd=None, env=None): if isinstance(argv, str): argv = shlex.split(argv) pid, self.master_fd = fork() self.is_child = pid == CHILD self.write_buf = b'' if self.is_child: while read_screen_size().width != columns * cell_width: time.sleep(0.01) if cwd: os.chdir(cwd) os.execvpe(argv[0], argv, env or os.environ) os.set_blocking(self.master_fd, False) self.cell_width = cell_width self.cell_height = cell_height self.set_window_size(rows=rows, columns=columns) self.callbacks = Callbacks(self) self.screen = Screen(self.callbacks, rows, columns, scrollback, cell_width, cell_height, 0, self.callbacks) self.received_bytes = b'' def turn_off_echo(self): s = termios.tcgetattr(self.master_fd) s[3] &= ~termios.ECHO termios.tcsetattr(self.master_fd, termios.TCSANOW, s) def is_echo_on(self): s = termios.tcgetattr(self.master_fd) return True if s[3] & termios.ECHO else False def __del__(self): if not self.is_child: fd = self.master_fd del self.master_fd os.close(fd) def write_to_child(self, data): if isinstance(data, str): data = data.encode('utf-8') self.write_buf += data def send_cmd_to_child(self, cmd): self.write_to_child(cmd + '\r') def process_input_from_child(self, timeout=10): rd, wd, err = select.select([self.master_fd], [self.master_fd] if self.write_buf else [], [self.master_fd], timeout) if err: raise OSError('master_fd is in error condition') while wd: try: n = os.write(self.master_fd, self.write_buf) except (BlockingIOError, OSError): n = 0 if not n: break self.write_buf = self.write_buf[n:] bytes_read = 0 while rd: try: data = os.read(self.master_fd, io.DEFAULT_BUFFER_SIZE) except (BlockingIOError, OSError): data = b'' if not data: break bytes_read += len(data) self.received_bytes += data parse_bytes(self.screen, data) return bytes_read def wait_till(self, q, timeout=10): end_time = time.monotonic() + timeout while not q() and time.monotonic() <= end_time: self.process_input_from_child( timeout=max(0, end_time - time.monotonic())) if not q(): raise TimeoutError( f'The condition was not met. Screen contents: \n {repr(self.screen_contents())}' ) def set_window_size(self, rows=25, columns=80): if hasattr(self, 'screen'): self.screen.resize(rows, columns) x_pixels = columns * self.cell_width y_pixels = rows * self.cell_height s = struct.pack('HHHH', rows, columns, x_pixels, y_pixels) fcntl.ioctl(self.master_fd, termios.TIOCSWINSZ, s) def screen_contents(self): lines = [] for i in range(self.screen.lines): x = str(self.screen.line(i)) if x: lines.append(x) return '\n'.join(lines) def last_cmd_output(self, as_ansi=False, add_wrap_markers=False): from kitty.window import cmd_output return cmd_output(self.screen, as_ansi=as_ansi, add_wrap_markers=add_wrap_markers)
def create_screen(self, cols=5, lines=5, scrollback=5): return Screen(Callbacks(), lines, cols, scrollback)