diff --git a/files/usr/share/cinnamon/cinnamon-looking-glass/cinnamon-looking-glass.py b/files/usr/share/cinnamon/cinnamon-looking-glass/cinnamon-looking-glass.py index 56575b9501..506427d376 100755 --- a/files/usr/share/cinnamon/cinnamon-looking-glass/cinnamon-looking-glass.py +++ b/files/usr/share/cinnamon/cinnamon-looking-glass/cinnamon-looking-glass.py @@ -15,9 +15,6 @@ import os import signal import sys -import dbus -import dbus.service -from dbus.mainloop.glib import DBusGMainLoop import pyinotify import gi gi.require_version('Gtk', '3.0') @@ -32,6 +29,19 @@ MELANGE_DBUS_NAME = "org.Cinnamon.Melange" MELANGE_DBUS_PATH = "/org/Cinnamon/Melange" +melange_xml = """ + + + + + + + + + +""" +interface_node_info = Gio.DBusNodeInfo.new_for_xml(melange_xml) + class MenuButton(Gtk.Button): def __init__(self, text): Gtk.Button.__init__(self, text) @@ -308,19 +318,97 @@ def __init__(self, label_text): def button_clicked(self, button, data=None): self.emit("close-clicked") -class MelangeApp(dbus.service.Object): +class MelangeApp(Gtk.Application): def __init__(self): - self.lg_proxy = LookingGlassProxy() - # The status label is shown iff we are not okay - self.lg_proxy.add_status_change_callback(lambda x: self.status_label.set_visible(not x)) + Gtk.Application.__init__(self, + application_id="org.Cinnamon.Melange", + register_session=True, + flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE, + inactivity_timeout=10 * 1000) self.window = None self._minimized = False - self.run() + self.reg_id = 0 + self.init_activation = True + self.startup_mode = None + + def do_dbus_register(self, connection, path): + self.reg_id = connection.register_object( + path, + interface_node_info.interfaces[0], + self._method_cb, + None, + None + ) + + return Gio.Application.do_dbus_register(self, connection, path) + + def do_dbus_unregister(self, connection, path): + if self.reg_id > 0: + connection.unregister_object(self.reg_id) + self.reg_id = 0 + + Gio.Application.do_dbus_unregister(self, connection, path) + + def _method_cb(self, connection, sender, path, interface, method, parameters, invocation, user_data=None): + if method == "show": + self._remote_show() + invocation.return_value(None) + elif method == "hide": + self._remote_hide() + invocation.return_value(None) + elif method == "getVisible": + visible = self.window.get_visible() if self.window is not None else False + invocation.return_value(GLib.Variant("(b)", (visible,))) + else: + print("Unhandled method: " + method) + + def handle_commandline_action(self): + if self.startup_mode is not None: + if self.startup_mode == "inspect": + self.inspect() + else: + pass # daemon, no activation + + self.startup_mode = None + return + self.activate() + + def do_command_line(self, command_line): + args = command_line.get_arguments() + + if not self.init_activation: + self.handle_commandline_action() + else: + if len(args) == 2: + self.startup_mode = args[1] + + return Gio.Application.do_command_line(self, command_line) + + def do_startup(self): + Gtk.Application.do_startup(self) + self.lg_proxy = LookingGlassProxy() + # The status label is shown iff we are not okay + self.lg_proxy.connect("status-changed", self.update_status_from_proxy) + + if self.window is None: + self.construct_window() + self.add_window(self.window) + + def update_status_from_proxy(self, proxy, online): + self.status_label.set_visible (not online) + if online and self.init_activation: + self.init_activation = False + self.handle_commandline_action() + + def do_activate(self): + Gtk.Application.do_activate(self) - dbus.service.Object.__init__(self, dbus.SessionBus(), MELANGE_DBUS_PATH, MELANGE_DBUS_NAME) + self.show() + + def _remote_show(self): + self.show() - @dbus.service.method(MELANGE_DBUS_NAME, in_signature='', out_signature='') def show(self): if self.window.get_visible(): if self._minimized: @@ -328,28 +416,19 @@ def show(self): else: self.window.hide() else: - self.show_and_focus() + self.window.show_all() + self.lg_proxy.refresh_status() + self.command_line.grab_focus() - @dbus.service.method(MELANGE_DBUS_NAME, in_signature='', out_signature='') - def hide(self): + def _remote_hide(self): self.window.hide() - @dbus.service.method(MELANGE_DBUS_NAME, in_signature='', out_signature='b') - def getVisible(self): - return self.window.get_visible() - - @dbus.service.method(MELANGE_DBUS_NAME, in_signature='', out_signature='') - def doInspect(self): + def inspect(self): if self.lg_proxy: self.lg_proxy.StartInspector() self.window.hide() - def show_and_focus(self): - self.window.show_all() - self.lg_proxy.refresh_status() - self.command_line.grab_focus() - - def run(self): + def construct_window(self): self.window = Gtk.Window(type=Gtk.WindowType.TOPLEVEL) self.window.set_title("Melange") self.window.set_icon_name("system-search") @@ -434,7 +513,7 @@ def run(self): column += 1 box = Gtk.HBox() - settings = Gio.Settings(schema="org.cinnamon.desktop.keybindings") + settings = Gio.Settings(schema_id="org.cinnamon.desktop.keybindings") arr = settings.get_strv("looking-glass-keybinding") if len(arr) > 0: # only the first mapped keybinding @@ -452,7 +531,7 @@ def run(self): table.attach(box, column, column+1, 1, 2, 0, 0, 1) - self.activate_page("results") + # self.activate_page("results") self.status_label.hide() self.window.set_focus(self.command_line) @@ -535,7 +614,7 @@ def on_delete(self, widget=None, event=None): tmp_pages = self.custom_pages.copy() for label, content in tmp_pages.items(): self.on_close_tab(label, content) - Gtk.main_quit() + self.quit() return False def on_window_state(self, widget, event): @@ -545,8 +624,7 @@ def on_window_state(self, widget, event): self._minimized = False def on_picker_clicked(self, widget): - self.lg_proxy.StartInspector() - self.window.hide() + self.inspect() def create_dummy_page(self, text, description): label = Gtk.Label(label=text) @@ -565,27 +643,13 @@ def activate_page(self, module_name): page = self.notebook.page_num(self.pages[module_name]) self.notebook.set_current_page(page) -def main(): - setproctitle("cinnamon-looking-glass") - DBusGMainLoop(set_as_default=True) - - session_bus = dbus.SessionBus() - request = session_bus.request_name(MELANGE_DBUS_NAME, dbus.bus.NAME_FLAG_DO_NOT_QUEUE) - if request != dbus.bus.REQUEST_NAME_REPLY_EXISTS: - app = MelangeApp() - else: - dbus_obj = session_bus.get_object(MELANGE_DBUS_NAME, MELANGE_DBUS_PATH) - app = dbus.Interface(dbus_obj, MELANGE_DBUS_NAME) - - daemon = len(sys.argv) == 2 and sys.argv[1] == "daemon" - inspect = len(sys.argv) == 2 and sys.argv[1] == "inspect" - - if inspect: - app.doInspect() - elif not daemon: - app.show() - - Gtk.main() + def do_shutdown(self): + self.window.destroy() + self.lg_proxy = None + Gtk.Application.do_shutdown(self) if __name__ == "__main__": - main() + setproctitle("cinnamon-looking-glass") + app = MelangeApp() + app.run(sys.argv) + exit(0) diff --git a/files/usr/share/cinnamon/cinnamon-looking-glass/lookingglass_proxy.py b/files/usr/share/cinnamon/cinnamon-looking-glass/lookingglass_proxy.py index 517b7ed3ef..120047b2ca 100644 --- a/files/usr/share/cinnamon/cinnamon-looking-glass/lookingglass_proxy.py +++ b/files/usr/share/cinnamon/cinnamon-looking-glass/lookingglass_proxy.py @@ -1,51 +1,57 @@ #!/usr/bin/python3 -from gi.repository import Gio +from gi.repository import Gio, GObject LG_DBUS_NAME = "org.Cinnamon.LookingGlass" LG_DBUS_PATH = "/org/Cinnamon/LookingGlass" +class LookingGlassProxy(GObject.Object): + __gsignals__ = { + 'status-changed': (GObject.SignalFlags.RUN_LAST, None, (bool, )), + "signal": (GObject.SignalFlags.RUN_LAST | GObject.SignalFlags.DETAILED, None, ()) + } -class LookingGlassProxy: def __init__(self): - self._signals = [] - self._status_change_callbacks = [] + GObject.Object.__init__(self) self._proxy = None + self.state = False Gio.bus_watch_name(Gio.BusType.SESSION, LG_DBUS_NAME, Gio.BusNameWatcherFlags.NONE, - self.on_connect, - self.on_disconnect) - - def add_status_change_callback(self, callback): - self._status_change_callbacks.append(callback) + self.on_bus_connect, + self.on_bus_disconnect) def refresh_status(self): - self.set_status(self._proxy is not None) + self.set_status(self.get_is_ready()) def get_is_ready(self): - return self._proxy is not None + return self._proxy is not None and self._proxy.get_name_owner() is not None + + def prepare_signal_name(self, signal): + out = signal[0].lower() + + for letter in signal[1:]: + out += ("-" if letter.isupper() else "") + letter.lower() - def connect(self, name, callback): - self._signals.append((name, callback)) + return "signal::" + out def on_signal(self, proxy, sender_name, signal_name, params): - for name, callback in self._signals: - if signal_name == name: - callback(*params) + detailed_name = self.prepare_signal_name(signal_name) + self.emit(detailed_name) def set_status(self, state): - for callback in self._status_change_callbacks: - callback(state) + if state != self.state: + self.state = state + self.emit("status-changed", state) - def on_connect(self, connection, name, owner): + def on_bus_connect(self, connection, name, owner): if self._proxy: return self.init_proxy() - def on_disconnect(self, connection, name): + def on_bus_disconnect(self, connection, name): self._proxy = None - self.set_status(False) + self.refresh_status() def init_proxy(self): try: @@ -59,13 +65,13 @@ def init_proxy(self): self.on_proxy_ready, None) except GLib.Error as e: - print(e.message) + print("Could not establish proxy with Cinnamon looking-glass interface: %s" % e.message) self._proxy = None def on_proxy_ready(self, obj, result, data=None): self._proxy = Gio.DBusProxy.new_for_bus_finish(result) self._proxy.connect("g-signal", self.on_signal) - self.set_status(True) + self.refresh_status() # Proxy Methods: def Eval(self, code): @@ -90,13 +96,15 @@ def AddResult(self, code): except Exception: pass - def GetErrorStack(self): + def GetErrorStack(self, result_cb): if self._proxy: try: - return self._proxy.GetErrorStack('()') + self._proxy.GetErrorStack('()', result_handler=result_cb, error_handler=self._get_error_stack_error_cb) except Exception: pass - return False, "" + + def _get_error_stack_error_cb(self, proxy, error): + print("Couldn't fetch the error stack: %s" % error.message) def GetMemoryInfo(self): if self._proxy: @@ -113,13 +121,15 @@ def FullGc(self): except Exception: pass - def Inspect(self, code): + def Inspect(self, code, result_cb, user_data=None): if self._proxy: try: - return self._proxy.Inspect('(s)', code) - except Exception: - pass - return False, "" + self._proxy.Inspect('(s)', code, result_handler=result_cb, error_handler=self._inspect_error_cb, user_data=user_data) + except Exception as e: + print(e) + + def _inspect_error_cb(self, proxy, error): + print("Couldn't inspect element: %s" % error.message) def GetLatestWindowList(self): if self._proxy: diff --git a/files/usr/share/cinnamon/cinnamon-looking-glass/page_extensions.py b/files/usr/share/cinnamon/cinnamon-looking-glass/page_extensions.py index df805e2e65..45d554e5db 100644 --- a/files/usr/share/cinnamon/cinnamon-looking-glass/page_extensions.py +++ b/files/usr/share/cinnamon/cinnamon-looking-glass/page_extensions.py @@ -15,9 +15,8 @@ def __init__(self, parent): self.create_text_column(1, "Type") self.create_text_column(2, "Name") self.create_text_column(3, "Description") - self.get_updates() - parent.lg_proxy.connect("ExtensionListUpdate", self.get_updates) - parent.lg_proxy.add_status_change_callback(self.on_status_change) + parent.lg_proxy.connect("signal::extension-list-update", self.get_updates) + parent.lg_proxy.connect("status-changed", self.on_status_change) self.tree_view.set_tooltip_column(8) self.popup = Gtk.Menu() @@ -81,11 +80,11 @@ def on_button_press_event(self, treeview, event): if error: self.parent.activate_page("log") - def on_status_change(self, online): + def on_status_change(self, proxy, online): if online: self.get_updates() - def get_updates(self): + def get_updates(self, proxy=None): success, data = self.parent.lg_proxy.GetExtensionList() if success: self.store.clear() diff --git a/files/usr/share/cinnamon/cinnamon-looking-glass/page_inspect.py b/files/usr/share/cinnamon/cinnamon-looking-glass/page_inspect.py index 04dddd926c..c54f0084ac 100644 --- a/files/usr/share/cinnamon/cinnamon-looking-glass/page_inspect.py +++ b/files/usr/share/cinnamon/cinnamon-looking-glass/page_inspect.py @@ -94,9 +94,9 @@ def __init__(self, parent): self.current_inspection = None self.stack = [] - self.parent.lg_proxy.add_status_change_callback(self.on_status_change) + self.parent.lg_proxy.connect("status-changed", self.on_status_change) - def on_status_change(self, online): + def on_status_change(self, proxy, online): if online: self.clear() @@ -142,20 +142,23 @@ def update_inspector(self, path, obj_type, name, value, push_to_stack=False): self.name_label.set_text(name) self.parent.activate_page("inspect") - success, data = self.parent.lg_proxy.Inspect(path) - if success: - try: - self.view.set_inspection_data(path, data) - except Exception as exc: - print(exc) - self.view.store.clear() - else: - self.view.store.clear() + self.parent.lg_proxy.Inspect(path, result_cb=self.inspect_finish_cb, user_data=path) elif obj_type in ("undefined", "null"): pageutils.ResultTextDialog("Value for '" + name + "'", "Value is <" + obj_type + ">") else: pageutils.ResultTextDialog("Value for " + obj_type + " '" + name + "'", value) + def inspect_finish_cb(self, proxy, result, path): + [success, data] = result + if success: + try: + self.view.set_inspection_data(path, data) + except Exception as e: + print("Inspect:", e) + self.view.store.clear() + else: + self.view.store.clear() + def inspect_element(self, path, obj_type, name, value): del self.stack[:] self.current_inspection = None diff --git a/files/usr/share/cinnamon/cinnamon-looking-glass/page_log.py b/files/usr/share/cinnamon/cinnamon-looking-glass/page_log.py index 42325aeaba..12f9bc1354 100644 --- a/files/usr/share/cinnamon/cinnamon-looking-glass/page_log.py +++ b/files/usr/share/cinnamon/cinnamon-looking-glass/page_log.py @@ -31,6 +31,7 @@ def __init__(self, proxy): self.log = [] self.added_messages = 0 self.first_message_time = None + self.need_reread = False context = self.get_style_context() @@ -57,9 +58,8 @@ def __init__(self, proxy): #self.enabled_types = {'info': False, 'warning': False, 'error': False, 'trace': False } #for key in data: # self.enabled_types[key] = True - self.get_updates() - self.proxy.connect("LogUpdate", self.get_updates) - self.proxy.add_status_change_callback(self.on_status_change) + self.proxy.connect("signal::log-update", self.get_updates) + self.proxy.connect("status-changed", self.on_status_change) def append(self, category, time, message): entry = LogEntry(category, time, message) @@ -72,7 +72,7 @@ def on_button_toggled(self, button, data): self.type_tags[data].props.invisible = not active self.textbuffer.set_modified(True) - def on_status_change(self, online): + def on_status_change(self, proxy, online): text_iter = self.textbuffer.get_end_iter() if online: entry = self.append("info", @@ -85,10 +85,16 @@ def on_status_change(self, online): self.textbuffer.insert_with_tags(text_iter, entry.formatted_text, self.type_tags[entry.category]) - self.get_updates(True) - def get_updates(self, reread=False): - success, data = self.proxy.GetErrorStack() + self.need_reread = True + self.get_updates() + + def get_updates(self, proxy=None): + self.proxy.GetErrorStack(result_cb=self.get_error_stack_finished) + + def get_error_stack_finished(self, proxy, result, user_data=None): + [success, data] = result + if success: try: data_size = len(data) @@ -97,14 +103,16 @@ def get_updates(self, reread=False): first_message_time = data[0]["timestamp"] if (self.added_messages > data_size or self.first_message_time != first_message_time or - reread): + self.need_reread): self.first_message_time = first_message_time self.added_messages = 0 - if reread: + if self.need_reread: start, end = self.textbuffer.get_bounds() self.textbuffer.delete(start, end) + self.need_reread = False + text_iter = self.textbuffer.get_end_iter() for item in data[self.added_messages:]: entry = self.append(item["category"], diff --git a/files/usr/share/cinnamon/cinnamon-looking-glass/page_results.py b/files/usr/share/cinnamon/cinnamon-looking-glass/page_results.py index bdc853d4c7..28a49de913 100644 --- a/files/usr/share/cinnamon/cinnamon-looking-glass/page_results.py +++ b/files/usr/share/cinnamon/cinnamon-looking-glass/page_results.py @@ -20,10 +20,9 @@ def __init__(self, parent): self.tree_view.connect("row-activated", self.on_row_activated) - self.get_updates() - self.parent.lg_proxy.connect("ResultUpdate", self.get_updates) - self.parent.lg_proxy.connect("InspectorDone", self.on_inspector_done) - self.parent.lg_proxy.add_status_change_callback(self.on_status_change) + self.parent.lg_proxy.connect("signal::result-update", self.get_updates) + self.parent.lg_proxy.connect("signal::inspector-done", self.on_inspector_done) + self.parent.lg_proxy.connect("status-changed", self.on_status_change) self._changed = False self.tree_view.connect("size-allocate", self.scroll_to_bottom) @@ -45,11 +44,11 @@ def on_row_activated(self, treeview, path, view_column): self.parent.pages["inspect"].inspect_element("r(%d)" % result_id, obj_type, name, value) - def on_status_change(self, online): + def on_status_change(self, proxy, online): if online: self.get_updates() - def get_updates(self): + def get_updates(self, proxy=None): self.store.clear() success, data = self.parent.lg_proxy.GetResults() if success: @@ -66,7 +65,7 @@ def get_updates(self): except Exception as exc: print(exc) - def on_inspector_done(self): + def on_inspector_done(self, proxy=None): self.parent.show() self.parent.activate_page("results") self.get_updates() diff --git a/files/usr/share/cinnamon/cinnamon-looking-glass/page_windows.py b/files/usr/share/cinnamon/cinnamon-looking-glass/page_windows.py index 9559e49741..f706dac101 100644 --- a/files/usr/share/cinnamon/cinnamon-looking-glass/page_windows.py +++ b/files/usr/share/cinnamon/cinnamon-looking-glass/page_windows.py @@ -16,9 +16,8 @@ def __init__(self, parent): self.create_text_column(2, "WMClass") self.create_text_column(3, "Application") - self.get_updates() - self.parent.lg_proxy.connect("WindowListUpdate", self.get_updates) - self.parent.lg_proxy.add_status_change_callback(self.on_status_change) + self.parent.lg_proxy.connect("signal::window-list-update", self.get_updates) + self.parent.lg_proxy.connect("status-changed", self.on_status_change) self.tree_view.connect("row-activated", self.on_row_activated) self.tree_view.connect("button-press-event", self.on_button_press) @@ -83,11 +82,11 @@ def on_button_press(self, treeview, event): self.popup.popup(None, None, None, None, event.button, event.time) return True - def on_status_change(self, online): + def on_status_change(self, proxy, online): if online: self.get_updates() - def get_updates(self): + def get_updates(self, proxy=None): self.store.clear() success, data = self.parent.lg_proxy.GetLatestWindowList() if success: diff --git a/js/ui/lookingGlass.js b/js/ui/lookingGlass.js index f83c9b56a5..a4bf96d9d6 100644 --- a/js/ui/lookingGlass.js +++ b/js/ui/lookingGlass.js @@ -122,8 +122,12 @@ function getObjKeysInfo(obj) { return Array.from(keys).map((k) => { if (!KEY_BLACKLIST.includes(k)) { - let [t, v] = getObjInfo(obj[k]); - return { name: k.toString(), type: t, value: v, shortValue: "" }; + try { + let [t, v] = getObjInfo(obj[k]); + return { name: k.toString(), type: t, value: v, shortValue: "" }; + } catch (e) { + return { name: k.toString(), type: '[inacessible]', value: '[inacessible]', shortValue: "" }; + } } else { return { name: k.toString(), type: '[inacessible]', value: '[inacessible]', shortValue: "" }; }