Skip to content

Support kitty protocol for showing app icons #195

Description

@tosbaha

Currently, only iTerm2's proprietary format is supported to show icons when you usethe frida-ps command. Modern terminals such as ghostty uses kitty image protocol. By using Claude, I added the kitty image protocol. Git diff is attached.

diff --git a/env/lib/python3.13/site-packages/frida_tools/ps_old.py b/env/lib/python3.13/site-packages/frida_tools/ps.py
index cbdef69..b63b838 100644
--- a/env/lib/python3.13/site-packages/frida_tools/ps_old.py
+++ b/env/lib/python3.13/site-packages/frida_tools/ps.py
@@ -3,6 +3,7 @@ def main() -> None:
     import functools
     import json
     import math
+    import os
     import platform
     import sys
     from base64 import b64encode
@@ -72,7 +73,8 @@ def main() -> None:
                 self._list_processes()
 
         def _list_processes(self) -> None:
-            if not self._exclude_icons and self._output_format == "text" and self._terminal_type == "iterm2":
+            supports_icons = self._terminal_type in ("iterm2", "ghostty")
+            if not self._exclude_icons and self._output_format == "text" and supports_icons:
                 scope = "full"
             else:
                 scope = "minimal"
@@ -121,7 +123,8 @@ def main() -> None:
             self._exit(0)
 
         def _list_applications(self) -> None:
-            if not self._exclude_icons and self._output_format == "text" and self._terminal_type == "iterm2":
+            supports_icons = self._terminal_type in ("iterm2", "ghostty")
+            if not self._exclude_icons and self._output_format == "text" and supports_icons:
                 scope = "full"
             else:
                 scope = "minimal"
@@ -191,14 +194,81 @@ def main() -> None:
             self._exit(0)
 
         def _render_icon(self, icon) -> str:
+            if self._terminal_type == "ghostty":
+                return self._render_icon_kitty(icon)
+            # iTerm2 inline image protocol
             return "\033]1337;File=inline=1;width={}px;height={}px;:{}\007".format(
                 self._icon_size, self._icon_size, b64encode(icon["image"]).decode("ascii")
             )
 
+        def _render_icon_kitty(self, icon) -> str:
+            image_data = b64encode(icon["image"]).decode("ascii")
+            # Split payload into ≤4096-byte chunks as required by the protocol.
+            chunk_size = 4096
+            chunks = [image_data[i:i + chunk_size] for i in range(0, len(image_data), chunk_size)]
+
+            result = ""
+            for idx, chunk in enumerate(chunks):
+                is_last = idx == len(chunks) - 1
+                more = 0 if is_last else 1
+                if idx == 0:
+                    # First chunk: include all display parameters.
+                    # a=T  – transmit and display immediately
+                    # f=100 – PNG format
+                    # c=1,r=1 – occupy exactly 1 column × 1 row in the grid
+                    # m=<more> – continuation flag
+                    header = f"a=T,f=100,c=2,r=1,m={more}"
+                else:
+                    header = f"a=T,m={more}"
+                result += f"\033_G{header};{chunk}\033\\"
+
+            return result
+
         def _detect_terminal(self) -> Tuple[str, int]:
             icon_size = 0
 
-            if not self._have_terminal or self._plain_terminal or platform.system() != "Darwin":
+            if not self._have_terminal or self._plain_terminal:
+                return ("simple", icon_size)
+
+            # ------------------------------------------------------------------
+            # Ghostty detection: it sets TERM_PROGRAM=ghostty and supports the
+            # Kitty graphics protocol.  We query the cell size in pixels with
+            # the XTWINOPS escape \033[16t which returns \033[6;<h>;<w>t so we
+            # can compute a reasonable icon_size (one cell tall).
+            # Ghostty runs on Linux and macOS so we skip the Darwin-only guard.
+            # ------------------------------------------------------------------
+            if os.environ.get("TERM_PROGRAM") == "ghostty":
+                try:
+                    fd = sys.stdin.fileno()
+                    old_attributes = termios.tcgetattr(fd)
+                    try:
+                        tty.setraw(fd)
+                        new_attributes = termios.tcgetattr(fd)
+                        new_attributes[3] = new_attributes[3] & ~termios.ICANON & ~termios.ECHO
+                        termios.tcsetattr(fd, termios.TCSANOW, new_attributes)
+
+                        # Query cell size in pixels: response is ESC[6;<height>;<width>t
+                        sys.stdout.write("\033[16t")
+                        sys.stdout.flush()
+                        cell_response = self._read_terminal_response("t")
+                        # cell_response looks like "6;<height>;<width>"
+                        parts = cell_response.split(";")
+                        if len(parts) >= 2:
+                            cell_height_px = int(parts[1])
+                            icon_size = max(cell_height_px, 16)  # at least 16 px
+                        else:
+                            icon_size = 16
+                    finally:
+                        termios.tcsetattr(fd, termios.TCSANOW, old_attributes)
+                except Exception:
+                    icon_size = 16  # safe fallback
+
+                return ("ghostty", icon_size)
+
+            # ------------------------------------------------------------------
+            # Original iTerm2 detection (Darwin only)
+            # ------------------------------------------------------------------
+            if platform.system() != "Darwin":
                 return ("simple", icon_size)
 
             fd = sys.stdin.fileno()
@@ -291,4 +361,4 @@ if __name__ == "__main__":
     try:
         main()
     except KeyboardInterrupt:
-        pass
+        pass
\ No newline at end of file

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions