Ubuntu Mate panel custom applet in Python revisited

Links

GitHub - furas/mate-python-applets: Examples which shows how to create MATE panel applet with Python.
My first panel applet
Create an Ubuntu Application Indicator in Python: step-by-step guide

Environment
  • Ubuntu Mate 22.04.5 LTS
  • Mate DE 1.26.0
  • Python 3.10.12
Boilerplate: files and code

/usr/share/mate-panel/applets/org.mate.panel.TST00Applet.mate-panel-applet

[Applet Factory]
Id=TST00AppletFactory
InProcess=false
Location=/usr/lib/mate-applets/mate-ugn/
Name=TST00 Applet Factory
Description=Boilerplate code for a Mate Applet

[TST00Applet]
Name=TST00
Description=Boilerplate code for a Mate Applet
Icon=mate-desktop

/usr/share/dbus-1/services/org.mate.panel.applet.TST00AppletFactory.service

[D-BUS Service]
Name=org.mate.panel.applet.TST00AppletFactory
Exec=/usr/bin/env python3  /usr/lib/mate-applets/mate-ugn/tst00_applet.py

Notes:

  • TST00Applet is applet's iid
  • Pay your attention to files' and names' naming conventions
  • Location directory mate-ugn has been created to hold ugn's applets
  • Name and (optional) Icon and Description are shown in Add to panel... dialogue

/usr/lib/mate-applets/mate-dock-applet/dock_applet.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import gi
gi.require_version('Gtk', '3.0')
gi.require_version('MatePanelApplet', '4.0')
from gi.repository import Gtk
from gi.repository import MatePanelApplet

# Your functions go here

def build_applet(applet):
    # Your GTK code goes here
    
def applet_factory(applet, iid, data):
    if iid != "TST00Applet":
       return False
    build_applet(applet)
    return True

MatePanelApplet.Applet.factory_main("TST00AppletFactory", True,
                                MatePanelApplet.Applet.__gtype__,
                                applet_factory, None)
Development and debugging tips

It is expected that an applet is shown as a text, an icon or both on the panel. Besides that, it is expected that

  • text/icon dynamically represent current status of an object/process;
  • text/icon is a button to invoke a GUI dialogue window;
  • text/icon is a top-level menu entry

I would recommend to develop and debug applet's code as a standalone GTK application and convert it to applet when finished. The point is that you can run it from terminal and see as your "print'ed" messages, as interpreter's exceptions information. The conversion procedure is simple and straightforward given that main window for GTK application is created manually and the parent window for an applet is provided as parameter of build_applet function automagically.

The converted applet functionality can be verified by adding it to panel or with mate-panel-test-applets utility. The latter provides applet selection menu and starts chosen applet in separate dialogue window, not in the panel.

You can print your diagnostic messages into file like this:

with open('/<path to log file>/tst00.log', 'a') as f:
        print('tst00 - building', file=f)

Otherwise, you can use logging module to log exceptions and messages. See the following link for recipe: mate-python-applets/example-X-debug/testapplet.py at master · furas/mate-python-applets · GitHub

If you want your applet to react to <ctrl>-<c> key combination then add the following lines to the boilerplate code:

import signal
signal.signal(signal.SIGINT, signal.SIG_DFL)
  • Running applet in terminal helps to catch syntax errors and is useless otherwise.
  • While debugging, it is convenient to set Location and Exec keys in applet and service files to your project directory.
  • killall mate-panel command resets panel.
  • Mate System Monitor utility can be used to find and kill applet's process.
  • Command line can be used for the same purpose like this:
$ ps -ef | grep -i tst
ugn        61733   20699  0 13:52 pts/3    00:00:00 python3 tst00_applet.py
ugn        61833   61823  0 13:54 pts/1    00:00:00 grep --color=auto -i tst
$ kill 61733
Example: Dynamic text
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# Mate panel applet to alter its text label
# ugnvs 4 Ubuntu Mate forum

import gi
gi.require_version('Gtk', '3.0')
gi.require_version('MatePanelApplet', '4.0')
from gi.repository import Gtk
from gi.repository import GLib
from gi.repository import MatePanelApplet

status = None
tick = True

def update_status():
    global status
    global tick
    if tick:
        status.set_label('TST00-TICK')
    else:
        status.set_label('TST00-TOCK')
    tick = not tick
    GLib.timeout_add(4000, update_status)

def build_applet(applet):
    global status
    status = Gtk.Label("")
    applet.add(status)
    update_status()
    applet.show_all()
    
def applet_factory(applet, iid, data):
    result = (iid == "TST00Applet")
    if result:
        build_applet(applet)
    return result

MatePanelApplet.Applet.factory_main("TST00AppletFactory", True,
                            MatePanelApplet.Applet.__gtype__,
                            applet_factory, None)

#EOF
Example: CPU usage
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# Mate panel applet to measure CPU usage
# ugnvs 4 Ubuntu Mate forum

import gi
gi.require_version('Gtk', '3.0')
gi.require_version('MatePanelApplet', '4.0')
from gi.repository import Gtk
from gi.repository import GLib
from gi.repository import MatePanelApplet
import time

status = None

# Refer to https://www.kernel.org/doc/Documentation/filesystems/proc.txt
# for /proc/stat fields explanation

def update_status():
    global status
    with open('/proc/stat', 'r') as f:
        s=f.readline()
    # name, user, nice, system, idle, iowait, irq, sofirq, steal, guest, guest_nice
    s = s.split()
    idle1 = int(s[4])
    total1 = idle1 + int(s[1]) + int(s[2]) + int(s[3]) + int(s[6]) + int(s[7])
    time.sleep(1)
    with open('/proc/stat', 'r') as f:
        s=f.readline()
    s = s.split()
    idle2 = int(s[4])
    total2 = idle2 + int(s[1]) + int(s[2]) + int(s[3]) + int(s[6]) + int(s[7])
    busy = 1.0-(idle2-idle1)/(total2 - total1)
    txt = f'CPU: {busy:.0%}'
    status.set_label(txt)
    
    GLib.timeout_add(4000, update_status)

def build_applet(applet):
    global status
    status = Gtk.Label("")
    applet.add(status)
    update_status()
    applet.show_all()
    
def applet_factory(applet, iid, data):
    result = (iid == "TST00Applet")
    if result:
        build_applet(applet)
    return result

MatePanelApplet.Applet.factory_main("TST00AppletFactory", True,
                            MatePanelApplet.Applet.__gtype__,
                            applet_factory, None)

#EOF

Example: Internet host availability and notification

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# ping Google DNS host 8.8.4.4 and notify
# ugnvs 4 Ubuntu Mate forum

import gi
gi.require_version('Gtk', '3.0')
gi.require_version('MatePanelApplet', '4.0')
from gi.repository import Gtk
from gi.repository import GLib
from gi.repository import MatePanelApplet
import subprocess

status = None
lastprobe = False
ping_command='ping -4nq -c 1 8.8.4.4'

def update_status():
    global status
    global lastprobe
    
    res = subprocess.run(ping_command.split(' '), stdout=subprocess.DEVNULL,
                         stderr=subprocess.DEVNULL, capture_output=False)
    result = (res.returncode == 0)
    if result != lastprobe:
        lastprobe = result
        if result:
            txt = 'Ping OK'
            subprocess.run(['notify-send', 'Internet connection available'], stdout=subprocess.DEVNULL,
                         stderr=subprocess.DEVNULL, capture_output=False)
        else:
            txt = 'NO ping'
            subprocess.run(['notify-send', 'Internet connection degraded'], stdout=subprocess.DEVNULL,
                         stderr=subprocess.DEVNULL, capture_output=False)
        status.set_label(txt)
    
    GLib.timeout_add(4000, update_status)

def build_applet(applet):
    global status
    status = Gtk.Label("")
    applet.add(status)
    update_status()
    applet.show_all()
    
def applet_factory(applet, iid, data):
    result = (iid == "TST00Applet")
    if result:
        build_applet(applet)
    return result

MatePanelApplet.Applet.factory_main("TST00AppletFactory", True,
                            MatePanelApplet.Applet.__gtype__,
                            applet_factory, None)

#EOF

5 Likes

Excellent contribution, Eugene! I am sure to use this at some point.

1 Like