How do I write a Unity system indicator in Python?
Background:
-
This is NOT for application-indicators but system-indicators.
Picture from: https://wiki.ubuntu.com/DesktopExperienceTeam/ApplicationIndicators
-
The objective is to show indicator-sysmonitor in Greeter/Lock/Ubiquity screens. There is a work around in:
How to make indicator-sysmonitor as a default indicator on the login screen
C Original Code: (working fine)
-
I already get one working in C language, see my other question:
How to develop a System Indicator for Unity?
However,
indicator-sysmonitor
is already developed in Python as many other application indicators. I don't like the idea that developers obliged to port their projects to C or write a Python-C proxy if they want to show the indicator in greeter/lock/ubiquity screens. Instead, making indicator-sysmonitor creates a system indicator directly from python would be the best solution (no workarounds, and it will be a generic solution for all python projects that currently using appindicator).
Python Code: (My failed trial to port c code to python)
-
I'm struggling to port it into Python. Here is my current code which doesn't work. It does create DBus object for both Menu & Actions. It is listed in the XFCE indicators plugin. But not showed on the panel.
/usr/lib/indicator-test/indicator-test-service
#!/usr/bin/python2 import os import sys import gi from gi.repository import Gio, GLib APPLICATION_ID = 'local.sneetsher.indicator.test' DBUS_MENU_PATH = '/local/sneetsher/indicator/test/desktop' DBUS_ACTION_PATH = '/local/sneetsher/indicator/test' def callback(): print ok def quit_callback(notification, loop): global connection global exported_action_group_id global exported_menu_model_id connection.unexport_action_group (exported_action_group_id) connection.unexport_menu_model (exported_menu_model_id) loop.quit() def cancel (notification, action, data): if action == "cancel": print "Cancel" else: print "That should not have happened (cancel)!" def bus_acquired(bus, name): # menu submenu = Gio.Menu() submenu.append("Show", "show") item = Gio.MenuItem.new(None, "_header") item.set_attribute([("x-canonical-type","s","com.canonical.indicator.root")]) item.set_submenu(submenu) menu = Gio.Menu() menu.append_item (item) actions = Gio.SimpleActionGroup.new() action1 = Gio.SimpleAction.new("_header", None) actions.insert(action1) action2 = Gio.SimpleAction.new('show', None) actions.insert(action2) action2.connect("activate",callback) global connection connection = bus global exported_action_group_id exported_action_group_id = connection.export_action_group(DBUS_ACTION_PATH, actions) global exported_menu_model_id exported_menu_model_id = connection.export_menu_model(DBUS_MENU_PATH, menu) def setup (): #bus connection Gio.bus_own_name(Gio.BusType.SESSION, APPLICATION_ID, 0, bus_acquired, None, None) if __name__ == '__main__': connection = None exported_menu_model_id = 0 exported_action_group_id = 0 password = "" loop = GLib.MainLoop() setup () loop.run()
local.sneetsher.indicator.test
[Indicator Service] Name=indicator-test ObjectPath=/local/sneetsher/indicator/test [desktop] ObjectPath=/local/sneetsher/indicator/test/desktop [desktop_greeter] ObjectPath=/local/sneetsher/indicator/test/desktop [desktop_lockscreen] ObjectPath=/local/sneetsher/indicator/test/desktop
local.sneetsher.indicator.test.service
[D-BUS Service] Name=local.sneetsher.indicator.test Exec=/usr/lib/indicator-test/indicator-test-service
90_unity-greeter.gschema.override
[com.canonical.unity-greeter] indicators=['ug-accessibility', 'com.canonical.indicator.keyboard', 'com.canonical.indicator.session', 'com.canonical.indicator.datetime', 'com.canonical.indicator.power', 'com.canonical.indicator.sound', 'local.sneetsher.indicator.test', 'application']
Question:
I expect the reason why, I didn't create the menu structure or its meta (pseudo items like _header
) as they are in the original C code.
Could anyone make a working port of this system indicator code in C to Python?
I've just uploaded a raw "working" Python example ported from the @user.dz C example. Here's the source code repository:
- github.com/marto-ales/systemindicator
I will update it as I go along but any contribution is welcome.
Thanks for the useful information!
Ported source code of the main script. Note: It is initial copy as link may broke in future. For complete package and last update, follow the link above.
#!/usr/bin/python3
import sys
import os
import logging
import logging.handlers
logger = logging.getLogger('LoginHelper')
logger.setLevel(logging.DEBUG)
handler = logging.handlers.SysLogHandler(address = '/dev/log')
logger.addHandler(handler)
logger.debug("Login-Helper: Start")
os.environ["DISPLAY"] = ":0"
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import Gio
from gi.repository import GLib
class LoginHelperIndicator():
def __init__(self, dbus_name, dbus_path):
self.dbus_name = dbus_name
self.dbus_path = dbus_path
self.actions = Gio.SimpleActionGroup()
self.menu = Gio.Menu()
self.actions_export_id = 0
self.menu_export_id = 0
def activate_about (self, action, parameter):
# gtk_show_about_dialog(NULL,
# "program-name", PROJECT_NAME,
# "title", "About " PROJECT_NAME,
# "version", PROJECT_VERSION_MAJOR "." PROJECT_VERSION_MINOR,
# "license_type", GTK_LICENSE_GPL_3_0,
# "wrap_license", TRUE,
# "website", "https://github.com/sneetsher/mysystemindicator",
# "website_label", "https://github.com/sneetsher/mysystemindicator",
# "logo_icon_name", "indicator-" SHORT_NAME,
# NULL);
# g_message ("showing about dialog");
pass
def activate_private (self, action, parameter):
#g_message ("clicked private menu entry");
pass
def activate_exit (self, action, parameter):
#g_message ("exit the program");
Gtk.main_quit()
def on_bus_acquired (self, connection, name):
logger.debug ('Bus acquired: ' + str(connection))
error = None
item = Gio.MenuItem()
submenu = Gio.Menu()
action_state = GLib.Variant("a{sv}", {
'label': GLib.Variant("s", "Login-helper"),
'icon': GLib.Variant("s", "indicator-loginhelper"),
'accessible-desc': GLib.Variant("s", "Login Helper indicator")
})
self.actions.add_action(Gio.SimpleAction.new_stateful("_header", None, action_state))
about_action = Gio.SimpleAction.new("about", None)
about_action.connect("activate", self.activate_about)
self.actions.add_action(about_action)
private_action = Gio.SimpleAction.new("private", None)
private_action.connect("activate", self.activate_private)
self.actions.add_action(private_action)
exit_action = Gio.SimpleAction.new("exit", None)
exit_action.connect("activate", self.activate_exit)
self.actions.add_action(exit_action)
submenu.append("About", "indicator.about")
submenu.append("Exit", "indicator.exit")
item = Gio.MenuItem().new(None, "indicator._header")
item.set_attribute_value("x-canonical-type", GLib.Variant("s", "com.canonical.indicator.root"))
item.set_submenu(submenu)
self.menu = Gio.Menu.new()
self.menu.append_item(item)
self.actions_export_id = connection.export_action_group(self.dbus_path, self.actions)
if self.actions_export_id == 0:
#g_warning ("cannot export action group: %s", error->message);
#g_error_free (error);
return
self.menu_export_id = connection.export_menu_model(self.dbus_path + "/greeter", self.menu)
if self.menu_export_id == 0:
#g_warning ("cannot export menu: %s", error->message);
#g_error_free (error);
return
def on_name_lost (self, connection, name):
if self.actions_export_id:
connection.unexport_action_group(self.actions_export_id)
if (self.menu_export_id):
connection.unexport_menu_model(self.menu_export_id)
if __name__ == '__main__':
logger.debug('Login-Helper: Initializing Login Helper Indicator')
res_gtk = Gtk.init(sys.argv)
indicator = LoginHelperIndicator('com.canonical.indicator.loginhelper', '/com/canonical/indicator/loginhelper')
logger.debug ('Login-Helper: Res_gtk: ' + str(res_gtk))
res_own = Gio.bus_own_name(Gio.BusType.SESSION,
indicator.dbus_name,
Gio.BusNameOwnerFlags.NONE,
indicator.on_bus_acquired,
None,
indicator.on_name_lost)
logger.debug ('Login-Helper: Res_own: ' + str(res_own))
Gtk.main()
exit(0)