/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
*
* Copyright (C) 2007 Novell, Inc.
* Copyright (C) 2008 Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#define GNOME_DESKTOP_USE_UNSTABLE_API
#include
#ifdef HAVE_SYSTEMD
#ifdef ENABLE_SYSTEMD_JOURNAL
#include
#endif
#include
#endif
#include "gsm-autostart-app.h"
#include "gsm-util.h"
enum {
AUTOSTART_LAUNCH_SPAWN = 0,
AUTOSTART_LAUNCH_ACTIVATE
};
enum {
GSM_CONDITION_NONE = 0,
GSM_CONDITION_IF_EXISTS = 1,
GSM_CONDITION_UNLESS_EXISTS = 2,
GSM_CONDITION_GSETTINGS = 3,
GSM_CONDITION_IF_SESSION = 4,
GSM_CONDITION_UNLESS_SESSION = 5,
GSM_CONDITION_UNKNOWN = 6
};
#define GSM_SESSION_CLIENT_DBUS_INTERFACE "org.gnome.SessionClient"
typedef struct
{
gboolean mask_systemd;
char *desktop_filename;
char *desktop_id;
char *startup_id;
GDesktopAppInfo *app_info;
/* provides defined in session definition */
GSList *session_provides;
/* desktop file state */
char *condition_string;
gboolean condition;
gboolean autorestart;
GFileMonitor *condition_monitor;
guint condition_notify_id;
GSettings *condition_settings;
int launch_type;
GPid pid;
guint child_watch_id;
} GsmAutostartAppPrivate;
enum {
CONDITION_CHANGED,
LAST_SIGNAL
};
typedef enum {
PROP_DESKTOP_FILENAME = 1,
PROP_MASK_SYSTEMD,
} GsmAutostartAppProperty;
static GParamSpec *props[PROP_MASK_SYSTEMD + 1] = { NULL, };
static guint signals[LAST_SIGNAL] = { 0 };
static void gsm_autostart_app_initable_iface_init (GInitableIface *iface);
G_DEFINE_TYPE_WITH_CODE (GsmAutostartApp, gsm_autostart_app, GSM_TYPE_APP,
G_ADD_PRIVATE (GsmAutostartApp)
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, gsm_autostart_app_initable_iface_init))
static void
gsm_autostart_app_init (GsmAutostartApp *app)
{
GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app);
priv->pid = -1;
priv->condition_monitor = NULL;
priv->condition = FALSE;
}
static gboolean
is_disabled (GsmApp *app)
{
GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (GSM_AUTOSTART_APP (app));
/* GSM_AUTOSTART_APP_ENABLED_KEY key, used by old gnome-session */
if (g_desktop_app_info_has_key (priv->app_info,
GSM_AUTOSTART_APP_ENABLED_KEY) &&
!g_desktop_app_info_get_boolean (priv->app_info,
GSM_AUTOSTART_APP_ENABLED_KEY)) {
g_debug ("app %s is disabled by " GSM_AUTOSTART_APP_ENABLED_KEY,
gsm_app_peek_id (app));
return TRUE;
}
/* Hidden key, used by autostart spec */
if (g_desktop_app_info_get_is_hidden (priv->app_info)) {
g_debug ("app %s is disabled by Hidden",
gsm_app_peek_id (app));
return TRUE;
}
/* Check OnlyShowIn/NotShowIn/TryExec */
if (!g_desktop_app_info_get_show_in (priv->app_info, NULL)) {
g_debug ("app %s is not for the current desktop",
gsm_app_peek_id (app));
return TRUE;
}
/* Check if app is systemd enabled and mask-systemd is set. */
if (priv->mask_systemd &&
g_desktop_app_info_has_key (priv->app_info,
GSM_AUTOSTART_APP_SYSTEMD_KEY) &&
g_desktop_app_info_get_boolean (priv->app_info,
GSM_AUTOSTART_APP_SYSTEMD_KEY)) {
g_debug ("app %s is disabled by " GSM_AUTOSTART_APP_SYSTEMD_KEY,
gsm_app_peek_id (app));
return TRUE;
}
/* Do not check AutostartCondition - this method is only to determine
if the app is unconditionally disabled */
return FALSE;
}
static gboolean
parse_condition_string (const char *condition_string,
guint *condition_kindp,
char **keyp)
{
const char *space;
const char *key;
int len;
guint kind;
space = condition_string + strcspn (condition_string, " ");
len = space - condition_string;
key = space;
while (isspace ((unsigned char)*key)) {
key++;
}
kind = GSM_CONDITION_UNKNOWN;
if (!g_ascii_strncasecmp (condition_string, "if-exists", len) && key) {
kind = GSM_CONDITION_IF_EXISTS;
} else if (!g_ascii_strncasecmp (condition_string, "unless-exists", len) && key) {
kind = GSM_CONDITION_UNLESS_EXISTS;
} else if (!g_ascii_strncasecmp (condition_string, "GSettings", len)) {
kind = GSM_CONDITION_GSETTINGS;
} else if (!g_ascii_strncasecmp (condition_string, "GNOME3", len)) {
condition_string = key;
space = condition_string + strcspn (condition_string, " ");
len = space - condition_string;
key = space;
while (isspace ((unsigned char)*key)) {
key++;
}
if (!g_ascii_strncasecmp (condition_string, "if-session", len) && key) {
kind = GSM_CONDITION_IF_SESSION;
} else if (!g_ascii_strncasecmp (condition_string, "unless-session", len) && key) {
kind = GSM_CONDITION_UNLESS_SESSION;
}
}
if (kind == GSM_CONDITION_UNKNOWN) {
key = NULL;
}
if (keyp != NULL) {
*keyp = g_strdup (key);
}
if (condition_kindp != NULL) {
*condition_kindp = kind;
}
return (kind != GSM_CONDITION_UNKNOWN);
}
static void
if_exists_condition_cb (GFileMonitor *monitor,
GFile *file,
GFile *other_file,
GFileMonitorEvent event,
GsmApp *app)
{
GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (GSM_AUTOSTART_APP (app));
gboolean condition = FALSE;
switch (event) {
case G_FILE_MONITOR_EVENT_CREATED:
condition = TRUE;
break;
case G_FILE_MONITOR_EVENT_DELETED:
condition = FALSE;
break;
default:
/* Ignore any other monitor event */
return;
}
/* Emit only if the condition actually changed */
if (condition != priv->condition) {
priv->condition = condition;
g_signal_emit (app, signals[CONDITION_CHANGED], 0, condition);
}
}
static void
unless_exists_condition_cb (GFileMonitor *monitor,
GFile *file,
GFile *other_file,
GFileMonitorEvent event,
GsmApp *app)
{
GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (GSM_AUTOSTART_APP (app));
gboolean condition = FALSE;
switch (event) {
case G_FILE_MONITOR_EVENT_CREATED:
condition = FALSE;
break;
case G_FILE_MONITOR_EVENT_DELETED:
condition = TRUE;
break;
default:
/* Ignore any other monitor event */
return;
}
/* Emit only if the condition actually changed */
if (condition != priv->condition) {
priv->condition = condition;
g_signal_emit (app, signals[CONDITION_CHANGED], 0, condition);
}
}
static void
gsettings_condition_cb (GSettings *settings,
const char *key,
gpointer user_data)
{
GsmApp *app = GSM_APP (user_data);
GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (GSM_AUTOSTART_APP (app));
gboolean condition;
g_return_if_fail (GSM_IS_APP (user_data));
condition = g_settings_get_boolean (settings, key);
g_debug ("GsmAutostartApp: app:%s condition changed condition:%d",
gsm_app_peek_id (app),
condition);
/* Emit only if the condition actually changed */
if (condition != priv->condition) {
priv->condition = condition;
g_signal_emit (app, signals[CONDITION_CHANGED], 0, condition);
}
}
static gboolean
setup_gsettings_condition_monitor (GsmAutostartApp *app,
const char *key)
{
GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app);
GSettingsSchemaSource *source;
GSettingsSchema *schema;
GSettings *settings;
GSettingsSchemaKey *schema_key;
const GVariantType *key_type;
char **elems;
gboolean retval = FALSE;
char *signal;
retval = FALSE;
schema = NULL;
elems = g_strsplit (key, " ", 2);
if (elems == NULL)
goto out;
if (elems[0] == NULL || elems[1] == NULL)
goto out;
source = g_settings_schema_source_get_default ();
schema = g_settings_schema_source_lookup (source, elems[0], TRUE);
if (schema == NULL)
goto out;
if (!g_settings_schema_has_key (schema, elems[1]))
goto out;
schema_key = g_settings_schema_get_key (schema, elems[1]);
g_assert (schema_key != NULL);
key_type = g_settings_schema_key_get_value_type (schema_key);
g_settings_schema_key_unref (schema_key);
g_assert (key_type != NULL);
if (!g_variant_type_equal (key_type, G_VARIANT_TYPE_BOOLEAN))
goto out;
settings = g_settings_new_full (schema, NULL, NULL);
retval = g_settings_get_boolean (settings, elems[1]);
signal = g_strdup_printf ("changed::%s", elems[1]);
g_signal_connect (G_OBJECT (settings), signal,
G_CALLBACK (gsettings_condition_cb), app);
g_free (signal);
priv->condition_settings = settings;
out:
if (schema)
g_settings_schema_unref (schema);
g_strfreev (elems);
return retval;
}
static void
if_session_condition_cb (GObject *object,
GParamSpec *pspec,
gpointer user_data)
{
GsmApp *app = GSM_APP (user_data);
GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (GSM_AUTOSTART_APP (app));
char *session_name;
char *key;
gboolean condition;
g_return_if_fail (GSM_IS_APP (user_data));
parse_condition_string (priv->condition_string, NULL, &key);
g_object_get (object, "session-name", &session_name, NULL);
condition = strcmp (session_name, key) == 0;
g_free (session_name);
g_free (key);
g_debug ("GsmAutostartApp: app:%s condition changed condition:%d",
gsm_app_peek_id (app),
condition);
/* Emit only if the condition actually changed */
if (condition != priv->condition) {
priv->condition = condition;
g_signal_emit (app, signals[CONDITION_CHANGED], 0, condition);
}
}
static void
unless_session_condition_cb (GObject *object,
GParamSpec *pspec,
gpointer user_data)
{
GsmApp *app = GSM_APP (user_data);
GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (GSM_AUTOSTART_APP (app));
char *session_name;
char *key;
gboolean condition;
g_return_if_fail (GSM_IS_APP (user_data));
parse_condition_string (priv->condition_string, NULL, &key);
g_object_get (object, "session-name", &session_name, NULL);
condition = strcmp (session_name, key) != 0;
g_free (session_name);
g_free (key);
g_debug ("GsmAutostartApp: app:%s condition changed condition:%d",
gsm_app_peek_id (app),
condition);
/* Emit only if the condition actually changed */
if (condition != priv->condition) {
priv->condition = condition;
g_signal_emit (app, signals[CONDITION_CHANGED], 0, condition);
}
}
static char *
resolve_conditional_file_path (const char *file)
{
if (g_path_is_absolute (file))
return g_strdup (file);
return g_build_filename (g_get_user_config_dir (), file, NULL);
}
static void
setup_condition_monitor (GsmAutostartApp *app)
{
GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app);
guint kind;
char *key;
gboolean res;
gboolean disabled;
if (priv->condition_monitor != NULL) {
g_file_monitor_cancel (priv->condition_monitor);
}
if (priv->condition_string == NULL) {
return;
}
/* if it is disabled outright there is no point in monitoring */
if (is_disabled (GSM_APP (app))) {
return;
}
key = NULL;
res = parse_condition_string (priv->condition_string, &kind, &key);
if (! res) {
g_free (key);
return;
}
if (key == NULL) {
return;
}
if (kind == GSM_CONDITION_IF_EXISTS) {
char *file_path;
GFile *file;
file_path = resolve_conditional_file_path (key);
disabled = !g_file_test (file_path, G_FILE_TEST_EXISTS);
file = g_file_new_for_path (file_path);
priv->condition_monitor = g_file_monitor_file (file, 0, NULL, NULL);
g_signal_connect (priv->condition_monitor, "changed",
G_CALLBACK (if_exists_condition_cb),
app);
g_object_unref (file);
g_free (file_path);
} else if (kind == GSM_CONDITION_UNLESS_EXISTS) {
char *file_path;
GFile *file;
file_path = resolve_conditional_file_path (key);
disabled = g_file_test (file_path, G_FILE_TEST_EXISTS);
file = g_file_new_for_path (file_path);
priv->condition_monitor = g_file_monitor_file (file, 0, NULL, NULL);
g_signal_connect (priv->condition_monitor, "changed",
G_CALLBACK (unless_exists_condition_cb),
app);
g_object_unref (file);
g_free (file_path);
} else if (kind == GSM_CONDITION_GSETTINGS) {
disabled = !setup_gsettings_condition_monitor (app, key);
} else if (kind == GSM_CONDITION_IF_SESSION) {
GsmManager *manager;
char *session_name;
/* get the singleton */
manager = gsm_manager_get ();
g_object_get (manager, "session-name", &session_name, NULL);
disabled = strcmp (session_name, key) != 0;
g_signal_connect (manager, "notify::session-name",
G_CALLBACK (if_session_condition_cb), app);
g_free (session_name);
} else if (kind == GSM_CONDITION_UNLESS_SESSION) {
GsmManager *manager;
char *session_name;
/* get the singleton */
manager = gsm_manager_get ();
g_object_get (manager, "session-name", &session_name, NULL);
disabled = strcmp (session_name, key) == 0;
g_signal_connect (manager, "notify::session-name",
G_CALLBACK (unless_session_condition_cb), app);
g_free (session_name);
} else {
disabled = TRUE;
}
g_free (key);
if (disabled) {
/* FIXME: cache the disabled value? */
}
}
static void
load_desktop_file (GsmAutostartApp *app)
{
GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app);
char *dbus_name;
char *startup_id;
char *phase_str;
int phase;
gboolean res;
g_assert (priv->app_info != NULL);
phase_str = g_desktop_app_info_get_string (priv->app_info,
GSM_AUTOSTART_APP_PHASE_KEY);
if (phase_str != NULL) {
if (strcmp (phase_str, "EarlyInitialization") == 0) {
phase = GSM_MANAGER_PHASE_EARLY_INITIALIZATION;
} else if (strcmp (phase_str, "PreDisplayServer") == 0) {
phase = GSM_MANAGER_PHASE_PRE_DISPLAY_SERVER;
} else if (strcmp (phase_str, "DisplayServer") == 0) {
phase = GSM_MANAGER_PHASE_DISPLAY_SERVER;
} else if (strcmp (phase_str, "Initialization") == 0) {
phase = GSM_MANAGER_PHASE_INITIALIZATION;
} else if (strcmp (phase_str, "WindowManager") == 0) {
phase = GSM_MANAGER_PHASE_WINDOW_MANAGER;
} else if (strcmp (phase_str, "Panel") == 0) {
phase = GSM_MANAGER_PHASE_PANEL;
} else if (strcmp (phase_str, "Desktop") == 0) {
phase = GSM_MANAGER_PHASE_DESKTOP;
} else {
phase = GSM_MANAGER_PHASE_APPLICATION;
}
g_free (phase_str);
} else {
phase = GSM_MANAGER_PHASE_APPLICATION;
}
dbus_name = g_desktop_app_info_get_string (priv->app_info,
GSM_AUTOSTART_APP_DBUS_NAME_KEY);
if (dbus_name != NULL) {
priv->launch_type = AUTOSTART_LAUNCH_ACTIVATE;
} else {
priv->launch_type = AUTOSTART_LAUNCH_SPAWN;
}
/* this must only be done on first load */
switch (priv->launch_type) {
case AUTOSTART_LAUNCH_SPAWN:
startup_id =
g_desktop_app_info_get_string (priv->app_info,
GSM_AUTOSTART_APP_STARTUP_ID_KEY);
if (startup_id == NULL) {
startup_id = gsm_util_generate_startup_id ();
}
break;
case AUTOSTART_LAUNCH_ACTIVATE:
startup_id = g_strdup (dbus_name);
break;
default:
g_assert_not_reached ();
}
res = g_desktop_app_info_has_key (priv->app_info,
GSM_AUTOSTART_APP_AUTORESTART_KEY);
if (res) {
priv->autorestart = g_desktop_app_info_get_boolean (priv->app_info,
GSM_AUTOSTART_APP_AUTORESTART_KEY);
} else {
priv->autorestart = FALSE;
}
g_free (priv->condition_string);
priv->condition_string = g_desktop_app_info_get_string (priv->app_info,
"AutostartCondition");
setup_condition_monitor (app);
g_object_set (app,
"phase", phase,
"startup-id", startup_id,
NULL);
g_free (startup_id);
g_free (dbus_name);
}
static void
gsm_autostart_app_set_desktop_filename (GsmAutostartApp *app,
const char *desktop_filename)
{
GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app);
if (g_strcmp0 (priv->desktop_filename, desktop_filename) == 0)
return;
if (priv->app_info != NULL) {
g_clear_object (&priv->app_info);
g_clear_pointer (&priv->desktop_filename, g_free);
g_clear_pointer (&priv->desktop_id, g_free);
}
if (desktop_filename != NULL) {
priv->desktop_filename = g_strdup (desktop_filename);
priv->desktop_id = g_path_get_basename (desktop_filename);
}
g_object_notify_by_pspec (G_OBJECT (app), props[PROP_DESKTOP_FILENAME]);
}
static void
gsm_autostart_app_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GsmAutostartApp *self = GSM_AUTOSTART_APP (object);
GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (self);
switch ((GsmAutostartAppProperty) prop_id) {
case PROP_DESKTOP_FILENAME:
gsm_autostart_app_set_desktop_filename (self, g_value_get_string (value));
break;
case PROP_MASK_SYSTEMD:
if (priv->mask_systemd != g_value_get_boolean (value)) {
priv->mask_systemd = g_value_get_boolean (value);
g_object_notify_by_pspec (object, props[PROP_MASK_SYSTEMD]);
}
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gsm_autostart_app_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GsmAutostartApp *self = GSM_AUTOSTART_APP (object);
GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (self);
switch ((GsmAutostartAppProperty) prop_id) {
case PROP_DESKTOP_FILENAME:
if (priv->app_info != NULL) {
g_value_set_string (value, g_desktop_app_info_get_filename (priv->app_info));
} else {
g_value_set_string (value, NULL);
}
break;
case PROP_MASK_SYSTEMD:
g_value_set_boolean (value, priv->mask_systemd);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gsm_autostart_app_dispose (GObject *object)
{
GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (GSM_AUTOSTART_APP (object));
g_clear_pointer (&priv->startup_id, g_free);
if (priv->session_provides) {
g_slist_free_full (priv->session_provides, g_free);
priv->session_provides = NULL;
}
g_clear_pointer (&priv->condition_string, g_free);
g_clear_object (&priv->condition_settings);
g_clear_object (&priv->app_info);
g_clear_pointer (&priv->desktop_filename, g_free);
g_clear_pointer (&priv->desktop_id, g_free);
if (priv->child_watch_id > 0) {
g_source_remove (priv->child_watch_id);
priv->child_watch_id = 0;
}
if (priv->condition_monitor) {
g_file_monitor_cancel (priv->condition_monitor);
}
G_OBJECT_CLASS (gsm_autostart_app_parent_class)->dispose (object);
}
static gboolean
is_running (GsmApp *app)
{
GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (GSM_AUTOSTART_APP (app));
gboolean is;
/* is running if pid is still valid or
* or a client is connected
*/
/* FIXME: check client */
is = (priv->pid != -1);
return is;
}
static gboolean
is_conditionally_disabled (GsmApp *app)
{
GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (GSM_AUTOSTART_APP (app));
gboolean res;
gboolean disabled;
char *key;
guint kind;
/* Check AutostartCondition */
if (priv->condition_string == NULL) {
return FALSE;
}
key = NULL;
res = parse_condition_string (priv->condition_string, &kind, &key);
if (! res) {
g_free (key);
return TRUE;
}
if (key == NULL) {
return TRUE;
}
if (kind == GSM_CONDITION_IF_EXISTS) {
char *file_path;
file_path = resolve_conditional_file_path (key);
disabled = !g_file_test (file_path, G_FILE_TEST_EXISTS);
g_free (file_path);
} else if (kind == GSM_CONDITION_UNLESS_EXISTS) {
char *file_path;
file_path = resolve_conditional_file_path (key);
disabled = g_file_test (file_path, G_FILE_TEST_EXISTS);
g_free (file_path);
} else if (kind == GSM_CONDITION_GSETTINGS &&
priv->condition_settings != NULL) {
char **elems;
elems = g_strsplit (key, " ", 2);
disabled = !g_settings_get_boolean (priv->condition_settings, elems[1]);
g_strfreev (elems);
} else if (kind == GSM_CONDITION_IF_SESSION) {
GsmManager *manager;
char *session_name;
/* get the singleton */
manager = gsm_manager_get ();
g_object_get (manager, "session-name", &session_name, NULL);
disabled = strcmp (session_name, key) != 0;
g_free (session_name);
} else if (kind == GSM_CONDITION_UNLESS_SESSION) {
GsmManager *manager;
char *session_name;
/* get the singleton */
manager = gsm_manager_get ();
g_object_get (manager, "session-name", &session_name, NULL);
disabled = strcmp (session_name, key) == 0;
g_free (session_name);
} else {
disabled = TRUE;
}
/* Set initial condition */
priv->condition = !disabled;
g_free (key);
return disabled;
}
static void
app_exited (GPid pid,
int status,
GsmAutostartApp *app)
{
GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app);
g_debug ("GsmAutostartApp: (pid:%d) done (%s:%d)",
(int) pid,
WIFEXITED (status) ? "status"
: WIFSIGNALED (status) ? "signal"
: "unknown",
WIFEXITED (status) ? WEXITSTATUS (status)
: WIFSIGNALED (status) ? WTERMSIG (status)
: -1);
g_spawn_close_pid (priv->pid);
priv->pid = -1;
priv->child_watch_id = 0;
if (WIFEXITED (status)) {
gsm_app_exited (GSM_APP (app), WEXITSTATUS (status));
} else if (WIFSIGNALED (status)) {
gsm_app_died (GSM_APP (app), WTERMSIG (status));
}
}
static int
_signal_pid (int pid,
int signal)
{
int status;
/* perhaps block sigchld */
g_debug ("GsmAutostartApp: sending signal %d to process %d", signal, pid);
errno = 0;
status = kill (pid, signal);
if (status < 0) {
if (errno == ESRCH) {
g_warning ("Child process %d was already dead.",
(int)pid);
} else {
g_warning ("Couldn't kill child process %d: %s",
pid,
g_strerror (errno));
}
}
/* perhaps unblock sigchld */
return status;
}
static gboolean
autostart_app_stop_spawn (GsmAutostartApp *app,
GError **error)
{
GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app);
int res;
if (priv->pid < 1) {
g_set_error (error,
GSM_APP_ERROR,
GSM_APP_ERROR_STOP,
"Not running");
return FALSE;
}
res = _signal_pid (priv->pid, SIGTERM);
if (res != 0) {
g_set_error (error,
GSM_APP_ERROR,
GSM_APP_ERROR_STOP,
"Unable to stop: %s",
g_strerror (errno));
return FALSE;
}
return TRUE;
}
static gboolean
autostart_app_stop_activate (GsmAutostartApp *app,
GError **error)
{
return TRUE;
}
static gboolean
gsm_autostart_app_stop (GsmApp *app,
GError **error)
{
GsmAutostartApp *self = GSM_AUTOSTART_APP (app);
GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (self);
gboolean ret;
g_return_val_if_fail (priv->app_info != NULL, FALSE);
switch (priv->launch_type) {
case AUTOSTART_LAUNCH_SPAWN:
ret = autostart_app_stop_spawn (self, error);
break;
case AUTOSTART_LAUNCH_ACTIVATE:
ret = autostart_app_stop_activate (self, error);
break;
default:
g_assert_not_reached ();
break;
}
return ret;
}
static void
app_launched (GAppLaunchContext *ctx,
GAppInfo *appinfo,
GVariant *platform_data,
gpointer data)
{
GsmAutostartApp *app = data;
GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app);
gint pid;
gchar *sn_id;
pid = 0;
sn_id = NULL;
g_variant_lookup (platform_data, "pid", "i", &pid);
g_variant_lookup (platform_data, "startup-notification-id", "s", &sn_id);
priv->pid = pid;
priv->startup_id = sn_id;
/* We are not interested in the result. */
gnome_start_systemd_scope (priv->desktop_id,
pid,
NULL,
NULL,
NULL, NULL, NULL);
}
#ifdef ENABLE_SYSTEMD_JOURNAL
static void
on_child_setup (GsmAutostartApp *app)
{
GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app);
int standard_output, standard_error;
/* The FALSE means programs aren't expected to prefix each
* line with prefix to specify priority.
*/
standard_output = sd_journal_stream_fd (priv->desktop_id,
LOG_INFO,
FALSE);
standard_error = sd_journal_stream_fd (priv->desktop_id,
LOG_WARNING,
FALSE);
if (standard_output >= 0) {
dup2 (standard_output, STDOUT_FILENO);
close (standard_output);
}
if (standard_error >= 0) {
dup2 (standard_error, STDERR_FILENO);
close (standard_error);
}
}
#endif
static gboolean
autostart_app_start_spawn (GsmAutostartApp *app,
GError **error)
{
GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app);
gboolean success;
GError *local_error;
const char *startup_id;
const char * const *variable_blacklist;
const char * const *child_environment;
int i;
GAppLaunchContext *ctx;
GSpawnChildSetupFunc child_setup_func = NULL;
gpointer child_setup_data = NULL;
guint handler;
startup_id = gsm_app_peek_startup_id (GSM_APP (app));
g_assert (startup_id != NULL);
g_debug ("GsmAutostartApp: starting %s: command=%s startup-id=%s", priv->desktop_id, g_app_info_get_commandline (G_APP_INFO (priv->app_info)), startup_id);
g_free (priv->startup_id);
local_error = NULL;
ctx = g_app_launch_context_new ();
variable_blacklist = gsm_util_get_variable_blacklist ();
for (i = 0; variable_blacklist[i] != NULL; i++)
g_app_launch_context_unsetenv (ctx, variable_blacklist[i]);
child_environment = gsm_util_listenv ();
for (i = 0; child_environment[i] != NULL; i++) {
char **environment_tuple;
const char *key;
const char *value;
environment_tuple = g_strsplit (child_environment[i], "=", 2);
key = environment_tuple[0];
value = environment_tuple[1];
if (value != NULL)
g_app_launch_context_setenv (ctx, key, value);
g_strfreev (environment_tuple);
}
if (startup_id != NULL) {
g_app_launch_context_setenv (ctx, "DESKTOP_AUTOSTART_ID", startup_id);
}
#ifdef ENABLE_SYSTEMD_JOURNAL
if (sd_booted () > 0) {
child_setup_func = (GSpawnChildSetupFunc) on_child_setup;
child_setup_data = app;
}
#endif
handler = g_signal_connect (ctx, "launched", G_CALLBACK (app_launched), app);
success = g_desktop_app_info_launch_uris_as_manager (priv->app_info,
NULL,
ctx,
G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH,
child_setup_func, child_setup_data,
NULL, NULL,
&local_error);
g_signal_handler_disconnect (ctx, handler);
if (success) {
if (priv->pid > 0) {
g_debug ("GsmAutostartApp: started pid:%d", priv->pid);
priv->child_watch_id = g_child_watch_add (priv->pid,
(GChildWatchFunc)app_exited,
app);
}
} else {
g_set_error (error,
GSM_APP_ERROR,
GSM_APP_ERROR_START,
"Unable to start application: %s", local_error->message);
g_error_free (local_error);
}
return success;
}
static void
start_notify (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
GError *error;
GsmAutostartApp *app = GSM_AUTOSTART_APP (user_data);
GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app);
error = NULL;
g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error);
if (error != NULL) {
g_warning ("GsmAutostartApp: Error starting application: %s", error->message);
g_error_free (error);
} else {
g_debug ("GsmAutostartApp: Started application %s", priv->desktop_id);
}
}
static gboolean
autostart_app_start_activate (GsmAutostartApp *app,
GError **error)
{
GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app);
const char *name;
char *path;
char *arguments;
GDBusConnection *bus;
GError *local_error;
local_error = NULL;
bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &local_error);
if (local_error != NULL) {
g_warning ("error getting session bus: %s", local_error->message);
g_propagate_error (error, local_error);
return FALSE;
}
name = gsm_app_peek_startup_id (GSM_APP (app));
g_assert (name != NULL);
path = g_desktop_app_info_get_string (priv->app_info,
GSM_AUTOSTART_APP_DBUS_PATH_KEY);
if (path == NULL) {
/* just pick one? */
path = g_strdup ("/");
}
arguments = g_desktop_app_info_get_string (priv->app_info,
GSM_AUTOSTART_APP_DBUS_ARGS_KEY);
g_dbus_connection_call (bus,
name,
path,
GSM_SESSION_CLIENT_DBUS_INTERFACE,
"Start",
g_variant_new ("(s)", arguments),
NULL,
G_DBUS_CALL_FLAGS_NONE,
-1, NULL,
start_notify, app);
g_object_unref (bus);
return TRUE;
}
static gboolean
gsm_autostart_app_start (GsmApp *app,
GError **error)
{
GsmAutostartApp *self = GSM_AUTOSTART_APP (app);
GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (self);
gboolean ret;
g_return_val_if_fail (priv->app_info != NULL, FALSE);
switch (priv->launch_type) {
case AUTOSTART_LAUNCH_SPAWN:
ret = autostart_app_start_spawn (self, error);
break;
case AUTOSTART_LAUNCH_ACTIVATE:
ret = autostart_app_start_activate (self, error);
break;
default:
g_assert_not_reached ();
break;
}
return ret;
}
static gboolean
gsm_autostart_app_restart (GsmApp *app,
GError **error)
{
GError *local_error;
gboolean res;
/* ignore stop errors - it is fine if it is already stopped */
local_error = NULL;
res = gsm_app_stop (app, &local_error);
if (! res) {
g_debug ("GsmAutostartApp: Couldn't stop app: %s", local_error->message);
g_error_free (local_error);
}
res = gsm_app_start (app, &local_error);
if (! res) {
g_propagate_error (error, local_error);
return FALSE;
}
return TRUE;
}
static gboolean
gsm_autostart_app_provides (GsmApp *app,
const char *service)
{
gchar *provides_str;
char **provides;
gsize i;
GSList *l;
GsmAutostartApp *self = GSM_AUTOSTART_APP (app);
GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (self);
g_return_val_if_fail (GSM_IS_APP (app), FALSE);
if (priv->app_info == NULL) {
return FALSE;
}
for (l = priv->session_provides; l != NULL; l = l->next) {
if (!strcmp (l->data, service))
return TRUE;
}
provides_str = g_desktop_app_info_get_string (priv->app_info,
GSM_AUTOSTART_APP_PROVIDES_KEY);
if (!provides_str) {
return FALSE;
}
provides = g_strsplit (provides_str, ";", -1);
g_free (provides_str);
for (i = 0; provides[i]; i++) {
if (!strcmp (provides[i], service)) {
g_strfreev (provides);
return TRUE;
}
}
g_strfreev (provides);
return FALSE;
}
static char **
gsm_autostart_app_get_provides (GsmApp *app)
{
GsmAutostartApp *self = GSM_AUTOSTART_APP (app);
GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (self);
gchar *provides_str;
char **provides;
gsize provides_len;
char **result;
gsize result_len;
int i;
GSList *l;
g_return_val_if_fail (GSM_IS_APP (app), NULL);
if (priv->app_info == NULL) {
return NULL;
}
provides_str = g_desktop_app_info_get_string (priv->app_info,
GSM_AUTOSTART_APP_PROVIDES_KEY);
if (provides_str == NULL) {
return NULL;
}
provides = g_strsplit (provides_str, ";", -1);
provides_len = g_strv_length (provides);
g_free (provides_str);
if (!priv->session_provides) {
return provides;
}
result_len = provides_len + g_slist_length (priv->session_provides);
result = g_new (char *, result_len + 1); /* including last NULL */
for (i = 0; provides[i] != NULL; i++)
result[i] = provides[i];
g_free (provides);
for (l = priv->session_provides; l != NULL; l = l->next, i++)
result[i] = g_strdup (l->data);
result[i] = NULL;
g_assert (i == result_len);
return result;
}
void
gsm_autostart_app_add_provides (GsmAutostartApp *aapp,
const char *provides)
{
GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (aapp);
g_return_if_fail (GSM_IS_AUTOSTART_APP (aapp));
priv->session_provides = g_slist_prepend (priv->session_provides,
g_strdup (provides));
}
static gboolean
gsm_autostart_app_has_autostart_condition (GsmApp *app,
const char *condition)
{
GsmAutostartApp *self = GSM_AUTOSTART_APP (app);
GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (self);
g_return_val_if_fail (GSM_IS_APP (app), FALSE);
g_return_val_if_fail (condition != NULL, FALSE);
if (priv->condition_string == NULL) {
return FALSE;
}
if (strcmp (priv->condition_string, condition) == 0) {
return TRUE;
}
return FALSE;
}
static gboolean
gsm_autostart_app_get_autorestart (GsmApp *app)
{
GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (GSM_AUTOSTART_APP (app));
gboolean res;
gboolean autorestart;
if (priv->app_info == NULL) {
return FALSE;
}
autorestart = FALSE;
res = g_desktop_app_info_has_key (priv->app_info,
GSM_AUTOSTART_APP_AUTORESTART_KEY);
if (res) {
autorestart = g_desktop_app_info_get_boolean (priv->app_info,
GSM_AUTOSTART_APP_AUTORESTART_KEY);
}
return autorestart;
}
static const char *
gsm_autostart_app_get_app_id (GsmApp *app)
{
GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (GSM_AUTOSTART_APP (app));
if (priv->app_info == NULL) {
return NULL;
}
return g_app_info_get_id (G_APP_INFO (priv->app_info));
}
static gboolean
gsm_autostart_app_initable_init (GInitable *initable,
GCancellable *cancellable,
GError **error)
{
GsmAutostartApp *app = GSM_AUTOSTART_APP (initable);
GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app);
g_assert (priv->desktop_filename != NULL);
priv->app_info = g_desktop_app_info_new_from_filename (priv->desktop_filename);
if (priv->app_info == NULL) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Could not parse desktop file %s or it references a not found TryExec binary", priv->desktop_id);
return FALSE;
}
load_desktop_file (app);
return TRUE;
}
static gboolean
gsm_autostart_app_save_to_keyfile (GsmApp *base_app,
GKeyFile *keyfile,
GError **error)
{
GsmAutostartApp *app = GSM_AUTOSTART_APP (base_app);
GsmAutostartAppPrivate *priv = gsm_autostart_app_get_instance_private (app);
char **provides = NULL;
char *dbus_name;
char *phase;
gboolean res;
provides = gsm_app_get_provides (base_app);
if (provides != NULL) {
g_key_file_set_string_list (keyfile,
G_KEY_FILE_DESKTOP_GROUP,
GSM_AUTOSTART_APP_PROVIDES_KEY,
(const char * const *)
provides,
g_strv_length (provides));
g_strfreev (provides);
}
phase = g_desktop_app_info_get_string (priv->app_info,
GSM_AUTOSTART_APP_PHASE_KEY);
if (phase != NULL) {
g_key_file_set_string (keyfile,
G_KEY_FILE_DESKTOP_GROUP,
GSM_AUTOSTART_APP_PHASE_KEY,
phase);
g_free (phase);
}
dbus_name = g_desktop_app_info_get_string (priv->app_info,
GSM_AUTOSTART_APP_DBUS_NAME_KEY);
if (dbus_name != NULL) {
g_key_file_set_string (keyfile,
G_KEY_FILE_DESKTOP_GROUP,
GSM_AUTOSTART_APP_DBUS_NAME_KEY,
dbus_name);
g_free (dbus_name);
}
res = g_desktop_app_info_has_key (priv->app_info,
GSM_AUTOSTART_APP_AUTORESTART_KEY);
if (res) {
g_key_file_set_boolean (keyfile,
G_KEY_FILE_DESKTOP_GROUP,
GSM_AUTOSTART_APP_AUTORESTART_KEY,
g_desktop_app_info_get_boolean (priv->app_info,
GSM_AUTOSTART_APP_AUTORESTART_KEY));
}
res = g_desktop_app_info_has_key (priv->app_info,
GSM_AUTOSTART_APP_AUTORESTART_KEY);
if (res) {
char *autostart_condition;
autostart_condition = g_desktop_app_info_get_string (priv->app_info, "AutostartCondition");
g_key_file_set_string (keyfile,
G_KEY_FILE_DESKTOP_GROUP,
"AutostartCondition",
autostart_condition);
g_free (autostart_condition);
}
return TRUE;
}
static void
gsm_autostart_app_initable_iface_init (GInitableIface *iface)
{
iface->init = gsm_autostart_app_initable_init;
}
static void
gsm_autostart_app_class_init (GsmAutostartAppClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GsmAppClass *app_class = GSM_APP_CLASS (klass);
object_class->set_property = gsm_autostart_app_set_property;
object_class->get_property = gsm_autostart_app_get_property;
object_class->dispose = gsm_autostart_app_dispose;
app_class->impl_is_disabled = is_disabled;
app_class->impl_is_conditionally_disabled = is_conditionally_disabled;
app_class->impl_is_running = is_running;
app_class->impl_start = gsm_autostart_app_start;
app_class->impl_restart = gsm_autostart_app_restart;
app_class->impl_stop = gsm_autostart_app_stop;
app_class->impl_provides = gsm_autostart_app_provides;
app_class->impl_get_provides = gsm_autostart_app_get_provides;
app_class->impl_has_autostart_condition = gsm_autostart_app_has_autostart_condition;
app_class->impl_get_app_id = gsm_autostart_app_get_app_id;
app_class->impl_get_autorestart = gsm_autostart_app_get_autorestart;
app_class->impl_save_to_keyfile = gsm_autostart_app_save_to_keyfile;
props[PROP_DESKTOP_FILENAME] =
g_param_spec_string ("desktop-filename",
"Desktop filename",
"Freedesktop .desktop file",
NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
props[PROP_MASK_SYSTEMD] =
g_param_spec_boolean ("mask-systemd",
"Mask if systemd started",
"Mask if GNOME systemd flag is set in desktop file",
FALSE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (object_class, G_N_ELEMENTS (props), props);
signals[CONDITION_CHANGED] =
g_signal_new ("condition-changed",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GsmAutostartAppClass, condition_changed),
NULL, NULL, NULL,
G_TYPE_NONE,
1,
G_TYPE_BOOLEAN);
}
GsmApp *
gsm_autostart_app_new (const char *desktop_file,
gboolean mask_systemd,
GError **error)
{
return (GsmApp*) g_initable_new (GSM_TYPE_AUTOSTART_APP, NULL, error,
"desktop-filename", desktop_file,
"mask-systemd", mask_systemd,
NULL);
}