Ubuntu – Editing Gsettings unsuccesful when initiated from cron

crongsettingsscripts

I must have a blind spot, but I cannot find what it is.

I made a small python script that removes VLC from the sound menu. It runs perfectly in any way I run it from the terminal or from a launcher or whatever you can think of.

What the script actually does is nothing more than get the get the current settings:

gsettings get com.canonical.indicator.sound interested-media-players

edit the list, and set the changed list by:

gsettings set com.canonical.indicator.sound interested-media-players "['newlist']"

These commands are executed by a python script,. However, when run from a cronjob (crontab -e) only the gsettings – get – part works, but not the gsettings – set – section. That the get section works fine with cron, I checked by making the script write data (the original as well as the edited) to an external file.

Not a python problem

To see if the problem is related to python code, I created a bash script that applies a changed sound menu items list. The story is the same: the bash script runs fine from the command line or a launcher, not from cron, while any other command in the same script would run fine.
Also, if I add any command at the end of the script below, it works fine and it looks like the script is satisfied with its own work.

Why is the gsettings set command not working when initiated from cron?

This is the script:

#!/usr/bin/python3
import subprocess

def read_soundmenu():
    # read the current launcher contents
    get_menuitems = subprocess.Popen([
        "gsettings", "get", "com.canonical.indicator.sound", "interested-media-players"
        ], stdout=subprocess.PIPE)
    return eval((get_menuitems.communicate()[0].decode("utf-8")))

def set_current_menu(current_list): # this takes no effect from cron
    # preparing subprocess command string
    current_list = str(current_list).replace(", ", ",")
    subprocess.Popen([
        "gsettings", "set", "com.canonical.indicator.sound", "interested-media-players",
        current_list,
        ])

current_list = read_soundmenu()
for item in current_list:
    if item == "vlc.desktop":
        current_list.remove(item)
set_current_menu(current_list)

Best Answer

The solution

It turned out the blind spot was a hole in my knowledge. The reason for not running specific commands in the python script (gsettings set) was because cron uses a very restricted set of environment variables.

To run a gsettings *set* command from cron (in general), it takes more than just running it from your personal cron file; the environment variable DBUS_SESSION_BUS_ADDRESS is needed for correct execution.

For reasons of convenience and flexibility, I solved it, inspired by- and based on the information in this post on stack overflow, by creating an "intermediate" script that both exports the variable and calls the actual script. The actual script edits gsettings. Since (normally) a process inherits the environment of its parent, now the script runs fine.

#!/bin/bash

PID=$(pgrep gnome-session)
export DBUS_SESSION_BUS_ADDRESS=$(grep -z DBUS_SESSION_BUS_ADDRESS /proc/$PID/environ|cut -d= -f2-);/path/to/script.py

(assuming script.py is executable)

Including the DBUS_SESSION_BUS_ADDRESS variable in a python script

To make editing gsettings possible by a python script, run by cron (and make an intermediate script unnecessary) the function below could be included in the script. It should be called before the gsettings set function in the script.

#!/usr/bin/env python3
import os
import subprocess

def set_envir():
    pid = subprocess.check_output(["pgrep", "gnome-session"]).decode("utf-8").strip()
    cmd = "grep -z DBUS_SESSION_BUS_ADDRESS /proc/"+pid+"/environ|cut -d= -f2-"
    os.environ["DBUS_SESSION_BUS_ADDRESS"] = subprocess.check_output(
        ['/bin/bash', '-c', cmd]).decode("utf-8").strip().replace("\0", "")
Related Question