summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorhut <hut@lavabit.com>2013-02-03 03:48:54 +0100
committerhut <hut@lavabit.com>2013-02-03 03:48:54 +0100
commit90b8d4399f53490d3180786cb92e7a12f694f92c (patch)
treeb3920ad8025bd650de2136c6577568454ac945e9
parent309d592b24299dcc8705115af92adb7667213a3d (diff)
parent71c81ff14f1be1f02f70216aa9a3a0b0e663bb52 (diff)
downloadranger-90b8d4399f53490d3180786cb92e7a12f694f92c.tar.gz
Merge remote-tracking branch 'potato/img-preview'
-rw-r--r--ranger/container/settingobject.py1
-rw-r--r--ranger/core/actions.py14
-rw-r--r--ranger/ext/img_display.py79
-rw-r--r--ranger/fsobject/file.py5
-rw-r--r--ranger/gui/ui.py1
-rw-r--r--ranger/gui/widgets/browsercolumn.py18
-rw-r--r--ranger/gui/widgets/pager.py46
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()