main.py
    
    Python script, ASCII text executable
        
            1
            """ 
        
            2
            Implements the org.freedesktop.StatusNotifierWatcher and nothing else. 
        
            3
            Copyright 2025, roundabout-host.com <vlad@roundabout-host.com> 
        
            4
             
        
            5
            This program is free software: you can redistribute it and/or modify 
        
            6
            it under the terms of the GNU General Public Licence as published by 
        
            7
            the Free Software Foundation, either version 3 of the Licence, or 
        
            8
            (at your option) any later version. 
        
            9
             
        
            10
            This program is distributed in the hope that it will be useful, 
        
            11
            but WITHOUT ANY WARRANTY; without even the implied warranty of 
        
            12
            MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
        
            13
            GNU General Public Licence for more details. 
        
            14
             
        
            15
            You should have received a copy of the GNU General Public Licence 
        
            16
            along with this program. If not, see <https://www.gnu.org/licenses/>. 
        
            17
            """ 
        
            18
             
        
            19
            import gi 
        
            20
            gi.require_version("Gtk", "4.0") 
        
            21
            from gi.repository import GLib, Gio, GObject, Gtk 
        
            22
            from pydbus import SessionBus 
        
            23
            import pydbus.generic 
        
            24
             
        
            25
             
        
            26
            class StatusNotifierWatcher: 
        
            27
                StatusNotifierItemRegistered = pydbus.generic.signal() 
        
            28
                StatusNotifierHostRegistered = pydbus.generic.signal() 
        
            29
                StatusNotifierItemUnregistered = pydbus.generic.signal() 
        
            30
             
        
            31
                def __init__(self, bus): 
        
            32
                    super().__init__() 
        
            33
                    self.hosts = set() 
        
            34
                    self.items = set() 
        
            35
                    self.bus = bus 
        
            36
             
        
            37
                    dbus_proxy = self.bus.get("org.freedesktop.DBus", "/org/freedesktop/DBus") 
        
            38
                    dbus_proxy.NameOwnerChanged.connect(self._on_name_owner_changed) 
        
            39
             
        
            40
                def _on_name_owner_changed(self, name, old_owner, new_owner): 
        
            41
                    if new_owner == "": 
        
            42
                        removed = False 
        
            43
                        for lookup in (name, old_owner): 
        
            44
                            if lookup in self.items: 
        
            45
                                self.items.discard(lookup) 
        
            46
                                self.StatusNotifierItemUnregistered(lookup) 
        
            47
                            if lookup in self.hosts: 
        
            48
                                self.hosts.discard(lookup) 
        
            49
             
        
            50
                def RegisterStatusNotifierItem(self, service_name): 
        
            51
                    self.items.add(service_name) 
        
            52
                    self.StatusNotifierItemRegistered(service_name) 
        
            53
             
        
            54
                def RegisterStatusNotifierHost(self, service_name): 
        
            55
                    self.hosts.add(service_name) 
        
            56
                    self.StatusNotifierHostRegistered(service_name) 
        
            57
             
        
            58
                @property 
        
            59
                def RegisteredStatusNotifierItems(self): 
        
            60
                    return sorted(self.items) 
        
            61
             
        
            62
                @property 
        
            63
                def IsStatusNotifierHostRegistered(self): 
        
            64
                    return bool(self.hosts) 
        
            65
             
        
            66
                @property 
        
            67
                def ProtocolVersion(self): 
        
            68
                    return 0 
        
            69
             
        
            70
             
        
            71
            class KDEStatusNotifierWatcher(StatusNotifierWatcher): 
        
            72
                """ 
        
            73
                <node> 
        
            74
                    <interface name="org.kde.StatusNotifierWatcher"> 
        
            75
                        <method name="RegisterStatusNotifierItem"> 
        
            76
                            <arg type="s" name="service_name" direction="in"/> 
        
            77
                        </method> 
        
            78
                        <method name="RegisterStatusNotifierHost"> 
        
            79
                            <arg type="s" name="service_name" direction="in"/> 
        
            80
                        </method> 
        
            81
                        <property name="RegisteredStatusNotifierItems" type="as" access="read"/> 
        
            82
                        <property name="IsStatusNotifierHostRegistered" type="b" access="read"/> 
        
            83
                        <property name="ProtocolVersion" type="i" access="read"/> 
        
            84
                        <signal name="StatusNotifierItemRegistered"> 
        
            85
                            <arg type="s" name="service" direction="out"/> 
        
            86
                        </signal> 
        
            87
                        <signal name="StatusNotifierItemUnregistered"> 
        
            88
                            <arg type="s" name="service" direction="out"/> 
        
            89
                        </signal> 
        
            90
                        <signal name="StatusNotifierHostRegistered"> 
        
            91
                            <arg type="s" name="service" direction="out"/> 
        
            92
                        </signal> 
        
            93
                    </interface> 
        
            94
                </node> 
        
            95
                """ 
        
            96
             
        
            97
             
        
            98
            class FDStatusNotifierWatcher(StatusNotifierWatcher): 
        
            99
                """ 
        
            100
                <node> 
        
            101
                    <interface name="org.freedesktop.StatusNotifierWatcher"> 
        
            102
                        <method name="RegisterStatusNotifierItem"> 
        
            103
                            <arg type="s" name="service_name" direction="in"/> 
        
            104
                        </method> 
        
            105
                        <method name="RegisterStatusNotifierHost"> 
        
            106
                            <arg type="s" name="service_name" direction="in"/> 
        
            107
                        </method> 
        
            108
                        <property name="RegisteredStatusNotifierItems" type="as" access="read"/> 
        
            109
                        <property name="IsStatusNotifierHostRegistered" type="b" access="read"/> 
        
            110
                        <property name="ProtocolVersion" type="i" access="read"/> 
        
            111
                        <signal name="StatusNotifierItemRegistered"> 
        
            112
                            <arg type="s" name="service" direction="out"/> 
        
            113
                        </signal> 
        
            114
                        <signal name="StatusNotifierItemUnregistered"> 
        
            115
                            <arg type="s" name="service" direction="out"/> 
        
            116
                        </signal> 
        
            117
                        <signal name="StatusNotifierHostRegistered"> 
        
            118
                            <arg type="s" name="service" direction="out"/> 
        
            119
                        </signal> 
        
            120
                    </interface> 
        
            121
                </node> 
        
            122
                """ 
        
            123
             
        
            124
             
        
            125
            class WatcherApplication(Gtk.Application): 
        
            126
                def __init__(self): 
        
            127
                    super().__init__(application_id="com.roundabout_host.roundabout.PanoramaIndicatorService", 
        
            128
                                     flags=Gio.ApplicationFlags.FLAGS_NONE) 
        
            129
                    self.bus = None 
        
            130
                    self.kde_publishing = None 
        
            131
                    self.fd_publishing = None 
        
            132
                    self.kde_watcher = None 
        
            133
                    self.fd_watcher = None 
        
            134
             
        
            135
                def do_startup(self): 
        
            136
                    Gtk.Application.do_startup(self) 
        
            137
                    self.bus = SessionBus() 
        
            138
                    self.kde_watcher = KDEStatusNotifierWatcher(self.bus) 
        
            139
                    self.fd_watcher = FDStatusNotifierWatcher(self.bus) 
        
            140
                    self.kde_publishing = self.bus.publish( 
        
            141
                        f"org.kde.StatusNotifierWatcher", 
        
            142
                        ("/StatusNotifierWatcher", self.kde_watcher) 
        
            143
                    ) 
        
            144
                    self.fd_publishing = self.bus.publish( 
        
            145
                        f"org.freedesktop.StatusNotifierWatcher", 
        
            146
                        ("/StatusNotifierWatcher", self.fd_watcher) 
        
            147
                    ) 
        
            148
             
        
            149
                def do_activate(self): 
        
            150
                    print("StatusNotifierWatcher running...") 
        
            151
                    self.hold() 
        
            152
             
        
            153
                def do_shutdown(self): 
        
            154
                    if self.bus: 
        
            155
                        if self.kde_publishing: 
        
            156
                            self.bus.unpublish(self.kde_publishing) 
        
            157
                        if self.fd_publishing: 
        
            158
                            self.bus.unpublish(self.fd_publishing) 
        
            159
                        print("Unregistered") 
        
            160
                    Gtk.Application.do_shutdown(self) 
        
            161
             
        
            162
             
        
            163
            if __name__ == "__main__": 
        
            164
                app = WatcherApplication() 
        
            165
                app.run(None) 
        
            166