123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200 |
- # SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect
- #
- # SPDX-License-Identifier: GPL-2.0-or-later
- """
- nautilus-gsconnect.py - A Nautilus extension for sending files via GSConnect.
- A great deal of credit and appreciation is owed to the indicator-kdeconnect
- developers for the sister Python script 'kdeconnect-send-nautilus.py':
- https://github.com/Bajoja/indicator-kdeconnect/blob/master/data/extensions/kdeconnect-send-nautilus.py
- """
- import gettext
- import os.path
- import sys
- import gi
- gi.require_version("Gio", "2.0")
- gi.require_version("GLib", "2.0")
- gi.require_version("GObject", "2.0")
- from gi.repository import Gio, GLib, GObject
- # Host application detection
- #
- # Nemo seems to reliably identify itself as 'nemo' in argv[0], so we
- # can test for that. Nautilus detection is less reliable, so don't try.
- # See https://github.com/linuxmint/nemo-extensions/issues/330
- if "nemo" in sys.argv[0].lower():
- # Host runtime is nemo-python
- gi.require_version("Nemo", "3.0")
- from gi.repository import Nemo as FileManager
- else:
- # Otherwise, just assume it's nautilus-python
- from gi.repository import Nautilus as FileManager
- SERVICE_NAME = "org.gnome.Shell.Extensions.GSConnect"
- SERVICE_PATH = "/org/gnome/Shell/Extensions/GSConnect"
- # Init gettext translations
- LOCALE_DIR = os.path.join(
- GLib.get_user_data_dir(),
- "gnome-shell",
- "extensions",
- "gsconnect@andyholmes.github.io",
- "locale",
- )
- if not os.path.exists(LOCALE_DIR):
- LOCALE_DIR = None
- try:
- i18n = gettext.translation(SERVICE_NAME, localedir=LOCALE_DIR)
- _ = i18n.gettext
- except (IOError, OSError) as e:
- print("GSConnect: {0}".format(str(e)))
- i18n = gettext.translation(
- SERVICE_NAME, localedir=LOCALE_DIR, fallback=True
- )
- _ = i18n.gettext
- class GSConnectShareExtension(GObject.Object, FileManager.MenuProvider):
- """A context menu for sending files via GSConnect."""
- def __init__(self):
- """Initialize the DBus ObjectManager."""
- GObject.Object.__init__(self)
- self.devices = {}
- Gio.DBusProxy.new_for_bus(
- Gio.BusType.SESSION,
- Gio.DBusProxyFlags.DO_NOT_AUTO_START,
- None,
- SERVICE_NAME,
- SERVICE_PATH,
- "org.freedesktop.DBus.ObjectManager",
- None,
- self._init_async,
- None,
- )
- def _init_async(self, proxy, res, user_data):
- proxy = proxy.new_for_bus_finish(res)
- proxy.connect("notify::g-name-owner", self._on_name_owner_changed)
- proxy.connect("g-signal", self._on_g_signal)
- self._on_name_owner_changed(proxy, None)
- def _on_g_signal(self, proxy, sender_name, signal_name, parameters):
- # Wait until the service is ready
- if proxy.props.g_name_owner is None:
- return
- objects = parameters.unpack()
- if signal_name == "InterfacesAdded":
- for object_path, props in objects.items():
- props = props["org.gnome.Shell.Extensions.GSConnect.Device"]
- self.devices[object_path] = (
- props["Name"],
- Gio.DBusActionGroup.get(
- proxy.get_connection(), SERVICE_NAME, object_path
- ),
- )
- elif signal_name == "InterfacesRemoved":
- for object_path in objects:
- try:
- del self.devices[object_path]
- except KeyError:
- pass
- def _on_name_owner_changed(self, proxy, pspec):
- # Wait until the service is ready
- if proxy.props.g_name_owner is None:
- self.devices = {}
- else:
- proxy.call(
- "GetManagedObjects",
- None,
- Gio.DBusCallFlags.NO_AUTO_START,
- -1,
- None,
- self._get_managed_objects,
- None,
- )
- def _get_managed_objects(self, proxy, res, user_data):
- objects = proxy.call_finish(res)[0]
- for object_path, props in objects.items():
- props = props["org.gnome.Shell.Extensions.GSConnect.Device"]
- if not props:
- continue
- self.devices[object_path] = (
- props["Name"],
- Gio.DBusActionGroup.get(
- proxy.get_connection(), SERVICE_NAME, object_path
- ),
- )
- def send_files(self, menu, files, action_group):
- """Send *files* to *device_id*."""
- for file in files:
- variant = GLib.Variant("(sb)", (file.get_uri(), False))
- action_group.activate_action("shareFile", variant)
- def get_file_items(self, *args):
- """Return a list of select files to be sent."""
- # 'args' will depend on the Nautilus API version.
- # * Nautilus 4.0:
- # `[files: List[Nautilus.FileInfo]]`
- # * Nautilus 3.0:
- # `[window: Gtk.Widget, files: List[Nautilus.FileInfo]]`
- files = args[-1]
- # Only accept regular files
- for uri in files:
- if uri.get_uri_scheme() != "file" or uri.is_directory():
- return ()
- # Enumerate capable devices
- devices = []
- for name, action_group in self.devices.values():
- if action_group.get_action_enabled("shareFile"):
- devices.append([name, action_group])
- # No capable devices; don't show menu entry
- if not devices:
- return ()
- # Context Menu Item
- menu = FileManager.MenuItem(
- name="GSConnectShareExtension::Devices",
- label=_("Send To Mobile Device"),
- )
- # Context Submenu
- submenu = FileManager.Menu()
- menu.set_submenu(submenu)
- # Context Submenu Items
- for name, action_group in devices:
- item = FileManager.MenuItem(
- name="GSConnectShareExtension::Device" + name, label=name
- )
- item.connect("activate", self.send_files, files, action_group)
- submenu.append_item(item)
- return (menu,)
|