Skip to content

Instantly share code, notes, and snippets.

@weshouman
Last active December 20, 2023 11:15
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save weshouman/a48205ce44c1b58eb27291503c38b61b to your computer and use it in GitHub Desktop.
Save weshouman/a48205ce44c1b58eb27291503c38b61b to your computer and use it in GitHub Desktop.
Nautilus meld extension

This gist is now packaged and maintained at weshouman/nautilus-meld-compare-extension


Nautilus Meld Extension

Overview

This Nautilus extension allows us to easily compare files and directories using the Meld comparison tool directly from the Nautilus context menu. The extension adds new menu items that let you set a "left" file or directory for comparison and then compare a "right" file or directory against it.

Features

  • Set a file or directory as the "left" item for comparison.
  • Compare another file or directory as the "right" item against the previously set "left" item.
  • Directly compare two selected files or directories.

Installation

Prerequisites

  • Ubuntu (or any GNOME-based Linux distribution)
  • Nautilus File Manager
  • Python 3.x
  • Meld
  • Nautilus Python bindings (apt install python3-nautilus)
  • For new Ubuntu releases, for example 23.10, not the case for 22.04: one needs to change the signature of get_file_items from get_file_items(self, window, files) to get_file_items(self, files), fix reference

Steps

  1. mkdir -p ~/.local/share/nautilus-python/extensions/
  2. Place the .py extension file in ~/.local/share/nautilus-python/extensions/.
  3. Restart Nautilus:
    nautilus -q && nautilus --no-desktop
    

Usage

Scenario 1

  • Right-click on a file or directory and choose "Set as Left File to Compare" or "Set as Left Directory to Compare."
  • Right-click on another file or directory to see an option to compare it to the previously set "left" item.

Scenario 2

  • Right-click on 2 files or 2 directories and compre both.
import os
from gi.repository import Nautilus, GObject
class MeldExtension(GObject.GObject, Nautilus.MenuProvider):
def __init__(self):
GObject.Object.__init__(self) # Initialize the GObject type system
self.left_file = None
self.left_dir = None
def menu_activate_cb(self, menu, file1, file2):
os.system(f"meld \"{file1}\" \"{file2}\" &")
def set_left_cb(self, menu, file, is_directory):
if is_directory:
self.left_dir = file
else:
self.left_file = file
def get_file_items(self, window, files):
print(GObject._version)
items = []
if len(files) == 1:
file = files[0]
is_directory = file.is_directory()
left = self.left_dir if is_directory else self.left_file
if left:
item = Nautilus.MenuItem(
name=f"MeldExtension::Compare_{'Dir' if is_directory else 'File'}",
label=f"Compare to '{left.get_name()}'",
tip=f"Compare selected {'directory' if is_directory else 'file'} with the left {'directory' if is_directory else 'file'} using Meld"
)
item.connect("activate", self.menu_activate_cb, left.get_location().get_path(), file.get_location().get_path())
items.append(item)
item = Nautilus.MenuItem(
name=f"MeldExtension::Set_Left_{'Dir' if is_directory else 'File'}",
label=f"Set as Left {'Directory' if is_directory else 'File'} to Compare",
tip=f"Set selected {'directory' if is_directory else 'file'} as the left {'directory' if is_directory else 'file'} for comparison using Meld"
)
item.connect("activate", self.set_left_cb, file, is_directory)
items.append(item)
elif len(files) == 2:
file1, file2 = files
# If both are of the same type
if file1.is_directory() == file2.is_directory():
item = Nautilus.MenuItem(
name="MeldExtension::Compare_Two",
label=f"Compare Selected {'Directories' if file1.is_directory() else 'Files'}",
tip=f"Compare the two selected {'directories' if file1.is_directory() else 'files'} using Meld"
)
item.connect("activate", self.menu_activate_cb, file1.get_location().get_path(), file2.get_location().get_path())
items.append(item)
return items
@tekstryder
Copy link

tekstryder commented Dec 11, 2023

--- a/nautilus-meld.py	2023-12-11 17:34:25.858538851 -0500
+++ b/nautilus-meld.py	2023-12-11 17:37:37.006480509 -0500
@@ -3,7 +3,7 @@
 
 class MeldExtension(Nautilus.MenuProvider, GObject.GObject):
     def __init__(self):
-        #GObject.GObject.__init__(self)  # Initialize the GObject type system
+        GObject.Object.__init__(self)  # Initialize the GObject type system
         self.left_file = None
         self.left_dir = None

Hi,

Thanks for this extension. I was planning to try implementing meld context menus and found you'd already done it.

This small change prevents the RuntimeError: object of type MeldExtension is not initialized

EDIT: In the spirit of the extension:

Screenshot from 2023-12-11 17-57-43

@weshouman
Copy link
Author

weshouman commented Dec 19, 2023

Thanks @tekstryder, even though GObject.GObject would allow initialization to work but using GObject.Object is indeed a better solution.

The root cause of the problem was rather due to the inheritance order, so the fix was to use:

class MeldExtension(GObject.GObject, Nautilus.MenuProvider):

instead of

class MeldExtension(Nautilus.MenuProvider, GObject.GObject):

May be that's related to the limitation that GObject.Object only supports single inheritance. Taking into consideration that this change results in Python's method resolution order (MRO) initializing GObject first (which would also be a fix/workaround if the second parent class has a dependency on the first one).

@weshouman
Copy link
Author

This gist is now maintained as a packaged extension

@tekstryder
Copy link

tekstryder commented Dec 19, 2023

The root cause of the problem was rather due to the inheritance order, so the fix was to use:

class MeldExtension(GObject.GObject, Nautilus.MenuProvider):

instead of

class MeldExtension(Nautilus.MenuProvider, GObject.GObject):

Interestingly, that's the first change I made, but seemed to have no effect.

Glad you got it sorted, and have the extension in a repo. Thanks!

EDIT: Ah, the nautilus process stays active for several seconds after closing an instance, then reattaches if launched before the first PID has been disposed.

@weshouman
Copy link
Author

Actually I wouldn't get into debugging it again if I was the only customer for it 😄 , plus the fact that GObject.Object should have been used instead of GObject.GObject is a great addition.

does the delay only occurs when the extension is there?

@tekstryder
Copy link

Actually I wouldn't get into debugging it again if I was the only customer for it 😄 , plus the fact that GObject.Object should have been used instead of GObject.GObject is a great addition.

Following your explanation I'm using your current version.

does the delay only occurs when the extension is there?

No, not related to the extension. I must have been trigger-happy and launched a new instance before the previous was dead when I tested that first change... and then moved on to the GObject.GObject change in my first comment here.

I'm getting used to this now, as I've recently been building nautilus locally, and am carrying 3 patches for functionality changes that I want, but are unlikely to be accepted upstream (narrower max sidebar width, optional hiding of the star system, and larger max thumbnail sizes in grid view). Now, with the Meld extension, my version of Nautilus is pretty spiffy for my needs!

Oh, and I put a watch on the /usr/bin/nautilus --gapplication-service process, and observed it can take 6-8 seconds to die after exiting the application.

@weshouman
Copy link
Author

that sounds pretty cool (and even much deeper than my knowledge 😄 )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment