Skip to content

Latest commit

 

History

6 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Nautilus QuickShare

Adds flat share items (Copy path, Upload to <name>, …) to Nautilus' right-click menu for selected files. The registered share methods live in methods/ and are auto-discovered — drop a new *.py file in there, define a ShareMethod subclass, and a new entry appears in the menu. (Items are deliberately flat, not a submenu: Nautilus 43+ uses GtkPopoverMenu, where submenus push a new page and hide the parent menu.)

Built-in methods:

Method Description Requires
Copy path Copy absolute path(s) to clipboard
Copy file URI Copy file://… URI(s)
Upload to <name> Upload to an HTTP endpoint via curl; multi-file selections are zipped first curl + config

Note on KDE Connect: KDE Connect ships its own Nautilus extension (kdeconnect-share.py) that adds a "Send to device" entry, so we don't duplicate it here. Install kdeconnect separately if you want that.

Targets GNOME Files (Nautilus) 43+ using the Nautilus 4.1 extension API and the GTK 4 stack.

Prerequisites

Hard (extension must load at all):

Distro Package
Arch sudo pacman -S nautilus-python
Debian/Ubuntu sudo apt install python3-nautilus gir1.2-nautilus-4.0 libgtk-4-dev
Fedora sudo dnf install python-nautilus gtk4-devel

Optional (gates individual methods, the rest still works):

Binary Affects
curl Web upload method
dms Routes clipboard + notifications through the DMS shell (preferred); falls back to Gdk/notify-send if absent
notify-send Notifications when dms is also absent (Gtk.MessageDialog as last resort)

Without dms, the extension still works: clipboard uses Gdk.Clipboard and notifications use notify-send (or a Gtk.MessageDialog if that's missing too). When dms is on PATH, clipboard copies go through dms cl copy (into DMS clipboard history) and notifications go through dms ipc call toast <info|warn|error> <message> (DMS in-shell toast).

Install

./install.sh

The installer:

  1. Verifies nautilus-python imports (refuses to install if not). Prints per-distro install hints on failure.
  2. Warns (but continues) if any optional binary is missing, naming the affected method.
  3. Symlinks share_menu.py, registry.py, methods/ into ~/.local/share/nautilus-python/extensions/.
  4. Runs nautilus -q so the running instance restarts with the new extension.

Reopen a Nautilus window, select a file, right-click → Copy path / Upload to <name> / … Symlinking means source edits take effect the next time Nautilus loads the extension (run nautilus -q to restart).

Configuring web upload

Web upload profiles live in ~/.config/nautilus-share/web-upload.conf (TOML). A commented template is written there on first activation. After editing, run nautilus -q and reopen a window.

[[profile]]
name        = "Personal transfer"        # menu label
url_template = "https://transfer.example.com/{filename}"  # one curl per file
method      = "put"                       # post (multipart) or put
headers     = { Authorization = "Bearer YOURTOKEN" }
follow_redirects = true
insecure    = false
timeout     = 120
max_days    = 5                           # sends "Max-Days: 5" (expiry; omit to skip)
max_downloads = 1                         # sends "Max-Downloads: 1" (omit to skip)
zip_multi   = true                        # zip multi-file selections into one archive
copy   = true                            # copy extracted result to clipboard
notify = true                            # desktop notification on success

# Keep plain keys ABOVE this header — in TOML, keys written after a table
# header belong to that table, so a `copy = false` placed below
# [profile.response] would be silently ignored.
[profile.response]
mode    = "json_key"                      # body | line | json_key
json_key = "url"                          # nested keys OK: "data.url"

Instead of url_template you can set a static url (e.g. url = "https://bin.example.com/upload" with method = "post" and field = "filedata"): all files then go out in a single multipart POST. Set exactly one of the two. Each [[profile]] becomes its own flat Upload to <name> menu entry.

All options:

Key Default Meaning
name required Menu label (Upload to <name>)
url Static endpoint; all files in one request
url_template Per-file endpoint; {filename} is replaced with each file's URL-encoded basename (transfer.sh style)
method "post" post (multipart form) or put
field "file" Multipart field name (POST only)
headers {} Extra headers table; fed to curl on stdin so tokens never appear in /proc/*/cmdline
follow_redirects true curl -L
insecure false curl -k
timeout 120 curl --max-time, seconds
max_days unset Sends Max-Days: <n> (transfer.sh-style expiry); omitted when unset
max_downloads unset Sends Max-Downloads: <n>; omitted when unset
zip_multi true Zip multi-file selections into one archive first
copy true Copy the extracted result to the clipboard
notify true Desktop notifications for progress/success/failure
response.mode "body" What to extract from the response (see below)
response.json_key Key to pull when mode = "json_key"

Multi-file selections are zipped into one archive before uploading (named after the files' parent directory, e.g. selecting three files in ~/Pictures uploads Pictures.zip), so you share a single link. Zipping happens off the UI thread and the temp archive is deleted after the upload. Set zip_multi = false to upload files individually instead — note that method = "put" with a static url only supports single files.

response.mode:

  • body — entire curl stdout → clipboard
  • line — first non-empty stdout line → clipboard
  • json_key — parse response as JSON; pull json_key (dotted path OK) → clipboard

Results and failures. On success the extracted result (usually the share link) is copied to the clipboard and a notification confirms it. On failure you get an error notification with the first curl error. If some of several per-file uploads fail, the URLs that did succeed are still copied and the error notice says how many made it.

Adding your own share method

Create methods/my_thing.py:

"""Share via Foo."""

from registry import ShareMethod, register


@register
class FooMethod(ShareMethod):
    name = "Send via Foo"
    icon = "document-send"

    def available(self) -> bool:
        # Return False when your backend isn't online and Nautilus will
        # quietly hide you from the menu.
        return True

    def share(self, files, ctx) -> None:
        for f in files:
            path = f.get_location().get_path()
            # ...do stuff...
        if ctx.notify:
            ctx.notify("Sent via Foo", "info")
        if ctx.copy_to_clipboard:
            ctx.copy_to_clipboard("…")

Restart Nautilus (nautilus -q) and it shows up. Methods whose available() returns False are silently skipped so a missing backend doesn't clutter the menu.

files is a list of Nautilus.FileInfo. Each has .get_location() returning a Gio.File (.get_path() → native string).

To expose several menu entries from one method (like Web upload's one entry per profile), override menu_entries(files) to return (label, icon, payload) tuples — the payload of whichever entry the user clicks is what share(payload, ctx) receives. The default implementation returns a single entry whose payload is the file list itself.

Long-running work must avoid blocking the GTK main loop — and must not fork() from a Python worker thread (subprocess in a thread can corrupt the multi-threaded Nautilus process). Use Gio.Subprocess with async callbacks for external commands (see methods/web_upload.py); pure-Python work is fine in a threading.Thread with GTK calls routed back through GLib.idle_add (the built-in ctx.notify / ctx.copy_to_clipboard already do this).

Uninstall

rm -f ~/.local/share/nautilus-python/extensions/{share_menu.py,registry.py}
rm -rf ~/.local/share/nautilus-python/extensions/methods
nautilus -q

Layout

extensions/
├── share_menu.py        # Nautilus.MenuProvider entry point (loaded by Nautilus)
├── registry.py          # ShareMethod ABC + auto-discovery of methods/
├── methods/
│   ├── copy_path.py     # copy absolute path / file:// URI
│   └── web_upload.py    # async curl to configured HTTP endpoints (TOML
│                        # config; zips multi-file selections, Max-Days/
│                        # Max-Downloads expiry headers)
├── install.sh
└── README.md

Troubleshooting

  • No share items in the menu. Run nautilus -q from a terminal and reopen a window — Nautilus only re-scans extensions on startup. Tail ~/.xsession-errors or journalctl --user -e for Python tracebacks at load time.
  • A method is missing. Its available() returned False (curl not installed, no [[profile]] in web-upload.conf). Edit config / install the binary and restart Nautilus.
  • No "Upload to …" entries. ~/.config/nautilus-share/web-upload.conf is still the commented template — uncomment a [[profile]] block with a real url or url_template.

About

Flat share items for the Nautilus right-click menu: copy paths/URIs, zip-and-upload to transfer.sh-style hosts via curl, with DMS shell integration

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages