82

Background:

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?

19
  • 2
    Could you make your question more specific? 'Help me fix it' with a lot of code sort of sets me off. Do you know where it is going wrong? Not sure we can help without a specific traceback, for instance. – don.joey Sep 30 '16 at 8:44
  • 3
    @ElderGeek, my main reason is indicator-sysmonitor which 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 a generic solution for all python projects that currently using appindicator ). – user.dz Jan 23 '17 at 21:37
  • 8
    So, basically, you want us to create a working port of your C code into Python... and you don't want to try porting it yourself? Not sure if that's ever going to be answered here, because you're basically wanting to hire a highly specialized Python programmer to write a highly-specialized piece of software/code for your indicators. System Indicators are completely different beasts from application indicators. – Thomas Ward Jan 23 '17 at 23:08
  • 6
    This question is very much specific to Ubuntu and development on Ubuntu. This is not some average 'hello world' stuff, and requires both knowledge of how Ubuntu is organized and programming with use of specific APIs. Comments about this being better for SO are not helpful – Sergiy Kolodyazhnyy Feb 22 '17 at 16:38
  • 6
    Since Ubuntu is going away from Unity, is this question then even relevant anymore? – beruic Dec 15 '17 at 13:20
4
+500

I've just uploaded a raw "working" Python example ported from the @user.dz C example. Here's the source code repository:

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)
4
  • Thank you very much Marto for sharing this. I had already made few trials but found it hard, without clear documentation (mix of old/legacy ones). I couldn't make any breakthrough. I need to review it well to understand passing Menu through DBus. – user.dz Nov 13 '20 at 13:22
  • 1
    I had to hardcode the DISPLAY environment variable because it is empty at the moment when the indicator is activated. I didn't find a better solution for this. If you can think of something to try, I will appreciate it – Marto Nov 13 '20 at 16:21
  • 1
    About passing Menu through DBus it's very similar to the C code, I think the major difference is that the Python libraries are object oriented so you have to adapt the code that way – Marto Nov 13 '20 at 16:27
  • Thanks for the hint. About DISPLAY, import Gdk too and after Gtk.init(), call Gdk.get_display() it is the equivalent of gdk_display_get_default() , I tested it in c, py, js, sometime ago also works for both X11 & Wayland. – user.dz Nov 13 '20 at 16:51
0

System Indicator Service

Well, it is really simpler then I expected. There is no specific API for it. Because it is just a GSimpleActionGroup & with corresponding GMenu's exported through DBus then Unity is told about their presence using declaration file with same name put in /usr/share/unity/indicators/. No need for any other library.

Here a very small C language example:

Get a copy of tests/indicator-test-service.c from libindicator source

apt-get source libindicator
cp libindicator-*/tests/indicator-test-service.c .
cp libindicator-*/tests/com.canonical.indicator.test*

. indicator-test-service.c no changes

**#include <gio/gio.h>

typedef struct
{
  GSimpleActionGroup *actions;
  GMenu *menu;

  guint actions_export_id;
  guint menu_export_id;
} IndicatorTestService;

static void
bus_acquired (GDBusConnection *connection,
              const gchar     *name,
              gpointer         user_data)
{
  IndicatorTestService *indicator = user_data;
  GError *error = NULL;

  indicator->actions_export_id = g_dbus_connection_export_action_group (connection,
                                                                        "/com/canonical/indicator/test",
                                                                        G_ACTION_GROUP (indicator->actions),
                                                                        &error);
  if (indicator->actions_export_id == 0)
    {
      g_warning ("cannot export action group: %s", error->message);
      g_error_free (error);
      return;
    }

  indicator->menu_export_id = g_dbus_connection_export_menu_model (connection,
                                                                   "/com/canonical/indicator/test/desktop",
                                                                   G_MENU_MODEL (indicator->menu),
                                                                   &error);
  if (indicator->menu_export_id == 0)
    {
      g_warning ("cannot export menu: %s", error->message);
      g_error_free (error);
      return;
    }
}

static void
name_lost (GDBusConnection *connection,
           const gchar     *name,
           gpointer         user_data)
{
  IndicatorTestService *indicator = user_data;

  if (indicator->actions_export_id)
    g_dbus_connection_unexport_action_group (connection, indicator->actions_export_id);

  if (indicator->menu_export_id)
    g_dbus_connection_unexport_menu_model (connection, indicator->menu_export_id);
}

static void
activate_show (GSimpleAction *action,
               GVariant      *parameter,
               gpointer       user_data)
{
  g_message ("showing");
}

int
main (int argc, char **argv)
{
  IndicatorTestService indicator = { 0 };
  GMenuItem *item;
  GMenu *submenu;
  GActionEntry entries[] = {
    { "_header", NULL, NULL, "{'label': <'Test'>,"
                             " 'icon': <'indicator-test'>,"
                             " 'accessible-desc': <'Test indicator'> }", NULL },
    { "show", activate_show, NULL, NULL, NULL }
  };
  GMainLoop *loop;

  indicator.actions = g_simple_action_group_new ();
  g_simple_action_group_add_entries (indicator.actions, entries, G_N_ELEMENTS (entries), NULL);

  submenu = g_menu_new ();
  g_menu_append (submenu, "Show", "indicator.show");
  item = g_menu_item_new (NULL, "indicator._header");
  g_menu_item_set_attribute (item, "x-canonical-type", "s", "com.canonical.indicator.root");
  g_menu_item_set_submenu (item, G_MENU_MODEL (submenu));
  indicator.menu = g_menu_new ();
  g_menu_append_item (indicator.menu, item);

  g_bus_own_name (G_BUS_TYPE_SESSION,
                  "com.canonical.indicator.test",
                  G_BUS_NAME_OWNER_FLAGS_NONE,
                  bus_acquired,
                  NULL,
                  name_lost,
                  &indicator,
                  NULL);

  loop = g_main_loop_new (NULL, FALSE);
  g_main_loop_run (loop);

  g_object_unref (submenu);
  g_object_unref (item);
  g_object_unref (indicator.actions);
  g_object_unref (indicator.menu);
  g_object_unref (loop);

  return 0;
}**

com.canonical.indicator.test modified to add lock & greeter mode

[Indicator Service]
Name=indicator-test
ObjectPath=/com/canonical/indicator/test

[desktop]
ObjectPath=/com/canonical/indicator/test/desktop

[desktop_greeter]
ObjectPath=/com/canonical/indicator/test/desktop

[desktop_lockscreen]
ObjectPath=/com/canonical/indicator/test/desktop

com.canonical.indicator.test.service remove .in postfix from filename and change the executable path

[D-BUS Service]
Name=com.canonical.indicator.test
Exec=/usr/lib/x86_64-linux-gnu/indicator-test/indicator-test-service

Compile it

gcc -o indicator-test-service indicator-test-service.c pkg-config --cflags --libs gtk+-3.0

Manual Installation

sudo su
mkdir /usr/lib/x86_64-linux-gnu/indicator-test/
cp indicator-test-service /usr/lib/x86_64-linux-gnu/indicator-test/
cp com.canonical.indicator.test /usr/share/unity/indicators/
cp com.canonical.indicator.test.service /usr/share/dbus-1/services/

Configuration for Greeter, override the default indicators list

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', 'com.canonical.indicator.test', 'application']

Install

cp 90_unity-greeter.gschema.override /usr/share/glib-2.0/schemas/
glib-compile-schemas /usr/share/glib-2.0/schemas/

Test

sudo service lightdm restart

Notes DBus service is troublesome, if you want user to be able to close application anytime. It is better to use autostart instead, like default indicators do.

I have uploaded ready files here:

https://github.com/sneetsher/mysystemindicator_minimum

and a modified copy here:

https://github.com/sneetsher/mysystemindicator

Where I have tried different menu for different mode. It could be installed and tested quickly.

This seems too simple and can be easily ported to any other language that have support for GIO Gnome lib (including DBus). As I'm looking for python, I may add it later.

References: libindicator README: The indicator service file format https://bazaar.launchpad.net/~indicator-applet-developers/libindicator/trunk.14.04/view/head:/README

3
  • Welcome to AskUbuntu. This is a copy of my answer here askubuntu.com/a/752750/26246 already linked in the question , it is in C language. But question was about Python version/port. Marto already posted a working solution, see the accepted answer. – user.dz Nov 20 '20 at 10:29
  • @user.dz had in mind about C, my bad – DCM Nov 21 '20 at 10:53
  • 1
    No problem @DCM, have nice day. – user.dz Nov 21 '20 at 11:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.