summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorhut <hut@lavabit.com>2013-04-24 22:30:59 +0200
committerhut <hut@lavabit.com>2013-04-24 22:30:59 +0200
commit0b2f84884b987cd53973c9416ba8ae5d77e5e714 (patch)
treee204f741b41a47b5b32bd4cfaa53f6f3cc85e4fe
parent89413ffe6da4f6ebc7663a678eb91f3cd6f1f374 (diff)
downloadranger-0b2f84884b987cd53973c9416ba8ae5d77e5e714.tar.gz
ext.img_display: more efficient way to draw images
Instead of loading up w3mimgdisplay for each image and killing it
afterwards, we just open it once for the first image and keep it open.
It can receive any number of commands, so we can just keep writing them
into stdin.  Perhaps it even caches the images to save time, I didn't
test that yet.
-rw-r--r--ranger/core/fm.py3
-rw-r--r--ranger/ext/img_display.py164
-rw-r--r--ranger/gui/widgets/pager.py18
3 files changed, 101 insertions, 84 deletions
diff --git a/ranger/core/fm.py b/ranger/core/fm.py
index 918d1de2..b0d34359 100644
--- a/ranger/core/fm.py
+++ b/ranger/core/fm.py
@@ -19,6 +19,7 @@ from ranger.container.tags import Tags
 from ranger.gui.ui import UI
 from ranger.container.bookmarks import Bookmarks
 from ranger.core.runner import Runner
+from ranger.ext.img_display import ImageDisplayer
 from ranger.ext.rifle import Rifle
 from ranger.container.directory import Directory
 from ranger.ext.signals import SignalDispatcher
@@ -47,6 +48,7 @@ class FM(Actions, SignalDispatcher):
         self.start_paths = paths
         self.directories = dict()
         self.log = deque(maxlen=20)
+        self.image_displayer = ImageDisplayer()
         self.bookmarks = bookmarks
         self.current_tab = 1
         self.tabs = {}
@@ -329,6 +331,7 @@ class FM(Actions, SignalDispatcher):
             raise SystemExit
 
         finally:
+            self.image_displayer.quit()
             if ranger.arg.choosedir and self.thisdir and self.thisdir.path:
                 # XXX: UnicodeEncodeError: 'utf-8' codec can't encode character
                 # '\udcf6' in position 42: surrogates not allowed
diff --git a/ranger/ext/img_display.py b/ranger/ext/img_display.py
index b6f59f4f..3d710837 100644
--- a/ranger/ext/img_display.py
+++ b/ranger/ext/img_display.py
@@ -10,7 +10,12 @@ framebuffer) or in a Xorg session.
 w3m need to be installed for this to work.
 """
 
-import termios, fcntl, struct, sys, os
+import fcntl
+import os
+import select
+import struct
+import sys
+import termios
 from subprocess import Popen, PIPE
 
 W3MIMGDISPLAY_PATH = '/usr/lib/w3m/w3mimgdisplay'
@@ -19,6 +24,91 @@ W3MIMGDISPLAY_OPTIONS = []
 class ImgDisplayUnsupportedException(Exception):
     pass
 
+
+class ImageDisplayer(object):
+    is_initialized = False
+
+    def initialize(self):
+        """start w3mimgdisplay"""
+        self.is_initialized = True
+        self.binary_path = os.environ.get("W3MIMGDISPLAY_PATH", None)
+        if not self.binary_path:
+            self.binary_path = W3MIMGDISPLAY_PATH
+        self.process = Popen([self.binary_path] + W3MIMGDISPLAY_OPTIONS,
+                stdin=PIPE, stdout=PIPE, universal_newlines=True)
+
+    def draw(self, path, start_x, start_y, width, height):
+        """Draw an image at the given coordinates."""
+        if not self.is_initialized or self.process.poll() is not None:
+            self.initialize()
+        self.process.stdin.write(self._generate_w3m_input(path, start_x,
+            start_y, width, height))
+        self.process.stdin.flush()
+        self.process.stdout.readline()
+
+    def clear(self, start_x, start_y, width, height):
+        """Clear a part of terminal display."""
+        if not self.is_initialized or self.process.poll() is not None:
+            self.initialize()
+
+        fontw, fonth = _get_font_dimensions()
+
+        cmd = "6;{x};{y};{w};{h}\n4;\n3;\n".format(
+                x = start_x * fontw,
+                y = start_y * fonth,
+                w = (width + 1) * fontw,
+                h = height * fonth)
+
+        self.process.stdin.write(cmd)
+        self.process.stdin.flush()
+        self.process.stdout.readline()
+
+    def _generate_w3m_input(self, path, start_x, start_y, max_width, max_height):
+        """Prepare the input string for w3mimgpreview
+
+        start_x, start_y, max_height and max_width specify the drawing area.
+        They are expressed in number of characters.
+        """
+        fontw, fonth = _get_font_dimensions()
+        if fontw == 0 or fonth == 0:
+            raise ImgDisplayUnsupportedException()
+
+        max_width_pixels = max_width * fontw
+        max_height_pixels = max_height * fonth
+
+        # get image size
+        cmd = "5;{}\n".format(path)
+
+        self.process.stdin.write(cmd)
+        self.process.stdin.flush()
+        output = self.process.stdout.readline().split()
+
+        if len(output) != 2:
+            raise Exception('Failed to execute w3mimgdisplay', output)
+
+        width = int(output[0])
+        height = int(output[1])
+
+        # get the maximum image size preserving ratio
+        if width > max_width_pixels:
+            height = (height * max_width_pixels) // width
+            width = max_width_pixels
+        if height > max_height_pixels:
+            width = (width * max_height_pixels) // height
+            height = max_height_pixels
+
+        return "0;1;{x};{y};{w};{h};;;;;{filename}\n4;\n3;\n".format(
+                x = start_x * fontw,
+                y = start_y * fonth,
+                w = width,
+                h = height,
+                filename = path)
+
+    def quit(self):
+        if self.is_initialized:
+            self.process.kill()
+
+
 def _get_font_dimensions():
     # Get the height and width of a character displayed in the terminal in
     # pixels.
@@ -28,75 +118,3 @@ def _get_font_dimensions():
     rows, cols, xpixels, ypixels = struct.unpack("HHHH", x)
 
     return (xpixels // cols), (ypixels // rows)
-
-
-def _w3mimgdisplay(commands):
-    """Invoke w3mimgdisplay and send commands on its standard input."""
-    path = os.environ.get("W3MIMGDISPLAY_PATH", None)
-    if not path:
-        path = W3MIMGDISPLAY_PATH
-    process = Popen([path] + W3MIMGDISPLAY_OPTIONS, stdin=PIPE,
-            stdout=PIPE, universal_newlines=True)
-
-    # wait for the external program to finish
-    output, _ = process.communicate(input=commands)
-
-    return output
-
-def generate_w3m_input(path, start_x, start_y, max_width, max_height):
-    """Prepare the input string for w3mimgpreview
-
-    start_x, start_y, max_height and max_width specify the drawing area.
-    They are expressed in number of characters.
-    """
-    fontw, fonth = _get_font_dimensions()
-    if fontw == 0 or fonth == 0:
-        raise ImgDisplayUnsupportedException()
-
-    max_width_pixels = max_width * fontw
-    max_height_pixels = max_height * fonth
-
-    # get image size
-    cmd = "5;{}".format(path)
-    output = _w3mimgdisplay(cmd).split()
-
-    if len(output) != 2:
-        raise Exception('Failed to execute w3mimgdisplay')
-
-    width = int(output[0])
-    height = int(output[1])
-
-    # get the maximum image size preserving ratio
-    if width > max_width_pixels:
-        height = (height * max_width_pixels) // width
-        width = max_width_pixels
-    if height > max_height_pixels:
-        width = (width * max_height_pixels) // height
-        height = max_height_pixels
-
-    return "0;1;{x};{y};{w};{h};;;;;{filename}\n4;\n3;".format(
-            x = start_x * fontw,
-            y = start_y * fonth,
-            w = width,
-            h = height,
-            filename = path)
-
-def draw(path, start_x, start_y, max_width, max_height):
-    """Draw an image file in the terminal.
-
-    start_x, start_y, max_height and max_width specify the drawing area.
-    They are expressed in number of characters.
-    """
-    _w3mimgdisplay(generate_w3m_input(path, start_x, start_y, max_width, max_height))
-
-def clear(start_x, start_y, width, height):
-    """Clear a part of terminal display."""
-    fontw, fonth = _get_font_dimensions()
-
-    cmd = "6;{x};{y};{w};{h}\n4;\n3;".format(
-            x = start_x * fontw,
-            y = start_y * fonth,
-            w = (width + 1) * fontw,
-            h = height * fonth)
-
-    _w3mimgdisplay(cmd)
diff --git a/ranger/gui/widgets/pager.py b/ranger/gui/widgets/pager.py
index 4a98e083..0b9071d7 100644
--- a/ranger/gui/widgets/pager.py
+++ b/ranger/gui/widgets/pager.py
@@ -8,7 +8,7 @@ from . import Widget
 from ranger.core.loader import CommandLoader
 from ranger.gui import ansi
 from ranger.ext.direction import Direction
-import ranger.ext.img_display as img_display
+from ranger.ext.img_display import ImgDisplayUnsupportedException
 
 # TODO: Scrolling in embedded pager
 class Pager(Widget):
@@ -40,7 +40,7 @@ class Pager(Widget):
 
     def clear_image(self, force=False):
         if (force or self.need_clear_image) and self.image_drawn:
-            img_display.clear(self.x, self.y, self.wid, self.hei)
+            self.fm.image_displayer.clear(self.x, self.y, self.wid, self.hei)
             self.need_clear_image = False
             self.image_drawn = False
 
@@ -90,18 +90,14 @@ class Pager(Widget):
             self.source = None
             self.need_redraw_image = False
             try:
-                cmd = CommandLoader([img_display.W3MIMGDISPLAY_PATH] +
-                            img_display.W3MIMGDISPLAY_OPTIONS,
-                        input=img_display.generate_w3m_input(self.image,
-                            self.x, self.y, self.wid, self.hei),
-                        descr="loading preview image",
-                        silent=True, kill_on_pause=True)
-                self.fm.loader.add(cmd)
-                self.image_drawn = True
-            except img_display.ImgDisplayUnsupportedException:
+                self.fm.image_displayer.draw(self.image, self.x, self.y,
+                        self.wid, self.hei)
+            except ImgDisplayUnsupportedException:
                 self.fm.settings.preview_images = False
             except Exception as e:
                 self.fm.notify(e, bad=True)
+            else:
+                self.image_drawn = True
 
     def _draw_line(self, i, line):
         if self.markup is None: