about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--doc/ranger.pod7
-rw-r--r--ranger/config/rc.conf12
-rw-r--r--ranger/container/settings.py4
-rw-r--r--ranger/core/fm.py2
-rw-r--r--ranger/ext/img_display.py110
5 files changed, 70 insertions, 65 deletions
diff --git a/doc/ranger.pod b/doc/ranger.pod
index a45ab2e4..887f704d 100644
--- a/doc/ranger.pod
+++ b/doc/ranger.pod
@@ -244,14 +244,11 @@ To enable this feature, set the option C<preview_images_method> to urxvt-full.
 =head3 kitty
 
 This only works on Kitty. It requires PIL (or pillow) to work.
+It is able to automatically work over network,
+by switching to a slower transfer method.
 
 To enable this feature, set the option C<preview_images_method> to kitty.
 
-=head3 kitty-network
-
-The same as kitty, but uses a streaming method to allow previews remotely,
-for example in an ssh session. However the system is more computationally taxing.
-
 =head2 SELECTION
 
 The I<selection> is defined as "All marked files IF THERE ARE ANY, otherwise
diff --git a/ranger/config/rc.conf b/ranger/config/rc.conf
index 7adbc239..305143c1 100644
--- a/ranger/config/rc.conf
+++ b/ranger/config/rc.conf
@@ -101,15 +101,11 @@ set preview_images false
 # * kitty:
 #   Preview images in full color using kitty image protocol.
 #   Requires python PIL or pillow library.
+#   If ranger does not share the local filesystem with kitty
+#   the transfer method is switched to encoding the whole image in
+#   the protocol that, while slower, allows remote previews,
+#   for example during an ssh session.
 #   Tmux support is untested.
-#
-# * kitty-network
-#   Similar to base kitty, but instead of local storage it streams the whole image
-#   over standard input. More error prone,
-#   and more intensive since it base64 encodes entire images.
-#   However it makes possible to see previews from ranger over the network,
-#   so it makes sense to enable this on remote machines.
-#   Note that has been untested over an actual network.
 set preview_images_method w3m
 
 # Default iTerm2 font size (see: preview_images_method: iterm2)
diff --git a/ranger/container/settings.py b/ranger/container/settings.py
index b360cf20..b9695301 100644
--- a/ranger/container/settings.py
+++ b/ranger/container/settings.py
@@ -100,8 +100,8 @@ ALLOWED_VALUES = {
     'confirm_on_delete': ['multiple', 'always', 'never'],
     'line_numbers': ['false', 'absolute', 'relative'],
     'one_indexed': [False, True],
-    'preview_images_method': ['w3m', 'iterm2', 'terminology', 'urxvt',
-                              'urxvt-full', 'kitty', 'kitty-network'],
+    'preview_images_method': ['w3m', 'iterm2', 'terminology',
+                              'urxvt', 'urxvt-full', 'kitty'],
     'vcs_backend_bzr': ['disabled', 'local', 'enabled'],
     'vcs_backend_git': ['enabled', 'disabled', 'local'],
     'vcs_backend_hg': ['disabled', 'local', 'enabled'],
diff --git a/ranger/core/fm.py b/ranger/core/fm.py
index 5f4e141d..f3a19f0a 100644
--- a/ranger/core/fm.py
+++ b/ranger/core/fm.py
@@ -238,8 +238,6 @@ class FM(Actions,  # pylint: disable=too-many-instance-attributes
             return URXVTImageFSDisplayer()
         elif self.settings.preview_images_method == "kitty":
             return KittyImageDisplayer()
-        elif self.settings.preview_images_method == "kitty-network":
-            return KittyImageDisplayer(stream=True)
         return ImageDisplayer()
 
     def _get_thisfile(self):
diff --git a/ranger/ext/img_display.py b/ranger/ext/img_display.py
index f444e0f9..387d61a3 100644
--- a/ranger/ext/img_display.py
+++ b/ranger/ext/img_display.py
@@ -57,10 +57,9 @@ def temporarily_moved_cursor(to_y, to_x):
 # this is excised since Terminology needs to move the cursor multiple times
 def move_cur(to_y, to_x):
     tparm = curses.tparm(curses.tigetstr("cup"), to_y, to_x)
-    if sys.version_info[0] < 3:
-        sys.stdout.write(tparm)
-    else:
-        sys.stdout.buffer.write(tparm)
+    # on python2 stdout is already in binary mode, in python3 is accessed via buffer
+    bin_stdout = getattr(sys.stdout, 'buffer', sys.stdout)
+    bin_stdout.write(tparm)
 
 
 class ImageDisplayError(Exception):
@@ -479,60 +478,72 @@ class KittyImageDisplayer(ImageDisplayer):
         https://github.com/kovidgoyal/kitty/blob/master/graphics-protocol.asciidoc"""
     protocol_start = b'\033_G'
     protocol_end = b'\033\\'
+    # we are going to use stdio in binary mode a lot, so due to py2 -> py3
+    # differnces is worth to do this:
+    stdbout = getattr(sys.stdout, 'buffer', sys.stdout)
+    stdbin = getattr(sys.stdin, 'buffer', sys.stdin)
+    # counter for image ids on kitty's end
+    image_id = 0
+    # we need to find out the encoding for a path string, ascii won't cut it
+    try:
+        fsenc = sys.getfilesystemencoding()  # returns None if standard utf-8 is used
+        # throws LookupError if can't find the codec, TypeError if fsenc is None
+        codecs.lookup(fsenc)
+    except (LookupError, TypeError):
+        fsenc = 'utf-8'
 
-    def __init__(self, stream=False):
-        self.image_id = 0
+    def __init__(self):
         self.temp_paths = []
-        # parameter deciding if we're going to send the picture data
-        # in the command body, or save it to a temporary file
-        self.stream = stream
 
         if "screen" in os.environ['TERM']:
             # TODO: probably need to modify the preamble
             pass
 
-        # we need to find out the encoding for a path string, ascii won't cut it
-        try:
-            self.fsenc = sys.getfilesystemencoding()  # returns None if standard utf-8 is used
-            # throws LookupError if can't find the codec, TypeError if fsenc is None
-            codecs.lookup(self.fsenc)
-        except (LookupError, TypeError):
-            self.fsenc = 'utf-8'
+        # the rest of the initializations that require reading stdio or raising exceptions
+        # are delayed to the first draw call, since curses
+        # and ranger exception handler are not online at __init__() time
+        self.needs_late_init = True
+        # to init in _late_init()
+        self.backend = None
+        self.stream = None
 
+    def _late_init(self):
         # automatic check if we share the filesystem using a dummy file
-        # TODO: this doesn't work somehow, the response from kitty appears on
-        # the tty, and until a newline is inserted the data won't be sent
-        # to the stdin we have. This works just fine in draw. Something tells me that since this is
-        # called early the tubes are not set up correctly yet, but I have no idea how to fix it
-        #
-        # with NamedTemporaryFile() as tmpf:
-        #     tmpf.write(bytearray([0xFA]*3))
-        #     tmpf.flush()
-        #     for cmd in self._format_cmd_str({'i': 1, 'f': 24,'t': 'f', 's': 1, 'v': 1, 'S': 3},
-        #             payload=base64.standard_b64encode(tmpf.name.encode(self.fsenc))):
-        #         sys.stdout.buffer.write(cmd)
-        #     resp = [b'']
-        #     sys.stdout.flush()
-        #     for _ in range(5):
-        #         while resp[-1] != b'\\':
-        #             resp.append(sys.stdin.buffer.read(1))
-        # if b''.join(resp[-4:-2]) == b'OK':
-        #     self.stream = False
-        # else:
-        #     self.stream = True
+        with NamedTemporaryFile() as tmpf:
+            tmpf.write(bytearray([0xFF] * 3))
+            tmpf.flush()
+            for cmd in self._format_cmd_str(
+                    {'a': 'q', 'i': 1, 'f': 24, 't': 'f', 's': 1, 'v': 1, 'S': 3},
+                    payload=base64.standard_b64encode(tmpf.name.encode(self.fsenc))):
+                self.stdbout.write(cmd)
+            sys.stdout.flush()
+            resp = [b'']
+            while resp[-1] != b'\\':
+                resp.append(self.stdbin.read(1))
+        # set the transfer method based on the response
+        resp = str(b''.join(resp))
+        if resp.find('OK') != -1:
+            self.stream = False
+        elif resp.find('EBADF') != -1:
+            self.stream = True
+        else:
+            raise ImgDisplayUnsupportedException(
+                'kitty replied an unexpected response: {}'
+                .format(resp))
 
-        self.backend = None
+        # get the image manipulation backend
         try:
             # pillow is the default since we are not going
             # to spawn other processes, so it _should_ be faster
             import PIL.Image
             self.backend = PIL.Image
-            self.filter = PIL.Image.LANCZOS
         except ImportError:
-            sys.stderr.write("PIL not Found")
+            raise ImageDisplayError("Images previews in kitty require PIL (pillow)")
             # TODO: implement a wrapper class for Imagemagick process to
             # replicate the functionality we use from im
 
+        self.needs_late_init = False
+
     def draw(self, path, start_x, start_y, width, height):  # pylint: disable=too-many-locals
         self.image_id += 1
         # dictionary to store the command arguments for kitty
@@ -541,7 +552,10 @@ class KittyImageDisplayer(ImageDisplayer):
         cmds = {'a': 'T', 'i': self.image_id}
         # sys.stderr.write('{}-{}@{}x{}\t'.format(start_x, start_y, width, height))
 
-        assert self.backend is not None  # sanity check if we actually have a backend
+        # finish initialization if it is the first call
+        if self.needs_late_init:
+            self._late_init()
+
         image = self.backend.open(path)
         # since kitty streches the image to fill the view box
         # we need to resize the box to not get distortion
@@ -556,10 +570,10 @@ class KittyImageDisplayer(ImageDisplayer):
             width = int(new_w)
 
         # resize image to a smaller size. Ideally this should be
-        # image = self._resize_max_area(image, (480*960), self.filter)
+        # image = self._resize_max_area(image, (), self.backend.LANCZOS)
 
         if self.stream:
-            image = self._resize_max_area(image, (480 * 960), self.filter)
+            image = self._resize_max_area(image, (480 * 960), self.backend.LANCZOS)
             # encode the whole image as base64
             # TODO: implement z compression
             # to possibly increase resolution in sent image
@@ -589,15 +603,15 @@ class KittyImageDisplayer(ImageDisplayer):
 
         with temporarily_moved_cursor(start_y, start_x):
             for cmd_str in self._format_cmd_str(cmds, payload=payload):
-                sys.stdout.buffer.write(cmd_str)
+                self.stdbout.write(cmd_str)
         # catch kitty answer before the escape codes corrupt the console
         resp = [b'']
         while resp[-1] != b'\\':
-            resp.append(sys.stdin.buffer.read(1))
-        if b''.join(resp[-4:-2]) == b'OK':
+            resp.append(self.stdbin.read(1))
+        if str(b''.join(resp)).find('OK'):
             return
         else:
-            raise ImageDisplayError
+            raise ImageDisplayError('kitty replied "{}"'.format(b''.join(resp)))
 
     def clear(self, start_x, start_y, width, height):
         # let's assume that every time ranger call this
@@ -605,8 +619,8 @@ class KittyImageDisplayer(ImageDisplayer):
         # TODO: implement this using the actual x, y, since the protocol supports it
         cmds = {'a': 'd', 'i': self.image_id}
         for cmd_str in self._format_cmd_str(cmds):
-            sys.stdout.buffer.write(cmd_str)
-        sys.stdout.flush()
+            self.stdbout.write(cmd_str)
+        self.stdbout.flush()
         # kitty doesn't seem to reply on deletes, checking like we do in draw()
         # will slows down scrolling with timeouts from select
         self.image_id -= 1