diff options
-rw-r--r-- | ranger/container/settingobject.py | 1 | ||||
-rw-r--r-- | ranger/core/actions.py | 14 | ||||
-rw-r--r-- | ranger/ext/img_display.py | 79 | ||||
-rw-r--r-- | ranger/fsobject/file.py | 5 | ||||
-rw-r--r-- | ranger/gui/ui.py | 1 | ||||
-rw-r--r-- | ranger/gui/widgets/browsercolumn.py | 18 | ||||
-rw-r--r-- | ranger/gui/widgets/pager.py | 46 |
7 files changed, 152 insertions, 12 deletions
diff --git a/ranger/container/settingobject.py b/ranger/container/settingobject.py index 431d26e3..7d715f38 100644 --- a/ranger/container/settingobject.py +++ b/ranger/container/settingobject.py @@ -26,6 +26,7 @@ ALLOWED_SETTINGS = { 'mouse_enabled': bool, 'padding_right': bool, 'preview_directories': bool, + 'preview_images': bool, 'preview_files': bool, 'preview_script': (str, type(None)), 'save_console_history': bool, diff --git a/ranger/core/actions.py b/ranger/core/actions.py index fba254c7..80db1aa5 100644 --- a/ranger/core/actions.py +++ b/ranger/core/actions.py @@ -777,7 +777,10 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): return pager = self.ui.open_embedded_pager() - pager.set_source(self.thisfile.get_preview_source(pager.wid, pager.hei)) + if self.settings.preview_images and self.thisfile.is_image(): + pager.set_image(self.thisfile.realpath) + else: + pager.set_source(self.thisfile.get_preview_source(pager.wid, pager.hei)) # -------------------------- # -- Previews @@ -789,7 +792,14 @@ class Actions(FileManagerAware, EnvironmentAware, SettingsAware): except: return False - def get_preview(self, path, width, height): + def get_preview(self, file, width, height): + pager = self.ui.browser.pager + path = file.realpath + + if self.settings.preview_images and file.is_image(): + pager.set_image(path) + return None + if self.settings.preview_script and self.settings.use_preview_script: # self.previews is a 2 dimensional dict: # self.previews['/tmp/foo.jpg'][(80, 24)] = "the content..." diff --git a/ranger/ext/img_display.py b/ranger/ext/img_display.py new file mode 100644 index 00000000..277d32ce --- /dev/null +++ b/ranger/ext/img_display.py @@ -0,0 +1,79 @@ +# This software is distributed under the terms of the GNU GPL version 3. + +""" +This module provides functions to draw images in the terminal using +w3mimgdisplay, an utilitary program from w3m (a text-based web browser). +w3mimgdisplay can display images either in virtual tty (using linux +framebuffer) or in a Xorg session. + +w3m need to be installed for this to work. +""" + +import termios, fcntl, struct, sys +from subprocess import Popen, PIPE + +W3MIMGDISPLAY_PATH = '/usr/lib/w3m/w3mimgdisplay' + +def _get_font_dimensions(): + """ + Get the height and width of a character displayed in the terminal in + pixels. + """ + s = struct.pack("HHHH", 0, 0, 0, 0) + fd_stdout = sys.stdout.fileno() + x = fcntl.ioctl(fd_stdout, termios.TIOCGWINSZ, s) + 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. + """ + process = Popen(W3MIMGDISPLAY_PATH, stdin=PIPE, stdout=PIPE, + universal_newlines=True) + + # wait for the external program to finish + output, _ = process.communicate(input=commands) + + return output + +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. + """ + fontw, fonth = _get_font_dimensions() + + 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 + + # draw + cmd = "0;1;{x};{y};{w};{h};;;;;{filename}\n4;\n3;".format( + x = start_x * fontw, + y = start_y * fonth, + w = width, + h = height, + filename = path) + _w3mimgdisplay(cmd) diff --git a/ranger/fsobject/file.py b/ranger/fsobject/file.py index 39146cd5..b79c6c7a 100644 --- a/ranger/fsobject/file.py +++ b/ranger/fsobject/file.py @@ -61,6 +61,9 @@ class File(FileSystemObject): return True return False + def is_image(self): + return self.mimetype and self.mimetype.startswith('image/') + def has_preview(self): if not self.fm.settings.preview_files: return False @@ -84,4 +87,4 @@ class File(FileSystemObject): return True def get_preview_source(self, width, height): - return self.fm.get_preview(self.realpath, width, height) + return self.fm.get_preview(self, width, height) diff --git a/ranger/gui/ui.py b/ranger/gui/ui.py index 019f50da..1d47157c 100644 --- a/ranger/gui/ui.py +++ b/ranger/gui/ui.py @@ -303,6 +303,7 @@ class UI(DisplayableContainer): sys.stdout.flush() except: pass + self.win.refresh() def finalize(self): diff --git a/ranger/gui/widgets/browsercolumn.py b/ranger/gui/widgets/browsercolumn.py index f4dcb03c..02c2a5f0 100644 --- a/ranger/gui/widgets/browsercolumn.py +++ b/ranger/gui/widgets/browsercolumn.py @@ -168,15 +168,23 @@ class BrowserColumn(Pager): Pager.close(self) return - f = self.target.get_preview_source(self.wid, self.hei) - if f is None: - Pager.close(self) - else: - self.set_source(f) + if self.fm.settings.preview_images and self.target.is_image(): + self.set_image(self.target.realpath) Pager.draw(self) + else: + f = self.target.get_preview_source(self.wid, self.hei) + if f is None: + Pager.close(self) + else: + self.set_source(f) + Pager.draw(self) def _draw_directory(self): """Draw the contents of a directory""" + if self.image: + self.image = None + self.need_clear_image = True + Pager.clear_image(self) if self.level > 0 and not self.settings.preview_directories: return diff --git a/ranger/gui/widgets/pager.py b/ranger/gui/widgets/pager.py index cf156715..0945021e 100644 --- a/ranger/gui/widgets/pager.py +++ b/ranger/gui/widgets/pager.py @@ -8,6 +8,7 @@ The pager displays text and allows you to scroll inside it. from . import Widget from ranger.gui import ansi from ranger.ext.direction import Direction +import ranger.ext.img_display as img_display # TODO: Scrolling in embedded pager class Pager(Widget): @@ -17,6 +18,7 @@ class Pager(Widget): old_source = None old_scroll_begin = 0 old_startx = 0 + need_clear_image = False max_width = None def __init__(self, win, embedded=False): Widget.__init__(self, win) @@ -25,6 +27,7 @@ class Pager(Widget): self.startx = 0 self.markup = None self.lines = [] + self.image = None def open(self): self.scroll_begin = 0 @@ -33,6 +36,12 @@ class Pager(Widget): self.startx = 0 self.need_redraw = True + def clear_image(self): + if self.need_clear_image: + self.win.clear() + self.win.refresh() + self.need_clear_image = False + def close(self): if self.source and self.source_is_stream: self.source.close() @@ -41,6 +50,9 @@ class Pager(Widget): self.fm.ui.win.move(self.y, self.x) def draw(self): + if self.need_clear_image: + self.need_redraw = True + if self.old_source != self.source: self.old_source = self.source self.need_redraw = True @@ -53,11 +65,23 @@ class Pager(Widget): if self.need_redraw: self.win.erase() - line_gen = self._generate_lines( - starty=self.scroll_begin, startx=self.startx) - for line, i in zip(line_gen, range(self.hei)): - self._draw_line(i, line) + self.clear_image() + + if self.image: + self.source = None + self.fm.ui.win.refresh() + try: + img_display.draw(self.image, self.x, self.y, self.wid, self.hei) + except Exception as e: + self.fm.notify(e, bad=True) + else: + line_gen = self._generate_lines( + starty=self.scroll_begin, startx=self.startx) + + for line, i in zip(line_gen, range(self.hei)): + self._draw_line(i, line) + self.need_redraw = False def _draw_line(self, i, line): @@ -100,7 +124,21 @@ class Pager(Widget): self.fm.ui.keymaps.use_keymap('pager') self.fm.ui.press(key) + def set_image(self, image): + if self.image: + self.need_clear_image = True + self.image = image + + if self.source and self.source_is_stream: + self.source.close() + self.source = None + self.source_is_stream = False + def set_source(self, source, strip=False): + if self.image: + self.image = None + self.need_clear_image = True + if self.source and self.source_is_stream: self.source.close() |