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 viewand 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 gtk4Installation:-------------1. Copy or save this file to the user extension directory: ~/.local/share/nautilus-python/extensions/sha256_column.py2. Restart Nautilus: nautilus -q && nautilusUsage:------- Column: Switch to List View (Ctrl + 2) -> View Options -> "Visible Columns..." -> Check "SHA256".- Copy Hash: Right-click any file -> Click "Copy SHA256"."""import hashlibimport osimport threadingimport gigi.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 * 1024class 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"