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)