nautilus-gsconnect.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. # SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect
  2. #
  3. # SPDX-License-Identifier: GPL-2.0-or-later
  4. """
  5. nautilus-gsconnect.py - A Nautilus extension for sending files via GSConnect.
  6. A great deal of credit and appreciation is owed to the indicator-kdeconnect
  7. developers for the sister Python script 'kdeconnect-send-nautilus.py':
  8. https://github.com/Bajoja/indicator-kdeconnect/blob/master/data/extensions/kdeconnect-send-nautilus.py
  9. """
  10. import gettext
  11. import os.path
  12. import sys
  13. import gi
  14. gi.require_version("Gio", "2.0")
  15. gi.require_version("GLib", "2.0")
  16. gi.require_version("GObject", "2.0")
  17. from gi.repository import Gio, GLib, GObject
  18. # Host application detection
  19. #
  20. # Nemo seems to reliably identify itself as 'nemo' in argv[0], so we
  21. # can test for that. Nautilus detection is less reliable, so don't try.
  22. # See https://github.com/linuxmint/nemo-extensions/issues/330
  23. if "nemo" in sys.argv[0].lower():
  24. # Host runtime is nemo-python
  25. gi.require_version("Nemo", "3.0")
  26. from gi.repository import Nemo as FileManager
  27. else:
  28. # Otherwise, just assume it's nautilus-python
  29. from gi.repository import Nautilus as FileManager
  30. SERVICE_NAME = "org.gnome.Shell.Extensions.GSConnect"
  31. SERVICE_PATH = "/org/gnome/Shell/Extensions/GSConnect"
  32. # Init gettext translations
  33. LOCALE_DIR = os.path.join(
  34. GLib.get_user_data_dir(),
  35. "gnome-shell",
  36. "extensions",
  37. "gsconnect@andyholmes.github.io",
  38. "locale",
  39. )
  40. if not os.path.exists(LOCALE_DIR):
  41. LOCALE_DIR = None
  42. try:
  43. i18n = gettext.translation(SERVICE_NAME, localedir=LOCALE_DIR)
  44. _ = i18n.gettext
  45. except (IOError, OSError) as e:
  46. print("GSConnect: {0}".format(str(e)))
  47. i18n = gettext.translation(
  48. SERVICE_NAME, localedir=LOCALE_DIR, fallback=True
  49. )
  50. _ = i18n.gettext
  51. class GSConnectShareExtension(GObject.Object, FileManager.MenuProvider):
  52. """A context menu for sending files via GSConnect."""
  53. def __init__(self):
  54. """Initialize the DBus ObjectManager."""
  55. GObject.Object.__init__(self)
  56. self.devices = {}
  57. Gio.DBusProxy.new_for_bus(
  58. Gio.BusType.SESSION,
  59. Gio.DBusProxyFlags.DO_NOT_AUTO_START,
  60. None,
  61. SERVICE_NAME,
  62. SERVICE_PATH,
  63. "org.freedesktop.DBus.ObjectManager",
  64. None,
  65. self._init_async,
  66. None,
  67. )
  68. def _init_async(self, proxy, res, user_data):
  69. proxy = proxy.new_for_bus_finish(res)
  70. proxy.connect("notify::g-name-owner", self._on_name_owner_changed)
  71. proxy.connect("g-signal", self._on_g_signal)
  72. self._on_name_owner_changed(proxy, None)
  73. def _on_g_signal(self, proxy, sender_name, signal_name, parameters):
  74. # Wait until the service is ready
  75. if proxy.props.g_name_owner is None:
  76. return
  77. objects = parameters.unpack()
  78. if signal_name == "InterfacesAdded":
  79. for object_path, props in objects.items():
  80. props = props["org.gnome.Shell.Extensions.GSConnect.Device"]
  81. self.devices[object_path] = (
  82. props["Name"],
  83. Gio.DBusActionGroup.get(
  84. proxy.get_connection(), SERVICE_NAME, object_path
  85. ),
  86. )
  87. elif signal_name == "InterfacesRemoved":
  88. for object_path in objects:
  89. try:
  90. del self.devices[object_path]
  91. except KeyError:
  92. pass
  93. def _on_name_owner_changed(self, proxy, pspec):
  94. # Wait until the service is ready
  95. if proxy.props.g_name_owner is None:
  96. self.devices = {}
  97. else:
  98. proxy.call(
  99. "GetManagedObjects",
  100. None,
  101. Gio.DBusCallFlags.NO_AUTO_START,
  102. -1,
  103. None,
  104. self._get_managed_objects,
  105. None,
  106. )
  107. def _get_managed_objects(self, proxy, res, user_data):
  108. objects = proxy.call_finish(res)[0]
  109. for object_path, props in objects.items():
  110. props = props["org.gnome.Shell.Extensions.GSConnect.Device"]
  111. if not props:
  112. continue
  113. self.devices[object_path] = (
  114. props["Name"],
  115. Gio.DBusActionGroup.get(
  116. proxy.get_connection(), SERVICE_NAME, object_path
  117. ),
  118. )
  119. def send_files(self, menu, files, action_group):
  120. """Send *files* to *device_id*."""
  121. for file in files:
  122. variant = GLib.Variant("(sb)", (file.get_uri(), False))
  123. action_group.activate_action("shareFile", variant)
  124. def get_file_items(self, *args):
  125. """Return a list of select files to be sent."""
  126. # 'args' will depend on the Nautilus API version.
  127. # * Nautilus 4.0:
  128. # `[files: List[Nautilus.FileInfo]]`
  129. # * Nautilus 3.0:
  130. # `[window: Gtk.Widget, files: List[Nautilus.FileInfo]]`
  131. files = args[-1]
  132. # Only accept regular files
  133. for uri in files:
  134. if uri.get_uri_scheme() != "file" or uri.is_directory():
  135. return ()
  136. # Enumerate capable devices
  137. devices = []
  138. for name, action_group in self.devices.values():
  139. if action_group.get_action_enabled("shareFile"):
  140. devices.append([name, action_group])
  141. # No capable devices; don't show menu entry
  142. if not devices:
  143. return ()
  144. # Context Menu Item
  145. menu = FileManager.MenuItem(
  146. name="GSConnectShareExtension::Devices",
  147. label=_("Send To Mobile Device"),
  148. )
  149. # Context Submenu
  150. submenu = FileManager.Menu()
  151. menu.set_submenu(submenu)
  152. # Context Submenu Items
  153. for name, action_group in devices:
  154. item = FileManager.MenuItem(
  155. name="GSConnectShareExtension::Device" + name, label=name
  156. )
  157. item.connect("activate", self.send_files, files, action_group)
  158. submenu.append_item(item)
  159. return (menu,)