Skip to content

Commit

Permalink
melange: convert to GApplication, cleanup, fix launch-as-inspector
Browse files Browse the repository at this point in the history
mode.

- Use GDBus to provide the org.Cinnamon.Melange interface.
- Ensure LookingGlass proxy is ready before acting on any service
  calls, so that the 'inspect' flag works properly.
- Use proper signals in the LookingGlass proxy for catching
  notifications from Cinnamon.
- Turn more expensive requests to Cinnamon (inspect and log view)
  into asynchronous calls.
  • Loading branch information
mtwebster committed Nov 12, 2023
1 parent c279ee0 commit b885436
Show file tree
Hide file tree
Showing 8 changed files with 208 additions and 122 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -32,6 +29,19 @@
MELANGE_DBUS_NAME = "org.Cinnamon.Melange"
MELANGE_DBUS_PATH = "/org/Cinnamon/Melange"

melange_xml = """
<node>
<interface name="org.Cinnamon.Melange">
<method name="show" />
<method name="hide" />
<method name="getVisible">
<arg type="b" direction="out" name="visible"/>
</method>
</interface>
</node>
"""
interface_node_info = Gio.DBusNodeInfo.new_for_xml(melange_xml)

class MenuButton(Gtk.Button):
def __init__(self, text):
Gtk.Button.__init__(self, text)
Expand Down Expand Up @@ -308,48 +318,117 @@ 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:
self.window.present()
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")
Expand Down Expand Up @@ -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
Expand All @@ -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)

Expand Down Expand Up @@ -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):
Expand All @@ -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)
Expand All @@ -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)
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -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):
Expand All @@ -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:
Expand All @@ -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:
Expand Down
Loading

0 comments on commit b885436

Please sign in to comment.