Nautilus extension for calculating SHA256

Since I often need to calculate file hashes, I decided to ask Gemini to create a Nautilus extension that would let me view the file hash in a new dedicated column and copy the value from the context menu.

Below is the code with instructions for installing it.

"""
Nautilus SHA256 Column and Clipboard Extension
===============================================
This extension adds a custom "SHA256" column to the GNOME Files (Nautilus) list view
and a context menu option (right-click) to copy the SHA256 hash to the clipboard.
Prerequisites:
--------------
Make sure `nautilus-python` and GTK4 bindings are installed on your system:
- Ubuntu / Debian:
sudo apt install python3-nautilus gir1.2-gtk-4.0
- Fedora:
sudo dnf install nautilus-python gtk4
- Arch Linux:
sudo pacman -S python-nautilus gtk4
Installation:
-------------
1. Copy or save this file to the user extension directory:
~/.local/share/nautilus-python/extensions/sha256_column.py
2. Restart Nautilus:
nautilus -q && nautilus
Usage:
------
- Column: Switch to List View (Ctrl + 2) -> View Options -> "Visible Columns..." -> Check "SHA256".
- Copy Hash: Right-click any file -> Click "Copy SHA256".
"""
import hashlib
import os
import threading
import gi
gi.require_version('Gdk', '4.0')
from gi.repository import GObject, Nautilus, GLib, Gdk
# Security threshold: Skip automatic calculation for files larger than 50 MB to prevent high disk usage.
MAX_FILE_SIZE_BYTES = 50 * 1024 * 1024
class Sha256ColumnExtension(GObject.GObject, Nautilus.ColumnProvider, Nautilus.InfoProvider, Nautilus.MenuProvider):
def __init__(self):
super().__init__()
# Cache to store calculated hashes: {file_path: sha256_str}
self._hash_cache = {}
# --- 1. COLUMN PROVIDER ---
def get_columns(self):
"""Adds the 'SHA256' column definition to Nautilus list view options."""
column = Nautilus.Column(
name="NautilusPython::sha256_column",
attribute="sha256_hash",
label="SHA256",
description="Displays the SHA256 checksum of the file"
)
return [column]
def update_file_info(self, file):
"""Callback invoked by Nautilus to populate custom file attributes."""
if file.is_directory() or file.get_uri_scheme() != "file":
return Nautilus.OperationResult.COMPLETE
file_path = file.get_location().get_path()
if not file_path or not os.path.exists(file_path):
return Nautilus.OperationResult.COMPLETE
# 1. Check if already cached
if file_path in self._hash_cache:
file.add_string_attribute("sha256_hash", self._hash_cache[file_path])
return Nautilus.OperationResult.COMPLETE
# 2. Check file size threshold
try:
file_size = os.path.getsize(file_path)
if file_size > MAX_FILE_SIZE_BYTES:
file.add_string_attribute("sha256_hash", "File too large (>50MB)")
return Nautilus.OperationResult.COMPLETE
except Exception:
return Nautilus.OperationResult.COMPLETE
# 3. Set placeholder
file.add_string_attribute("sha256_hash", "Calculating...")
# 4. Compute in background using file_path string (thread-safe)
thread = threading.Thread(target=self._async_compute_hash, args=(file, file_path))
thread.daemon = True
thread.start()
return Nautilus.OperationResult.COMPLETE
def _async_compute_hash(self, file, file_path):
"""Computes hash in background and notifies Nautilus on main thread."""
hash_digest = self._get_sha256(file_path)
self._hash_cache[file_path] = hash_digest
GLib.idle_add(self._update_file_attribute, file, hash_digest)
def _update_file_attribute(self, file, hash_value):
"""Applies attribute update on the main GTK thread."""
try:
file.add_string_attribute("sha256_hash", hash_value)
file.invalidate_extension_info()
except Exception:
pass
return False
# --- 2. MENU PROVIDER (CONTEXT MENU) ---
def get_file_items(self, files):
"""Adds 'Copy SHA256' option to context menu for single file selection."""
if len(files) != 1:
return []
file = files[0]
if file.is_directory() or file.get_uri_scheme() != "file":
return []
item = Nautilus.MenuItem(
name="Sha256ColumnExtension::CopyHash",
label="Copy SHA256",
tip="Calculates and copies the SHA256 checksum of this file to the clipboard"
)
item.connect("activate", self._on_copy_menu_clicked, file)
return [item]
def _on_copy_menu_clicked(self, menu, file):
"""Triggered when user clicks 'Copy SHA256' in context menu."""
file_path = file.get_location().get_path()
if not file_path or not os.path.exists(file_path):
return
def task():
# Use cached value if available, else compute
if file_path in self._hash_cache:
hash_digest = self._hash_cache[file_path]
else:
hash_digest = self._get_sha256(file_path)
self._hash_cache[file_path] = hash_digest
GLib.idle_add(self._set_clipboard_text, hash_digest)
thread = threading.Thread(target=task)
thread.daemon = True
thread.start()
def _set_clipboard_text(self, text):
"""Copies text to system clipboard using GTK4 Gdk.ContentProvider."""
try:
display = Gdk.Display.get_default()
if display:
clipboard = display.get_clipboard()
# GTK4 robust clipboard mechanism
val = GObject.Value(GObject.TYPE_STRING, text)
provider = Gdk.ContentProvider.new_for_value(val)
clipboard.set_content(provider)
except Exception:
# Fallback for systems with external tools if native clipboard fails
self._fallback_clipboard_copy(text)
return False
def _fallback_clipboard_copy(self, text):
"""Fallback clipboard mechanism using wl-copy or xclip if available."""
import subprocess
try:
p = subprocess.Popen(["wl-copy"], stdin=subprocess.PIPE)
p.communicate(input=text.encode("utf-8"))
except FileNotFoundError:
try:
p = subprocess.Popen(["xclip", "-selection", "clipboard"], stdin=subprocess.PIPE)
p.communicate(input=text.encode("utf-8"))
except FileNotFoundError:
pass
# --- HELPER METHOD ---
def _get_sha256(self, file_path):
"""Calculates SHA256 reading file in chunks."""
sha256 = hashlib.sha256()
try:
with open(file_path, "rb") as f:
for block in iter(lambda: f.read(65536), b""):
sha256.update(block)
return sha256.hexdigest()
except Exception:
return "Read Error"

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.