diff --git a/docs/graphics-protocol.rst b/docs/graphics-protocol.rst index 6ce593743..1ad7b4f23 100644 --- a/docs/graphics-protocol.rst +++ b/docs/graphics-protocol.rst @@ -697,7 +697,7 @@ take, and the default value they take when missing. All integers are 32-bit. Key Value Default Description ======= ==================== ========= ================= ``a`` Single character. ``t`` The overall action this graphics command is performing. - ``(a, c, d, f, ` ``t`` - transmit data, ``T`` - transmit data and display image, + ``(a, c, d, f, `` ``t`` - transmit data, ``T`` - transmit data and display image, ``p, q, t, T)`` ``q`` - query terminal, ``p`` - put (display) previous transmitted image, ``d`` - delete image, ``f`` - transmit data for animation frames, ``a`` - control animation, ``c`` - compose animation frames diff --git a/kittens/tui/images.py b/kittens/tui/images.py index 348f7e603..bfddf6c2c 100644 --- a/kittens/tui/images.py +++ b/kittens/tui/images.py @@ -18,7 +18,8 @@ from typing import ( from kitty.conf.utils import positive_float, positive_int from kitty.fast_data_types import create_canvas from kitty.typing import ( - CompletedProcess, GRT_a, GRT_d, GRT_f, GRT_m, GRT_o, GRT_t, HandlerType + GRT_C, CompletedProcess, GRT_a, GRT_d, GRT_f, GRT_m, GRT_o, GRT_t, + HandlerType ) from kitty.utils import ScreenSize, find_exe, fit_image @@ -313,7 +314,7 @@ class GraphicsCommand: I: int = 0 # image number p: int = 0 # placement id o: Optional[GRT_o] = None # type of compression - m: GRT_m = 0 # 0 or 1 whether there is more chunked data + m: GRT_m = 0 # 0 or 1 whether there is more chunked data x: int = 0 # left edge of image area to display y: int = 0 # top edge of image area to display w: int = 0 # image width to display @@ -323,7 +324,268 @@ class GraphicsCommand: c: int = 0 # number of cols to display image over r: int = 0 # number of rows to display image over z: int = 0 # z-index - d: GRT_d = 'a' # what to delete + C: GRT_C = 0 # cursor movement policy/composition mode + d: GRT_d = 'a' # what to delete + + @property + def action(self) -> GRT_a: + return self.a + + @action.setter + def action(self, val: GRT_a) -> None: + self.a = val + + @property + def transmission_type(self) -> GRT_t: + return self.t + + @transmission_type.setter + def transmission_type(self, val: GRT_t) -> None: + self.t = val + + @property + def compressed(self) -> Optional[GRT_o]: + return self.o + + @compressed.setter + def compressed(self, val: Optional[GRT_o]) -> None: + self.o = val + + @property + def delete_action(self) -> Optional[GRT_d]: + return self.d + + @delete_action.setter + def delete_action(self, val: GRT_d) -> None: + self.d = val + + @delete_action.setter + def delete_action(self, val: GRT_d) -> None: + self.d = val + + @property + def format(self) -> GRT_f: + return self.f + + @format.setter + def format(self, val: GRT_f) -> None: + self.f = val + + @property + def more(self) -> GRT_m: + return self.m + + @more.setter + def more(self, val: GRT_m) -> None: + self.m = val + + @property + def image_id(self) -> int: + return self.i + + @image_id.setter + def image_id(self, val: int) -> None: + self.i = val + + @property + def image_number(self) -> int: + return self.I + + @image_number.setter + def image_number(self, val: int) -> None: + self.I = val # noqa + + @property + def placement_id(self) -> int: + return self.p + + @placement_id.setter + def placement_id(self, val: int) -> None: + self.p = val + + @property + def data_size(self) -> int: + return self.S + + @data_size.setter + def data_size(self, val: int) -> None: + self.S = val + + @property + def data_offset(self) -> int: + return self.O + + @data_offset.setter + def data_offset(self, val: int) -> None: + self.O = val # noqa + + @property + def quiet(self) -> int: + return self.q + + @quiet.setter + def quiet(self, val: int) -> None: + self.q = val + + @property + def width(self) -> int: + return self.w + + @width.setter + def width(self, val: int) -> None: + self.w = val + + @property + def height(self) -> int: + return self.h + + @height.setter + def height(self, val: int) -> None: + self.h = val + + @property + def left_edge(self) -> int: + return self.x + + @left_edge.setter + def left_edge(self, val: int) -> None: + self.x = val + + @property + def top_edge(self) -> int: + return self.y + + @top_edge.setter + def top_edge(self, val: int) -> None: + self.y = val + + @property + def cursor_movement(self) -> GRT_C: + return self.C + + @cursor_movement.setter + def cursor_movement(self, val: GRT_C) -> None: + self.C = val + + @property + def compose_mode(self) -> GRT_C: + return self.C + + @compose_mode.setter + def compose_mode(self, val: GRT_C) -> None: + self.C = val + + @property + def cell_x_offset(self) -> int: + return self.X + + @cell_x_offset.setter + def cell_x_offset(self, val: int) -> None: + self.X = val + + @property + def blend_mode(self) -> int: + return self.X + + @blend_mode.setter + def blend_mode(self, val: int) -> None: + self.X = val + + @property + def cell_y_offset(self) -> int: + return self.Y + + @cell_y_offset.setter + def cell_y_offset(self, val: int) -> None: + self.Y = val + + @property + def bgcolor(self) -> int: + return self.Y + + @bgcolor.setter + def bgcolor(self, val: int) -> None: + self.Y = val + + @property + def data_width(self) -> int: + return self.s + + @data_width.setter + def data_width(self, val: int) -> None: + self.s = val + + @property + def animation_state(self) -> int: + return self.s + + @animation_state.setter + def animation_state(self, val: int) -> None: + self.s = val + + @property + def data_height(self) -> int: + return self.v + + @data_height.setter + def data_height(self, val: int) -> None: + self.v = val + + @property + def loop_count(self) -> int: + return self.v + + @loop_count.setter + def loop_count(self, val: int) -> None: + self.v = val + + @property + def num_lines(self) -> int: + return self.r + + @num_lines.setter + def num_lines(self, val: int) -> None: + self.r = val + + @property + def frame_number(self) -> int: + return self.r + + @frame_number.setter + def frame_number(self, val: int) -> None: + self.r = val + + @property + def num_cells(self) -> int: + return self.c + + @num_cells.setter + def num_cells(self, val: int) -> None: + self.c = val + + @property + def other_frame_number(self) -> int: + return self.c + + @other_frame_number.setter + def other_frame_number(self, val: int) -> None: + self.c = val + + @property + def z_index(self) -> int: + return self.z + + @z_index.setter + def z_index(self, val: int) -> None: + self.z = val + + @property + def gap(self) -> int: + return self.z + + @gap.setter + def gap(self, val: int) -> None: + self.z = val def __repr__(self) -> str: return self.serialize().decode('ascii').replace('\033', '^]') @@ -359,6 +621,23 @@ class GraphicsCommand: defval: Union[str, None, int] = getattr(GraphicsCommand, k) setattr(self, k, defval) + def iter_transmission_chunks(self, data: bytes, level: int = -1, compression_threshold: int = 1024) -> Iterator[bytes]: + gc = self.clone() + gc.S = len(data) + if level and len(data) >= compression_threshold: + import zlib + compressed = zlib.compress(data, level) + if len(compressed) < len(data): + gc.o = 'z' + data = compressed + gc.S = len(data) + data = standard_b64encode(data) + while data: + chunk, data = data[:4096], data[4096:] + gc.m = 1 if data else 0 + yield gc.serialize(chunk) + gc.clear() + class Placement: cmd: GraphicsCommand diff --git a/kitty/rgb.py b/kitty/rgb.py index 881ee56f8..53733878e 100644 --- a/kitty/rgb.py +++ b/kitty/rgb.py @@ -8,9 +8,9 @@ from typing import Optional, NamedTuple class Color(NamedTuple): - red: int - green: int - blue: int + red: int = 0 + green: int = 0 + blue: int = 0 def luminance(self) -> float: return 0.299 * self.red + 0.587 * self.green + 0.114 * self.blue @@ -25,6 +25,11 @@ class Color(NamedTuple): def __int__(self) -> int: return self.red << 16 | self.green << 8 | self.blue + def as_bytearray(self, alpha: Optional[int] = None) -> bytearray: + if alpha is None: + return bytearray((self.red, self.green, self.blue)) + return bytearray((self.red, self.green, self.blue, alpha)) + def alpha_blend_channel(top_color: int, bottom_color: int, alpha: float) -> int: return int(alpha * top_color + (1 - alpha) * bottom_color) diff --git a/kitty/typing.py b/kitty/typing.py index e2b0c170e..62db96ba2 100644 --- a/kitty/typing.py +++ b/kitty/typing.py @@ -11,7 +11,7 @@ AddressFamily = PopenType = Socket = StartupCtx = None SessionTab = SessionType = LayoutType = SpecialWindowInstance = None MarkType = RemoteCommandType = CoreTextFont = FontConfigPattern = None KeyEventType = ImageManagerType = KittyCommonOpts = HandlerType = None -GRT_t = GRT_a = GRT_d = GRT_f = GRT_m = GRT_o = None +GRT_t = GRT_a = GRT_d = GRT_f = GRT_m = GRT_o = GRT_C = None ScreenSize = KittensKeyActionType = MouseEvent = AbstractEventLoop = None TermManagerType = LoopType = Debug = GraphicsCommandType = None diff --git a/kitty/typing.pyi b/kitty/typing.pyi index 2e325dbde..19a20aa3d 100644 --- a/kitty/typing.pyi +++ b/kitty/typing.pyi @@ -37,11 +37,12 @@ from .window import Window as WindowType EdgeLiteral = Literal['left', 'top', 'right', 'bottom'] MatchType = Literal['mime', 'ext', 'protocol', 'file', 'path', 'url', 'fragment_matches'] PowerlineStyle = Literal['angled', 'slanted', 'round'] -GRT_a = Literal['t', 'T', 'q', 'p', 'd', 'f', 'a'] +GRT_a = Literal['t', 'T', 'q', 'p', 'd', 'f', 'a', 'c', 'q'] GRT_f = Literal[24, 32, 100] GRT_t = Literal['d', 'f', 't', 's'] GRT_o = Literal['z'] GRT_m = Literal[0, 1] +GRT_C = Literal[0, 1] GRT_d = Literal['a', 'A', 'c', 'C', 'i', 'I', 'p', 'P', 'q', 'Q', 'x', 'X', 'y', 'Y', 'z', 'Z', 'f', 'F']