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?

user.dz
  • 48,105
  • 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 08:44
  • 1
    @don.joey yeah i see that I put all details to create complete system indicator. You can skip all those configuration files. So try directly compiling tests/indicator-test-service.c and run it directly. Check its DBus menu structure using d-feet. Then write a python code or modify mine to create same structure in dbus menu. Don't hesitate to ask anything, even flowing your steps to test it. I got troubles in finding clear full python doc for GMenu & GSimpleActionGroup, only C doc is complete, python just a Glib Introspection binding. – user.dz Sep 30 '16 at 09:25
  • 2
    @user.dz Do you have the C code somewhere on GitHub ? – Sergiy Kolodyazhnyy Oct 04 '16 at 22:56
  • 1
    @Serg, yes , could find it here under Note section: https://askubuntu.com/a/752750/26246 – user.dz Oct 05 '16 at 05:13
  • 1
    Can I ask why you need it in python if you have a working solution in C? – Elder Geek Jan 23 '17 at 21:24
  • 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
  • 1
    Understood. I still use indicator-multiload (force of habit), I'll have to take a look at indicator-sysmonitorI can see the value of having this information on the greeter/lock/ubiquity screens. Watching with interest... :-) – Elder Geek Jan 23 '17 at 22:06
  • 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
  • @ThomasWard, Yeah, but actually I have tried porting it multiple times but I failed (question contains full code of one of my trials) . I agree, it seems that I went far with my dreams for a C-Python-DBUS-GLib angel :). Gnome maintains only C documentation (I miss binding documentation also I have somehow weak programming background). On other hand, Ubuntu didn't document system indicator API, as it seems they don't want much crap to leak into the panel (So they jailed appindicators in indicator-application). I like challenges, may be some bounty hunter beats me. – user.dz Jan 23 '17 at 23:35
  • If your question is about the code, wouldn't this question be a better fit for something like Stack Overflow if it's about C-Python since that's not specifically Ubuntu? – spark Jan 24 '17 at 15:19
  • 1
    @spark Actually it is Ubuntu specific, indicators are Canonical's invention for Unity DE. Due to its difficulty, I will try to rewrite another question in SO after bounty running out. Thank you. – user.dz Jan 24 '17 at 15:35
  • 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
  • This question could rationalize raising bounty limits from 500 to say 5,000. – WinEunuuchs2Unix Jan 17 '18 at 11:54
  • @beruic We've seen Wayland promised since 2010 and it appeared last year only to disappear (or put on the back burner) under Ubuntu 18.04 LTS which will include Unity 7.5 in addition to Gnome Desktop. That's not to say that Unity won't die, it's just to say it isn't dead yet. – WinEunuuchs2Unix Mar 19 '18 at 00:27
  • @Thomas You make me laugh, because that was indeed the same as i thought. It sounds funny tho. Because of 2 things. A. If the C thing he made is a good working program. Why make one in python ? And B, the only reason why you would do that is when you want to LEARN Python, in that case you ask for help or tips instead of "Could anyone make ..." To answer the question starter. What if I can, but I don't know C ? – An0n Mar 24 '18 at 02:20
  • @user.dz - Do you want this write in python? There are systems, which do not allow python, or have python disabled. Would it be not more effective, to write a shell-script for this? – dschinn1001 Jun 15 '18 at 16:34
  • @WinEunuuchs2Unix - Your kindness toward Linux Users, ... does not suite to your bounty rate. – dschinn1001 Jun 15 '18 at 16:37
  • @user.dz can you put this in a git repo? I would love to help you with this... – Joshua Besneatte Aug 17 '18 at 23:22

2 Answers2

4

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)

user.dz
  • 48,105
Marto
  • 656
  • 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

DCM
  • 16
  • Welcome to AskUbuntu. This is a copy of my answer here https://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