Question

I've got a Python 3 script that gets some JSON from a URL, processes it, and notifies me if there's any significant changes to the data I get. I've tried using notify2 and PyGObject's libnotify bindings (gi.repository.Notify) and get similar results with either method. This script works a-ok when I run it from a terminal, but chokes when cron tries to run it.

import notify2
from gi.repository import Notify

def notify_pygobject(new_stuff):
    Notify.init('My App')
    notify_str = '\n'.join(new_stuff)
    print(notify_str)
    popup = Notify.Notification.new('Hey! Listen!', notify_str,
                                    'dialog-information')
    popup.show()

def notify_notify2(new_stuff):
    notify2.init('My App')
    notify_str = '\n'.join(new_stuff)
    print(notify_str)
    popup = notify2.Notification('Hey! Listen!', notify_str,
                                 'dialog-information')
    popup.show()

Now, if I create a script that calls notify_pygobject with a list of strings, cron throws this error back at me via the mail spool:

Traceback (most recent call last):
  File "/home/p0lar_bear/Documents/devel/notify-test/test1.py", line 3, in <module>
    main()
  File "/home/p0lar_bear/Documents/devel/notify-test/test1.py", line 4, in main
    testlib.notify(notify_projects)
  File "/home/p0lar_bear/Documents/devel/notify-test/testlib.py", line 8, in notify
    popup.show()
  File "/usr/lib/python3/dist-packages/gi/types.py", line 113, in function
    return info.invoke(*args, **kwargs)
gi._glib.GError: Error spawning command line `dbus-launch --autolaunch=776643a88e264621544719c3519b8310 --binary-syntax --close-stderr': Child process exited with code 1

...and if I change it to call notify_notify2() instead:

Traceback (most recent call last):
  File "/home/p0lar_bear/Documents/devel/notify-test/test2.py", line 3, in <module>
    main()
  File "/home/p0lar_bear/Documents/devel/notify-test/test2.py", line 4, in main
    testlib.notify(notify_projects)
  File "/home/p0lar_bear/Documents/devel/notify-test/testlib.py", line 13, in notify
    notify2.init('My App')
  File "/usr/lib/python3/dist-packages/notify2.py", line 93, in init
    bus = dbus.SessionBus(mainloop=mainloop)
  File "/usr/lib/python3/dist-packages/dbus/_dbus.py", line 211, in __new__
    mainloop=mainloop)
  File "/usr/lib/python3/dist-packages/dbus/_dbus.py", line 100, in __new__
    bus = BusConnection.__new__(subclass, bus_type, mainloop=mainloop)
  File "/usr/lib/python3/dist-packages/dbus/bus.py", line 122, in __new__
    bus = cls._new_for_bus(address_or_type, mainloop=mainloop)
dbus.exceptions.DBusException: org.freedesktop.DBus.Error.NotSupported: Unable to autolaunch a dbus-daemon without a $DISPLAY for X11

I did some research and saw suggestions to put a PATH= into my crontab, or to export $DISPLAY (I did this within the script by calling os.system('export DISPLAY=:0')) but neither resulted in any change...

Was it helpful?

Solution

You are in the right track. This behavior is because cron is run in a multiuser headless environment (think of it as running as root in a terminal without GUI, kinda), so he doesn't know to what display (X Window Server session) and user target to. If your application open, for example, windows or notification to some user desktop, then this problems is raised.

I suppose you edit your cron with crontab -e and the entry looks like this:

m h dom mon dow command

Something like:

0 5 * * 1 /usr/bin/python /home/foo/myscript.py

Note that I use full path to Python, is better if this kind of situation where PATH environment variable could be different.

Then just change to:

0 5 * * 1 export DISPLAY=:0 && /usr/bin/python /home/foo/myscript.py

If this still doesn't work you need to allow your user to control the X Windows server:

Add to your .bash_rc:

xhost +si:localuser:$(whoami)

OTHER TIPS

If you want to set the DISPLAY from within python like you attempted with os.system('export DISPLAY=:0'), you can do something like this

import os

if not 'DISPLAY' in os.environ:
    os.environ['DISPLAY'] = ':0'

This will respect any DISPLAY that users may have on a multi-seat box, and fall back to the main head :0.

If ur notify function, regardless of Python version or notify library, does not track the notify id [in a Python list] and deleting the oldest before the queue is completely full or on error, then depending on the dbus settings (in Ubuntu it's 21 notification max) dbus will throw an error, maximum notifications reached!

from gi.repository import Notify
from gi.repository.GLib import GError

# Normally implemented as class variables.  
DBUS_NOTIFICATION_MAX = 21
lstNotify = []

def notify_show(strSummary, strBody, strIcon="dialog-information"):
    try:
        # full queue, delete oldest 
        if len(lstNotify)==DBUS_NOTIFICATION_MAX:
            #Get oldest id
            lngOldID = lstNotify.pop(0)
            Notify.Notification.clear(lngOldID)
            del lngOldID
            if len(lstNotify)==0:
                 lngLastID = 0
            else:
                lngLastID = lstNotify[len(lstNotify) -1] + 1
                lstNotify.append(lngLastID)
                notify = Notify.Notification.new(strSummary, strBody, strIcon)
                notify.set_property('id', lngLastID)
                print("notify_show id %(id)d " % {'id': notify.props.id} )
                #notify.set_urgency(Notify.URGENCY_LOW)
                notify.show()
    except GError as e:
        # Most likely exceeded max notifications
        print("notify_show error ", e )
    finally:
        if notify is not None:
            del notify

Although it may somehow be possible to ask dbus what the notification queue max limit is. Maybe someone can help ... Improve this until perfection.

Plz

Cuz gi.repository complete answers are spare to come by.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top