diff options
author | mark-dawn <albama92@gmail.com> | 2018-02-20 02:46:42 +0100 |
---|---|---|
committer | mark-dawn <albama92@gmail.com> | 2018-05-29 10:06:08 +0200 |
commit | 5bde5d533557968deda1e37db49d9d8650925786 (patch) | |
tree | 0313fe83f303213c0c6d71703f2bcfbc8ebdee40 | |
parent | 3c428cad7e752c38a282ba1c83190eb41fc4ff3a (diff) | |
download | ranger-5bde5d533557968deda1e37db49d9d8650925786.tar.gz |
Automatic network detection, python2 and Exception fixes
Created late_init method to handle task dependent on late facilities Enabled automatic network detection (required stdin to be properly init) Fixed proper handling of import Errors (required ranger Exception handling) Fixed handling of binary stdio differnces between py2/3 Unified said handling aross the module
-rw-r--r-- | doc/ranger.pod | 7 | ||||
-rw-r--r-- | ranger/config/rc.conf | 12 | ||||
-rw-r--r-- | ranger/container/settings.py | 4 | ||||
-rw-r--r-- | ranger/core/fm.py | 2 | ||||
-rw-r--r-- | ranger/ext/img_display.py | 110 |
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 |