xdg.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. import functools
  2. from gi.repository import Gio # pylint: disable=import-error
  3. from gi.repository import GLib # pylint: disable=import-error
  4. import sys
  5. import os
  6. import io
  7. from xdg.DesktopEntry import DesktopEntry
  8. DROPINS_DIR = '/etc/qubes/applications'
  9. def find_dropins(filename, dropins_dir):
  10. result = []
  11. app_dropins_dir = os.path.join(
  12. dropins_dir,
  13. os.path.basename(filename) + '.d')
  14. if os.path.isdir(app_dropins_dir):
  15. for dropin in sorted(os.listdir(app_dropins_dir)):
  16. result.append(
  17. os.path.join(app_dropins_dir, dropin))
  18. return result
  19. def load_desktop_entry_with_dropins(filename, dropins):
  20. desktop_entry = DesktopEntry(filename)
  21. for dropin in dropins:
  22. dropin_entry = DesktopEntry(dropin)
  23. for group_name, group in dropin_entry.content.items():
  24. desktop_entry.content.setdefault(group_name, {}).update(group)
  25. return desktop_entry
  26. def make_launcher(filename, dropins_dir=DROPINS_DIR):
  27. dropins = find_dropins(filename, dropins_dir)
  28. if not dropins:
  29. return Gio.DesktopAppInfo.new_from_filename(filename)
  30. desktop_entry = load_desktop_entry_with_dropins(filename, dropins)
  31. data = GLib.Bytes(ini_to_string(desktop_entry).encode('utf-8'))
  32. keyfile = GLib.KeyFile()
  33. keyfile.load_from_bytes(data, 0)
  34. return Gio.DesktopAppInfo.new_from_keyfile(keyfile)
  35. def ini_to_string(ini):
  36. # See IniFile.write() in xdg package.
  37. output = io.StringIO()
  38. if ini.defaultGroup:
  39. output.write("[%s]\n" % ini.defaultGroup)
  40. for (key, value) in ini.content[ini.defaultGroup].items():
  41. output.write("%s=%s\n" % (key, value))
  42. output.write("\n")
  43. for (name, group) in ini.content.items():
  44. if name != ini.defaultGroup:
  45. output.write("[%s]\n" % name)
  46. for (key, value) in group.items():
  47. output.write("%s=%s\n" % (key, value))
  48. output.write("\n")
  49. return output.getvalue()
  50. def pid_callback(launcher, pid, pid_list):
  51. pid_list.append(pid)
  52. def dbus_name_change(loop, name, old_owner, new_owner):
  53. if not new_owner:
  54. loop.quit()
  55. def launch(filename, *files, **kwargs):
  56. wait = kwargs.pop('wait', True)
  57. launcher = make_launcher(filename)
  58. try:
  59. import dbus
  60. from dbus.mainloop.glib import DBusGMainLoop
  61. if hasattr(launcher, 'get_boolean'):
  62. activatable = launcher.get_boolean('DBusActivatable')
  63. if activatable:
  64. loop = GLib.MainLoop()
  65. DBusGMainLoop(set_as_default=True)
  66. bus = dbus.SessionBus()
  67. service_id = launcher.get_id()
  68. # cut the .desktop suffix
  69. service_id = service_id[:-len('.desktop')]
  70. # see D-Bus Activation Desktop entry specification
  71. object_path = '/' + service_id.replace('.', '/').\
  72. replace('-', '_')
  73. try:
  74. proxy = bus.get_object(service_id, object_path)
  75. match = bus.add_signal_receiver(
  76. functools.partial(dbus_name_change, loop),
  77. 'NameOwnerChanged',
  78. dbus.BUS_DAEMON_IFACE,
  79. dbus.BUS_DAEMON_NAME,
  80. dbus.BUS_DAEMON_PATH)
  81. if files:
  82. proxy.Open(files, {},
  83. dbus_interface='org.freedesktop.Application')
  84. else:
  85. proxy.Activate({},
  86. dbus_interface='org.freedesktop.Application')
  87. except dbus.DBusException as e:
  88. print(e)
  89. # fallback to non-dbus version
  90. pass
  91. else:
  92. if wait:
  93. loop.run()
  94. match.remove()
  95. return
  96. except ImportError:
  97. pass
  98. if wait:
  99. pid_list = []
  100. flags = GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.DO_NOT_REAP_CHILD
  101. launcher.launch_uris_as_manager(files, None, flags, None, None,
  102. pid_callback, pid_list)
  103. for pid in pid_list:
  104. os.waitpid(pid, 0)
  105. else:
  106. launcher.launch(files, None)
  107. if __name__ == "__main__":
  108. launch(*sys.argv[1:])