diff options
-rw-r--r-- | ranger/core/fm.py | 4 | ||||
-rw-r--r-- | ranger/core/main.py | 2 | ||||
-rw-r--r-- | ranger/ext/img_display.py | 74 |
3 files changed, 35 insertions, 45 deletions
diff --git a/ranger/core/fm.py b/ranger/core/fm.py index f3a19f0a..61b3cb11 100644 --- a/ranger/core/fm.py +++ b/ranger/core/fm.py @@ -428,5 +428,5 @@ class FM(Actions, # pylint: disable=too-many-instance-attributes if not ranger.args.clean and self.settings.save_tabs_on_exit and len(self.tabs) > 1: with open(self.datapath('tabs'), 'a') as fobj: # Don't save active tab since launching ranger changes the active tab - fobj.write('\0'.join(v.path for t, v in self.tabs.items()) + - '\0\0') + fobj.write('\0'.join(v.path for t, v in self.tabs.items()) + + '\0\0') diff --git a/ranger/core/main.py b/ranger/core/main.py index b5d0af77..598ce243 100644 --- a/ranger/core/main.py +++ b/ranger/core/main.py @@ -363,7 +363,7 @@ def load_settings( # pylint: disable=too-many-locals,too-many-branches,too-many # Load custom commands def import_file(name, path): # From https://stackoverflow.com/a/67692 - # pragma pylint: disable=no-name-in-module,import-error,no-member + # pragma pylint: disable=no-name-in-module,import-error,no-member, deprecated-method if sys.version_info >= (3, 5): import importlib.util as util spec = util.spec_from_file_location(name, path) diff --git a/ranger/ext/img_display.py b/ranger/ext/img_display.py index 49bbc200..ba2f6f4d 100644 --- a/ranger/ext/img_display.py +++ b/ranger/ext/img_display.py @@ -11,7 +11,6 @@ implementations, which are currently w3m, iTerm2 and urxvt. from __future__ import (absolute_import, division, print_function) -import math import base64 import curses import errno @@ -468,7 +467,7 @@ class URXVTImageFSDisplayer(URXVTImageDisplayer): class KittyImageDisplayer(ImageDisplayer): """Implementation of ImageDisplayer for kitty (https://github.com/kovidgoyal/kitty/) - terminal. It uses the built APC to send commands and data to kitty, + terminal. It uses the built APC to send commands and data to kitty, which in turn renders the image. The APC takes the form '\033_Gk=v,k=v...;bbbbbbbbbbbbbb\033\\' | ---------- -------------- | @@ -477,8 +476,8 @@ class KittyImageDisplayer(ImageDisplayer): key: value pairs as parameters For more info please head over to : https://github.com/kovidgoyal/kitty/blob/master/graphics-protocol.asciidoc""" - protocol_start = b'\033_G' - protocol_end = b'\033\\' + protocol_start = b'\x1b_G' + protocol_end = b'\x1b\\' # 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) @@ -494,8 +493,6 @@ class KittyImageDisplayer(ImageDisplayer): fsenc = 'utf-8' def __init__(self): - self.temp_paths = [] - # 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 @@ -507,12 +504,15 @@ class KittyImageDisplayer(ImageDisplayer): def _late_init(self): # tmux - if "screen" in os.environ['TERM']: + if 'kitty' not in os.environ['TERM']: # this doesn't seem to work, ranger freezes... # commenting out the response check does nothing # self.protocol_start = b'\033Ptmux;\033' + self.protocol_start # self.protocol_end += b'\033\\' - raise ImgDisplayUnsupportedException('tmux support is not implemented with kitty') + raise ImgDisplayUnsupportedException( + 'kitty previews only work in' + + ' kitty and outside tmux. ' + + 'Make sure your TERM contains the string "kitty"') # automatic check if we share the filesystem using a dummy file with NamedTemporaryFile() as tmpf: @@ -523,19 +523,18 @@ class KittyImageDisplayer(ImageDisplayer): 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)) + resp = b'' + while resp[-2:] != self.protocol_end: + resp += self.stdbin.read(1) # set the transfer method based on the response - resp = str(b''.join(resp)) - if resp.find('OK') != -1: + # if resp.find(b'OK') != -1: + if b'OK' in resp: self.stream = False - elif resp.find('EBADF') != -1: + elif b'EBADF' in resp: self.stream = True else: raise ImgDisplayUnsupportedException( - 'kitty replied an unexpected response: {}' - .format(resp)) + 'kitty replied an unexpected response: {}'.format(resp)) # get the image manipulation backend try: @@ -544,7 +543,7 @@ class KittyImageDisplayer(ImageDisplayer): import PIL.Image self.backend = PIL.Image except ImportError: - raise ImageDisplayError("Images previews in kitty require PIL (pillow)") + raise ImageDisplayError("Image previews in kitty require PIL (pillow)") # TODO: implement a wrapper class for Imagemagick process to # replicate the functionality we use from im @@ -552,7 +551,7 @@ class KittyImageDisplayer(ImageDisplayer): ret = fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ, struct.pack('HHHH', 0, 0, 0, 0)) n_cols, n_rows, x_px_tot, y_px_tot = struct.unpack('HHHH', ret) - self.pix_row, self.pix_col = x_px_tot / n_rows, y_px_tot / n_cols + self.pix_row, self.pix_col = x_px_tot // n_rows, y_px_tot // n_cols self.needs_late_init = False def draw(self, path, start_x, start_y, width, height): @@ -566,6 +565,7 @@ class KittyImageDisplayer(ImageDisplayer): # finish initialization if it is the first call if self.needs_late_init: self._late_init() + with warnings.catch_warnings(record=True): # as warn: warnings.simplefilter('ignore', self.backend.DecompressionBombWarning) image = self.backend.open(path) @@ -603,22 +603,21 @@ class KittyImageDisplayer(ImageDisplayer): # the only format except raw RGB(A) bitmap that kitty understand) # c, r: size in cells of the viewbox cmds.update({'t': 't', 'f': 100, }) - with NamedTemporaryFile(prefix='rgr_thumb_', suffix='.png', delete=False) as tmpf: + with NamedTemporaryFile(prefix='ranger_thumb_', suffix='.png', delete=False) as tmpf: image.save(tmpf, format='png', compress_level=0) - self.temp_paths.append(tmpf.name) payload = base64.standard_b64encode(tmpf.name.encode(self.fsenc)) with temporarily_moved_cursor(int(start_y), int(start_x)): for cmd_str in self._format_cmd_str(cmds, payload=payload): self.stdbout.write(cmd_str) # catch kitty answer before the escape codes corrupt the console - resp = [b''] - while resp[-1] != b'\\': - resp.append(self.stdbin.read(1)) - if str(b''.join(resp)).find('OK'): + resp = b'' + while resp[-2:] != self.protocol_end: + resp += self.stdbin.read(1) + if b'OK' in resp: return else: - raise ImageDisplayError('kitty replied "{}"'.format(b''.join(resp))) + raise ImageDisplayError('kitty replied "{}"'.format(resp)) def clear(self, start_x, start_y, width, height): # let's assume that every time ranger call this @@ -632,13 +631,13 @@ class KittyImageDisplayer(ImageDisplayer): # will slows down scrolling with timeouts from select self.image_id -= 1 - def _format_cmd_str(self, cmd, payload=None, max_l=2048): + def _format_cmd_str(self, cmd, payload=None, max_slice_len=2048): central_blk = ','.join(["{}={}".format(k, v) for k, v in cmd.items()]).encode('ascii') if payload is not None: # we add the m key to signal a multiframe communication # appending the end (m=0) key to a single message has no effect - while len(payload) > max_l: - payload_blk, payload = payload[:max_l], payload[max_l:] + while len(payload) > max_slice_len: + payload_blk, payload = payload[:max_slice_len], payload[max_slice_len:] yield self.protocol_start + \ central_blk + b',m=1;' + payload_blk + \ self.protocol_end @@ -648,21 +647,12 @@ class KittyImageDisplayer(ImageDisplayer): else: yield self.protocol_start + central_blk + b';' + self.protocol_end - @staticmethod - def _resize_max_area(image, max_area, img_filter): - aspect = image.width / image.height - area = image.width * image.height - if area > max_area: - image = image.resize((int(math.sqrt(area * aspect)), - int(math.sqrt(area / aspect))), img_filter) - return image - def quit(self): # clear all remaining images, then check if all files went through or are orphaned while self.image_id >= 1: self.clear(0, 0, 0, 0) - while self.temp_paths: - try: - os.remove(self.temp_paths.pop()) - except (OSError, IOError): - continue + # for k in self.temp_paths: + # try: + # os.remove(self.temp_paths[k]) + # except (OSError, IOError): + # continue |