1# Copyright 2014 Christoph Reiter 2# 2018 Ludovic Druette 3# 4# This program is free software; you can redistribute it and/or modify 5# it under the terms of the GNU General Public License as published by 6# the Free Software Foundation; either version 2 of the License, or 7# (at your option) any later version. 8 9import time 10 11from gi.repository import GLib, Gio 12 13from quodlibet.util import print_exc 14from ._base import MMKeysBackend, MMKeysAction 15 16 17def dbus_has_interface(dbus_name, dbus_path, dbus_interface): 18 try: 19 proxy = Gio.DBusProxy.new_for_bus_sync( 20 Gio.BusType.SESSION, Gio.DBusProxyFlags.NONE, None, 21 dbus_name, dbus_path, 22 "org.freedesktop.DBus.Introspectable", None) 23 xml = proxy.Introspect() 24 node = Gio.DBusNodeInfo.new_for_xml(xml) 25 for iface in node.interfaces: 26 if iface.name == dbus_interface: 27 return True 28 return False 29 except GLib.Error: 30 return False 31 32 33class GnomeBackend(MMKeysBackend): 34 35 DBUS_NAME = "org.gnome.SettingsDaemon.MediaKeys" 36 DBUS_PATH = "/org/gnome/SettingsDaemon/MediaKeys" 37 DBUS_IFACE = "org.gnome.SettingsDaemon.MediaKeys" 38 39 _EVENTS = { 40 "Next": MMKeysAction.NEXT, 41 "Previous": MMKeysAction.PREV, 42 "Play": MMKeysAction.PLAYPAUSE, 43 "Pause": MMKeysAction.PAUSE, 44 "Stop": MMKeysAction.STOP, 45 "FastForward": MMKeysAction.FORWARD, 46 "Rewind": MMKeysAction.REWIND, 47 "Repeat": MMKeysAction.REPEAT, 48 "Shuffle": MMKeysAction.SHUFFLE 49 } 50 51 def __init__(self, name, callback): 52 self.__interface = None 53 self.__watch = None 54 self.__grab_time = -1 55 self.__name = name 56 self.__key_pressed_sig = None 57 self.__callback = callback 58 self.__enable_watch() 59 60 @classmethod 61 def is_active(cls): 62 """If the gsd plugin is active atm""" 63 64 return dbus_has_interface(cls.DBUS_NAME, cls.DBUS_PATH, cls.DBUS_IFACE) 65 66 def cancel(self): 67 if self.__callback: 68 self.__disable_watch() 69 self.__release() 70 self.__callback = None 71 72 def grab(self, update=True): 73 """Tells gsd that QL started or got the focus. 74 update: whether to send the current time or the last one""" 75 76 if update: 77 # so this breaks every 50 days.. ok.. 78 self.__grab_time = int((time.time() * 1000)) & 0xFFFFFFFF 79 elif self.__grab_time < 0: 80 # can not send the last event if there was none 81 return 82 83 iface = self.__update_interface() 84 if not iface: 85 return 86 87 try: 88 iface.GrabMediaPlayerKeys('(su)', self.__name, self.__grab_time) 89 except GLib.Error: 90 print_exc() 91 92 def __update_interface(self): 93 """If __interface is None, set a proxy interface object and connect 94 to the key pressed signal.""" 95 96 if self.__interface: 97 return self.__interface 98 99 try: 100 iface = Gio.DBusProxy.new_for_bus_sync( 101 Gio.BusType.SESSION, Gio.DBusProxyFlags.NONE, None, 102 self.DBUS_NAME, self.DBUS_PATH, self.DBUS_IFACE, None) 103 except GLib.Error: 104 print_exc() 105 else: 106 self.__key_pressed_sig = iface.connect( 107 'g-signal', self.__on_signal) 108 self.__interface = iface 109 110 return self.__interface 111 112 def __enable_watch(self): 113 """Enable events for dbus name owner change""" 114 if self.__watch: 115 return 116 117 # This also triggers for existing name owners 118 self.__watch = Gio.bus_watch_name( 119 Gio.BusType.SESSION, self.DBUS_NAME, Gio.BusNameWatcherFlags.NONE, 120 self.__owner_appeared, self.__owner_vanished) 121 122 def __disable_watch(self): 123 """Disable name owner change events""" 124 if self.__watch: 125 Gio.bus_unwatch_name(self.__watch) 126 self.__watch = None 127 128 def __owner_appeared(self, bus, name, owner): 129 """This gets called when the owner of the dbus name appears 130 so we can handle gnome-settings-daemon restarts.""" 131 132 if not self.__interface: 133 # new owner, get a new interface object and 134 # resend the last grab event 135 self.grab(update=False) 136 137 def __owner_vanished(self, bus, owner): 138 """This gets called when the owner of the dbus name disappears 139 so we can handle gnome-settings-daemon restarts.""" 140 141 # owner gone, remove the signal matches/interface etc. 142 self.__release() 143 144 def __on_signal(self, proxy, sender, signal, args): 145 if signal == 'MediaPlayerKeyPressed': 146 application, action = tuple(args)[:2] 147 self.__key_pressed(application, action) 148 149 def __key_pressed(self, application, action): 150 if application != self.__name: 151 return 152 153 if action in self._EVENTS: 154 self.__callback(self._EVENTS[action]) 155 156 def __release(self): 157 """Tells gsd that we don't want events anymore and 158 removes all signal matches""" 159 160 if not self.__interface: 161 return 162 163 if self.__key_pressed_sig: 164 self.__interface.disconnect(self.__key_pressed_sig) 165 self.__key_pressed_sig = None 166 167 try: 168 self.__interface.ReleaseMediaPlayerKeys('(s)', self.__name) 169 except GLib.Error: 170 print_exc() 171 self.__interface = None 172 173 174# https://mail.gnome.org/archives/desktop-devel-list/2017-April/msg00069.html 175class GnomeBackendOldName(GnomeBackend): 176 DBUS_NAME = "org.gnome.SettingsDaemon" 177 178 179class MateBackend(GnomeBackend): 180 181 DBUS_NAME = "org.mate.SettingsDaemon" 182 DBUS_PATH = "/org/mate/SettingsDaemon/MediaKeys" 183 DBUS_IFACE = "org.mate.SettingsDaemon.MediaKeys" 184