1#!/usr/local/bin/python3.8
2import gi
3gi.require_version('Gtk', '3.0')
4gi.require_version('XApp', '1.0')
5from gi.repository import Gio, GLib, GObject, Gtk, XApp, Gdk
6import os
7import sys
8import json
9
10DBUS_NAME = "org.x.StatusIcon"
11DBUS_PATH = "/org/x/StatusIcon"
12
13class StatusWidget(Gtk.ToggleButton):
14    __gsignals__ = {
15        "re-sort": (GObject.SignalFlags.RUN_LAST, None, ())
16    }
17
18    def __init__(self, icon):
19        super(Gtk.ToggleButton, self).__init__()
20
21        self.proxy = icon
22        self.name = self.proxy.get_name()
23        self.add_events(Gdk.EventMask.SCROLL_MASK)
24
25        box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
26
27        self.image = Gtk.Image()
28        self.label = Gtk.Label()
29        box.pack_start(self.image, False, False, 6)
30        box.add(self.label)
31        self.add(box)
32
33        flags = GObject.BindingFlags.DEFAULT | GObject.BindingFlags.SYNC_CREATE
34
35        self.image.props.icon_size = Gtk.IconSize.DIALOG
36        self.set_icon(self.proxy.props.icon_name)
37
38        self.show_all()
39
40        self.proxy.bind_property("label", self.label, "label", flags)
41        self.proxy.bind_property("tooltip-text", self, "tooltip-markup", flags)
42        self.proxy.bind_property("visible", self, "visible", flags)
43
44        self.proxy.bind_property("primary-menu-is-open", self, "active", flags)
45        self.proxy.bind_property("secondary-menu-is-open", self, "active", flags)
46
47        self.proxy.connect("notify::icon-name", self.on_icon_name_changed)
48        self.proxy.connect("notify::name", self.on_name_changed)
49
50        self.connect("button-press-event", self.on_button_press)
51        self.connect("button-release-event", self.on_button_release)
52        self.connect("scroll-event", self.on_scroll)
53
54    def on_icon_name_changed(self, proxy, gparamspec, data=None):
55        string = self.proxy.props.icon_name
56
57        self.set_icon(string)
58
59    def on_name_changed(self, proxy, gparamspec, data=None):
60        self.emit("re-sort")
61
62    def set_icon(self, string):
63        if string:
64            if string and os.path.exists(string):
65                self.image.set_from_file(string)
66            else:
67                self.image.set_from_icon_name(string, Gtk.IconSize.DIALOG)
68        else:
69            self.image.set_from_icon_name("image-missing", Gtk.IconSize.DIALOG)
70
71    def on_button_press(self, widget, event):
72        # We're simulating a top panel here
73        alloc = widget.get_allocation()
74        ignore, x, y = widget.get_window().get_origin()
75
76        x += alloc.x
77        y += alloc.y + alloc.height
78        time = event.time
79        print ("Button press : %d:%d" % (x, y))
80        self.proxy.call_button_press_sync(x, y, event.button, event.time, Gtk.PositionType.TOP, None)
81
82    def on_button_release(self, widget, event):
83        # We're simulating a top panel here
84        alloc = widget.get_allocation()
85        ignore, x, y = widget.get_window().get_origin()
86
87        x += alloc.x
88        y += alloc.y + alloc.height
89        time = event.time
90        print ("Button release : %d:%d" % (x, y))
91        self.proxy.call_button_release_sync(x, y, event.button, event.time, Gtk.PositionType.TOP, None)
92
93    def on_scroll(self, widget, event):
94        has, direction = event.get_scroll_direction()
95
96        x_dir = XApp.ScrollDirection.UP
97        delta = 0
98
99        if direction != Gdk.ScrollDirection.SMOOTH:
100            x_dir = XApp.ScrollDirection(int(direction))
101
102            if direction == Gdk.ScrollDirection.UP:
103                delta = -1
104            elif direction == Gdk.ScrollDirection.DOWN:
105                delta = 1
106            elif direction == Gdk.ScrollDirection.LEFT:
107                delta = -1
108            elif direction == Gdk.ScrollDirection.RIGHT:
109                delta = 1
110
111        print ("Scroll : delta: %d, orientation: %d" % (delta, x_dir))
112        self.proxy.call_scroll_sync(delta, x_dir, event.time, None)
113
114class StatusApplet(GObject.Object):
115
116    def __init__(self):
117        super(StatusApplet, self).__init__()
118
119        self.window = Gtk.Window()
120        self.window.set_accept_focus (False)
121        self.window.connect("destroy", self.on_window_destroy)
122
123        self.main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL,
124                                margin=6,
125                                spacing=6)
126        self.window.add(self.main_box)
127
128        self.disco_button = Gtk.Button()
129        self.main_box.pack_start(self.disco_button, False, False, 0)
130        self.disco_button.connect("clicked", self.on_disco_button_clicked)
131
132        self.indicator_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL,
133                                     spacing=6)
134        self.main_box.pack_start(self.indicator_box, False, False, 0)
135
136        self.indicators = {}
137
138        self.monitor = None
139        self.setup_monitor()
140
141        self.window.show_all()
142
143    def on_window_destroy(self, widget, data=None):
144        self.destroy_monitor()
145        Gtk.main_quit()
146
147    def on_disco_button_clicked(self, widget, data=None):
148        if self.monitor == None:
149            self.setup_monitor ()
150        else:
151            self.destroy_monitor()
152
153    def setup_monitor (self):
154        self.monitor = XApp.StatusIconMonitor()
155        self.monitor.connect("icon-added", self.on_icon_added)
156        self.monitor.connect("icon-removed", self.on_icon_removed)
157
158        self.disco_button.set_label("Disconnect monitor")
159
160    def destroy_monitor (self):
161        for key in self.indicators.keys():
162            self.indicator_box.remove(self.indicators[key])
163
164        self.monitor = None
165        self.indicators = {}
166
167        self.disco_button.set_label("Connect monitor")
168
169    def on_icon_added(self, monitor, proxy):
170        name = proxy.get_name() + proxy.get_object_path()
171
172        self.indicators[name] = StatusWidget(proxy)
173        self.indicator_box.add(self.indicators[name])
174        self.indicators[name].connect("re-sort", self.sort_icons)
175
176        self.sort_icons()
177
178    def on_icon_removed(self, monitor, proxy):
179        name = proxy.get_name() + proxy.get_object_path()
180
181        self.indicator_box.remove(self.indicators[name])
182        self.indicators[name].disconnect_by_func(self.sort_icons)
183        del(self.indicators[name])
184
185        self.sort_icons()
186
187    def sort_icons(self, status_icon=None):
188        icon_list = list(self.indicators.values())
189
190        # for i in icon_list:
191        #     print("before: ", i.proxy.props.icon_name, i.proxy.props.name.lower())
192
193        icon_list.sort(key=lambda icon: icon.proxy.props.name.replace("org.x.StatusIcon.", "").lower())
194        icon_list.sort(key=lambda icon: icon.proxy.props.icon_name.lower().endswith("symbolic"))
195
196        # for i in icon_list:
197        #     print("after: ", i.proxy.props.icon_name, i.proxy.props.name.lower())
198
199        icon_list.reverse()
200
201        for icon in icon_list:
202            self.indicator_box.reorder_child(icon, 0)
203
204if __name__ == '__main__':
205    applet = StatusApplet()
206    Gtk.main()
207    sys.exit(0)