diff --git a/data/gnome-session-basic-services.target b/data/gnome-session-basic-services.target deleted file mode 100644 index ac8f7011..00000000 --- a/data/gnome-session-basic-services.target +++ /dev/null @@ -1,44 +0,0 @@ -[Unit] -Description=GNOME basic session services -OnFailure=gnome-session-shutdown.target -OnFailureJobMode=replace-irreversibly -DefaultDependencies=no -RefuseManualStart=yes -RefuseManualStop=yes - -Requisite=gnome-session-initialized.target -After=gnome-session-initialized.target -PartOf=gnome-session-initialized.target - -Requisite=gnome-session.target -PartOf=gnome-session.target -Before=gnome-session.target - -Requires=org.gnome.Shell.target - -# Manages a11y settings, starts Orca when needed -Wants=org.gnome.SettingsDaemon.A11ySettings.target - -# Migrates some input-method settings from previous -# versions of GNOME -Wants=org.gnome.SettingsDaemon.Keyboard.target - -# Handles keyboard keybindings -Wants=org.gnome.SettingsDaemon.MediaKeys.target - -# Power management service: auto-suspend, display -# brightness, screensaver, etc -Wants=org.gnome.SettingsDaemon.Power.target - -# Service to shutdown radio hardware (wifi, bt, wwan) -Wants=org.gnome.SettingsDaemon.Rfkill.target - -# File/media sharing services, and remote desktop -# (Manages remote desktop handover on login screen) -Wants=org.gnome.SettingsDaemon.Sharing.target - -# Handles smart card insertion/removal -Wants=org.gnome.SettingsDaemon.Smartcard.target - -# USBGuard integration -Wants=org.gnome.SettingsDaemon.UsbProtection.target diff --git a/data/gnome-session-failed.service.in b/data/gnome-session-failed.service.in new file mode 100644 index 00000000..4e22ab76 --- /dev/null +++ b/data/gnome-session-failed.service.in @@ -0,0 +1,19 @@ +[Unit] +Description=GNOME Session Failed lockdown screen (user) +OnFailure=gnome-session-shutdown.target +OnFailureJobMode=replace-irreversibly +CollectMode=inactive-or-failed + +PartOf=gnome-session-failed.target + +# We could do this, but it requires an intermediate target for OnFailure +# handling, so gnome-session-failed checks RUNNING_UNDER_GDM itself +#Conflicts=gnome-session@gnome-login.target +# or in the case of GDM and then not passing --allow-logout +#Requisite=gnome-session@gnome-login.target + +[Service] +Type=simple +ExecStart=@libexecdir@/gnome-session-failed --allow-logout +# The fail whale doesn't trigger a shutdown itself, so do it here +ExecStopPost=-@libexecdir@/gnome-session-ctl --shutdown diff --git a/data/gnome-session-failed.target b/data/gnome-session-failed.target new file mode 100644 index 00000000..3ad2eb19 --- /dev/null +++ b/data/gnome-session-failed.target @@ -0,0 +1,12 @@ +[Unit] +Description=GNOME Session Failed +OnFailure=gnome-session-shutdown.target +OnFailureJobMode=replace-irreversibly + +# We need an initialized session +Requisite=gnome-session-initialized.target +BindsTo=gnome-session-initialized.target +After=gnome-session-initialized.target + +BindsTo=gnome-session-failed.service +After=gnome-session-failed.service diff --git a/data/gnome-session-manager@.service.in b/data/gnome-session-manager@.service.in index 19012cf2..b06dbeb3 100644 --- a/data/gnome-session-manager@.service.in +++ b/data/gnome-session-manager@.service.in @@ -15,5 +15,5 @@ Before=gnome-session-manager.target [Service] Type=notify -ExecStart=@libexecdir@/gnome-session-service --session=%i +ExecStart=@libexecdir@/gnome-session-binary --systemd-service --session=%i ExecStopPost=-@libexecdir@/gnome-session-ctl --shutdown diff --git a/data/gnome-session-shutdown.target b/data/gnome-session-shutdown.target index 6240926d..2c3d1963 100644 --- a/data/gnome-session-shutdown.target +++ b/data/gnome-session-shutdown.target @@ -14,8 +14,8 @@ After=graphical-session.target graphical-session-pre.target Conflicts=gnome-session.target gnome-session-manager.target After=gnome-session.target gnome-session-manager.target -Conflicts=gnome-session-pre.target gnome-session-initialized.target -After=gnome-session-pre.target gnome-session-initialized.target +Conflicts=gnome-session-pre.target gnome-session-initialized.target gnome-session-failed.target +After=gnome-session-pre.target gnome-session-initialized.target gnome-session-failed.target # We need to make sure this unit is stopped; primarily so that the tree of # units that we created is completely cleaned. diff --git a/data/gnome-session-wayland.target b/data/gnome-session-wayland.target index 9a0a72a5..6552b4d4 100644 --- a/data/gnome-session-wayland.target +++ b/data/gnome-session-wayland.target @@ -1,5 +1,6 @@ [Unit] Description=GNOME Wayland Session +# On wayland all is lost, do a shutdown OnFailure=gnome-session-shutdown.target OnFailureJobMode=replace-irreversibly # Avoid default After/Before rules diff --git a/data/gnome-session-x11.target b/data/gnome-session-x11.target index a91d0877..e13cc9ee 100644 --- a/data/gnome-session-x11.target +++ b/data/gnome-session-x11.target @@ -1,7 +1,8 @@ [Unit] Description=GNOME X11 Session -OnFailure=gnome-session-shutdown.target -OnFailureJobMode=replace-irreversibly +# On X11, try to show the fail screen +OnFailure=gnome-session-failed.target +OnFailureJobMode=replace # Avoid default After/Before rules DefaultDependencies=no diff --git a/data/gnome-session-x11@.target b/data/gnome-session-x11@.target index b371759a..ce5879b2 100644 --- a/data/gnome-session-x11@.target +++ b/data/gnome-session-x11@.target @@ -1,7 +1,7 @@ [Unit] Description=GNOME X11 Session (session: %i) -OnFailure=gnome-session-shutdown.target -OnFailureJobMode=replace-irreversibly +OnFailure=gnome-session-failed.target +OnFailureJobMode=replace DefaultDependencies=no # Start happens explicitly RefuseManualStart=no diff --git a/data/gnome-session.target b/data/gnome-session.target index bf4615b6..ea305558 100644 --- a/data/gnome-session.target +++ b/data/gnome-session.target @@ -1,7 +1,7 @@ [Unit] Description=GNOME Session -OnFailure=gnome-session-shutdown.target -OnFailureJobMode=replace-irreversibly +OnFailure=gnome-session-failed.target +OnFailureJobMode=replace DefaultDependencies=no RefuseManualStart=yes RefuseManualStop=yes diff --git a/data/gnome-session@.target b/data/gnome-session@.target index d5ca3a11..536d9c0f 100644 --- a/data/gnome-session@.target +++ b/data/gnome-session@.target @@ -1,7 +1,7 @@ [Unit] Description=GNOME Session (session: %i) -OnFailure=gnome-session-shutdown.target -OnFailureJobMode=replace-irreversibly +OnFailure=gnome-session-failed.target +OnFailureJobMode=replace DefaultDependencies=no RefuseManualStart=yes RefuseManualStop=yes diff --git a/data/gnome.session.conf b/data/gnome.session.conf deleted file mode 100644 index 916191fc..00000000 --- a/data/gnome.session.conf +++ /dev/null @@ -1,48 +0,0 @@ -[Unit] -# Basic services required by any GNOME session -Requires=gnome-session-basic-services.target - -# Manages monitor color calibration and night-light -Wants=org.gnome.SettingsDaemon.Color.target - -# Manages location-based timezone -Wants=org.gnome.SettingsDaemon.Datetime.target - -# Notifies when disk is full, notifies when RAM is full, -# cleans up trash and temp files, donation reminders -Wants=org.gnome.SettingsDaemon.Housekeeping.target - -# Notifies when events happen with printers: low on ink, -# jammed, job cancelled, etc etc etc -Wants=org.gnome.SettingsDaemon.PrintNotifications.target - -# org.freedesktop.ScreenSaver API -Wants=org.gnome.SettingsDaemon.ScreensaverProxy.target - -# Flushes PulseAudio sample cache whenever the -# sound theme changes -Wants=org.gnome.SettingsDaemon.Sound.target - -# Manages SIM cards (unlocking, etc) -Wants=org.gnome.SettingsDaemon.Wwan.target - -# Monitors disks for SMART errors, and notifies -# the user if their disk is about to fail -Wants=gnome-disk-utility-notify.service - -# Filesystem indexer -Wants=localsearch-3.service - -# Graphical wrapper around ssh-agent -Wants=gcr-ssh-agent.socket - -# Checking for automatic updates, etc -Wants=gnome-software.service - -# Translates the user's standard directories -# (Downloads/, Documents/, Music/, etc) whenever -# the user's locale changes -Wants=user-dirs-update-gtk.service - -# Notifies the user about their calendar events -Wants=evolution-alarm-notify.service diff --git a/data/gnome.session.conf.in b/data/gnome.session.conf.in new file mode 100644 index 00000000..73da0850 --- /dev/null +++ b/data/gnome.session.conf.in @@ -0,0 +1,6 @@ +[Unit] +@wants_components@ + +Wants=localsearch-3.service + +@requires_components@ diff --git a/data/gnome.session.desktop b/data/gnome.session.desktop deleted file mode 100644 index 1a084cf3..00000000 --- a/data/gnome.session.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[GNOME Session] -Name=GNOME diff --git a/data/gnome.session.desktop.in.in b/data/gnome.session.desktop.in.in new file mode 100644 index 00000000..b3ea9fcf --- /dev/null +++ b/data/gnome.session.desktop.in.in @@ -0,0 +1,4 @@ +[GNOME Session] +Name=GNOME +# Must be in sync with gnome-session@gnome.target.d/gnome.session.conf drop-in +RequiredComponents=@required_components@; diff --git a/data/meson.build b/data/meson.build index 76b15e92..e4256fcd 100644 --- a/data/meson.build +++ b/data/meson.build @@ -60,18 +60,87 @@ if have_x11 endforeach endif -# Next, let's install the necessary systemd scaffolding +# Next, let's configure the required components for a session. -install_data( - 'gnome.session.conf', - install_dir: systemd_userunitdir / 'gnome-session@gnome.target.d', +required_components = [ + 'org.gnome.Shell', +] + +wanted_components = [ + 'org.gnome.SettingsDaemon.A11ySettings', + 'org.gnome.SettingsDaemon.Color', + 'org.gnome.SettingsDaemon.Datetime', + 'org.gnome.SettingsDaemon.Housekeeping', + 'org.gnome.SettingsDaemon.Keyboard', + 'org.gnome.SettingsDaemon.MediaKeys', + 'org.gnome.SettingsDaemon.Power', + 'org.gnome.SettingsDaemon.PrintNotifications', + 'org.gnome.SettingsDaemon.Rfkill', + 'org.gnome.SettingsDaemon.ScreensaverProxy', + 'org.gnome.SettingsDaemon.Sharing', + 'org.gnome.SettingsDaemon.Smartcard', + 'org.gnome.SettingsDaemon.Sound', + 'org.gnome.SettingsDaemon.UsbProtection', + 'org.gnome.SettingsDaemon.Wwan', +] + +# Generate the legacy "builtin" session configuration. In this mode, gnome-session +# parses .session files that list other .desktop files to start and +# manage. gnome-session then does this management on its own. + +builtin_required = required_components + wanted_components +desktop = 'gnome.session.desktop' +desktop_in = configure_file( + input: desktop + '.in.in', + output: desktop + '.in', + configuration: { + 'libexecdir': session_libexecdir, + 'required_components': ';'.join(builtin_required), + } ) +i18n.merge_file( + type: 'desktop', + input: desktop_in, + output: 'gnome.session', + po_dir: po_dir, + install: true, + install_dir: join_paths(session_pkgdatadir, 'sessions') +) + +# Generate the systemd session configuration. In this mode, gnome-session lets +# systemd start and manage the necessary session components. These are defined +# via systemd unit files. The list of required components to start comes via a +# drop-in file for gnome-session@.target + +wanted_targets = [] +foreach component: wanted_components + wanted_targets += 'Wants=@0@.target'.format(component) +endforeach + +required_targets = [] +foreach component: required_components + wanted_targets += 'Requires=@0@.target'.format(component) +endforeach + +configure_file( + input: 'gnome.session.conf.in', + output: 'gnome.session.conf', + configuration: { + 'wants_components': '\n'.join(wanted_targets), + 'requires_components': '\n'.join(required_targets), + }, + install_dir: systemd_userunitdir / 'gnome-session@gnome.target.d', +) + +# Install the other necessary systemd scaffolding + systemd_service = [ 'gnome-session-manager@.service', 'gnome-session-signal-init.service', 'gnome-session-restart-dbus.service', 'gnome-session-monitor.service', + 'gnome-session-failed.service', ] foreach service: systemd_service @@ -91,11 +160,11 @@ systemd_target = [ 'gnome-session-wayland.target', 'gnome-session@.target', 'gnome-session.target', - 'gnome-session-basic-services.target', 'gnome-session-pre.target', 'gnome-session-manager.target', 'gnome-session-initialized.target', 'gnome-session-shutdown.target', + 'gnome-session-failed.target', 'gnome-session-x11-services.target', 'gnome-session-x11-services-ready.target', ] @@ -128,15 +197,6 @@ install_data( # Install some other misc. configuration files -i18n.merge_file( - type: 'desktop', - input: 'gnome.session.desktop', - output: 'gnome.session', - po_dir: po_dir, - install: true, - install_dir: join_paths(session_pkgdatadir, 'sessions') -) - install_data( 'org.gnome.SessionManager.gschema.xml', install_dir: join_paths(session_datadir, 'glib-2.0', 'schemas'), diff --git a/doc/gnome-session.xml b/doc/gnome-session.xml index 1085490f..64da44ed 100644 --- a/doc/gnome-session.xml +++ b/doc/gnome-session.xml @@ -37,7 +37,9 @@ + + diff --git a/gnome-session/gdm-log.c b/gnome-session/gdm-log.c new file mode 100644 index 00000000..c0dca411 --- /dev/null +++ b/gnome-session/gdm-log.c @@ -0,0 +1,205 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann + * + * 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 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 . + * + * Authors: William Jon McCann + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "gdm-log.h" + +static gboolean initialized = FALSE; +static int syslog_levels = (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING); + +static void +log_level_to_priority_and_prefix (GLogLevelFlags log_level, + int *priorityp, + const char **prefixp) +{ + int priority; + const char *prefix; + + /* Process the message prefix and priority */ + switch (log_level & G_LOG_LEVEL_MASK) { + case G_LOG_FLAG_FATAL: + priority = LOG_EMERG; + prefix = "FATAL"; + break; + case G_LOG_LEVEL_ERROR: + priority = LOG_ERR; + prefix = "ERROR"; + break; + case G_LOG_LEVEL_CRITICAL: + priority = LOG_CRIT; + prefix = "CRITICAL"; + break; + case G_LOG_LEVEL_WARNING: + priority = LOG_WARNING; + prefix = "WARNING"; + break; + case G_LOG_LEVEL_MESSAGE: + priority = LOG_NOTICE; + prefix = "MESSAGE"; + break; + case G_LOG_LEVEL_INFO: + priority = LOG_INFO; + prefix = "INFO"; + break; + case G_LOG_LEVEL_DEBUG: + /* if debug was requested then bump this up to ERROR + * to ensure it is seen in a log */ + if (syslog_levels & G_LOG_LEVEL_DEBUG) { + priority = LOG_WARNING; + prefix = "DEBUG(+)"; + } else { + priority = LOG_DEBUG; + prefix = "DEBUG"; + } + break; + default: + priority = LOG_DEBUG; + prefix = "UNKNOWN"; + break; + } + + if (priorityp != NULL) { + *priorityp = priority; + } + if (prefixp != NULL) { + *prefixp = prefix; + } +} + +void +gdm_log_default_handler (const gchar *log_domain, + GLogLevelFlags log_level, + const gchar *message, + gpointer unused_data) +{ + GString *gstring; + int priority; + const char *level_prefix; + char *string; + gboolean do_log; + gboolean is_fatal; + + is_fatal = (log_level & G_LOG_FLAG_FATAL) != 0; + + do_log = (log_level & syslog_levels); + if (! do_log) { + return; + } + + if (! initialized) { + gdm_log_init (); + } + + log_level_to_priority_and_prefix (log_level, + &priority, + &level_prefix); + + gstring = g_string_new (NULL); + + if (log_domain != NULL) { + g_string_append (gstring, log_domain); + g_string_append_c (gstring, '-'); + } + g_string_append (gstring, level_prefix); + + g_string_append (gstring, ": "); + if (message == NULL) { + g_string_append (gstring, "(NULL) message"); + } else { + g_string_append (gstring, message); + } + if (is_fatal) { + g_string_append (gstring, "\naborting...\n"); + } else { + g_string_append (gstring, "\n"); + } + + string = g_string_free (gstring, FALSE); + + syslog (priority, "%s", string); + + g_free (string); +} + +void +gdm_log_toggle_debug (void) +{ + if (syslog_levels & G_LOG_LEVEL_DEBUG) { + g_debug ("Debugging disabled"); + syslog_levels &= ~G_LOG_LEVEL_DEBUG; + } else { + syslog_levels |= G_LOG_LEVEL_DEBUG; + g_debug ("Debugging enabled"); + } +} + +void +gdm_log_set_debug (gboolean debug) +{ + if (debug) { + syslog_levels |= G_LOG_LEVEL_DEBUG; + g_debug ("Enabling debugging"); + } else { + g_debug ("Disabling debugging"); + syslog_levels &= ~G_LOG_LEVEL_DEBUG; + } +} + +void +gdm_log_init (void) +{ + const char *prg_name; + int options; + + g_log_set_default_handler (gdm_log_default_handler, NULL); + + prg_name = g_get_prgname (); + + options = LOG_PID; +#ifdef LOG_PERROR + options |= LOG_PERROR; +#endif + + openlog (prg_name, options, LOG_DAEMON); + + initialized = TRUE; +} + +void +gdm_log_shutdown (void) +{ + closelog (); + initialized = FALSE; +} + diff --git a/gnome-session/gdm-log.h b/gnome-session/gdm-log.h new file mode 100644 index 00000000..33daf40e --- /dev/null +++ b/gnome-session/gdm-log.h @@ -0,0 +1,50 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann + * + * 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 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 . + * + * Authors: William Jon McCann + * + */ + +#ifndef __GDM_LOG_H +#define __GDM_LOG_H + +#include +#include + +G_BEGIN_DECLS + +void gdm_log_default_handler (const gchar *log_domain, + GLogLevelFlags log_level, + const gchar *message, + gpointer unused_data); +void gdm_log_set_debug (gboolean debug); +void gdm_log_toggle_debug (void); +void gdm_log_init (void); +void gdm_log_shutdown (void); + +/* compatibility */ +#define gdm_fail g_critical +#define gdm_error g_warning +#define gdm_info g_message +#define gdm_debug g_debug + +#define gdm_assert g_assert +#define gdm_assert_not_reached g_assert_not_reached + +G_END_DECLS + +#endif /* __GDM_LOG_H */ diff --git a/gnome-session/gnome-session.in b/gnome-session/gnome-session.in new file mode 100755 index 00000000..b3b93a1c --- /dev/null +++ b/gnome-session/gnome-session.in @@ -0,0 +1,39 @@ +#!/bin/sh + +if [ "x$XDG_SESSION_TYPE" = "xwayland" ] && + [ "x$XDG_SESSION_CLASS" != "xgreeter" ] && + [ -n "$SHELL" ] && + grep -q "$SHELL" /etc/shells && + ! (echo "$SHELL" | grep -q "false") && + ! (echo "$SHELL" | grep -q "nologin"); then + if [ "$1" != '-l' ]; then + exec bash -c "exec -l '$SHELL' -c 'exec $0 -l $*'" + else + shift + fi +fi + +SETTING=$(G_MESSAGES_DEBUG='' gsettings get org.gnome.system.locale region) +REGION=${SETTING#\'} +REGION=${REGION%\'} + +if [ -n "$REGION" ]; then + unset LC_NUMERIC LC_TIME LC_MONETARY LC_PAPER LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT + + if [ "$LANG" != "$REGION" ] ; then + # LC_CTYPE + export LC_NUMERIC=$REGION + export LC_TIME=$REGION + # LC_COLLATE + export LC_MONETARY=$REGION + # LC_MESSAGES + export LC_PAPER=$REGION + # LC_NAME + export LC_ADDRESS=$REGION + export LC_TELEPHONE=$REGION + export LC_MEASUREMENT=$REGION + # LC_IDENTIFICATION + fi +fi + +exec @libexecdir@/gnome-session-binary "$@" diff --git a/gnome-session/gsm-app.c b/gnome-session/gsm-app.c index d38138be..c05ae06a 100644 --- a/gnome-session/gsm-app.c +++ b/gnome-session/gsm-app.c @@ -21,49 +21,174 @@ #include "config.h" #endif +#include #include -#include -#include -#include +#include "gsm-app.h" +#include "org.gnome.SessionManager.App.h" -#define GNOME_DESKTOP_USE_UNSTABLE_API -#include +/* If a component crashes twice within a minute, we count that as a fatal error */ +#define _GSM_APP_RESPAWN_RATELIMIT_USEC (60 * G_USEC_PER_SEC) -#include "gsm-app.h" -#include "gsm-util.h" +typedef struct +{ + char *id; + char *app_id; + int phase; + char *startup_id; + gboolean registered; + gint64 last_restart_time; + GDBusConnection *connection; + GsmExportedApp *skeleton; +} GsmAppPrivate; -#define GSM_APP_SYSTEMD_SKIP_KEY "X-systemd-skip" -#define GSM_APP_SYSTEMD_HIDDEN_KEY "X-GNOME-HiddenUnderSystemd" -#define GSM_APP_ENABLED_KEY "X-GNOME-Autostart-enabled" -#define GSM_APP_PHASE_KEY "X-GNOME-Autostart-Phase" -/* This comment is a record of keys that were previously used but are not used - * anymore. We keep this so that we don't accidentally redefine these keys in - * the future, to be used in some incompatible way. - * - * X-GNOME-AutoRestart - * X-GNOME-Autostart-discard-exec - * AutostartCondition - * X-GNOME-DBus-Name - * X-GNOME-DBus-Path - * X-GNOME-DBus-Start-Arguments - * X-GNOME-Autostart-startup-id - * */ - -struct _GsmApp -{ - GObject parent; - GDesktopAppInfo *inner; +enum { + EXITED, + DIED, + LAST_SIGNAL }; +static guint32 app_serial = 1; + +static guint signals[LAST_SIGNAL] = { 0 }; + enum { PROP_0, - PROP_INNER, + PROP_ID, + PROP_STARTUP_ID, + PROP_PHASE, + PROP_REGISTERED, LAST_PROP }; -G_DEFINE_TYPE (GsmApp, gsm_app, G_TYPE_OBJECT) +G_DEFINE_TYPE_WITH_PRIVATE (GsmApp, gsm_app, G_TYPE_OBJECT) + +GQuark +gsm_app_error_quark (void) +{ + static GQuark ret = 0; + if (ret == 0) { + ret = g_quark_from_static_string ("gsm_app_error"); + } + + return ret; + +} + +static gboolean +gsm_app_get_app_id (GsmExportedApp *skeleton, + GDBusMethodInvocation *invocation, + GsmApp *app) +{ + const gchar *id; + + id = GSM_APP_GET_CLASS (app)->impl_get_app_id (app); + gsm_exported_app_complete_get_app_id (skeleton, invocation, id); + + return TRUE; +} + +static gboolean +gsm_app_get_startup_id (GsmExportedApp *skeleton, + GDBusMethodInvocation *invocation, + GsmApp *app) +{ + GsmAppPrivate *priv = gsm_app_get_instance_private (app); + const gchar *id; + + id = g_strdup (priv->startup_id); + gsm_exported_app_complete_get_startup_id (skeleton, invocation, id); + + return TRUE; +} + +static gboolean +gsm_app_get_phase (GsmExportedApp *skeleton, + GDBusMethodInvocation *invocation, + GsmApp *app) +{ + GsmAppPrivate *priv = gsm_app_get_instance_private (app); + + gsm_exported_app_complete_get_phase (skeleton, invocation, priv->phase); + return TRUE; +} + +static guint32 +get_next_app_serial (void) +{ + guint32 serial; + + serial = app_serial++; + + if ((gint32)app_serial < 0) { + app_serial = 1; + } + + return serial; +} + +static gboolean +register_app (GsmApp *app) +{ + GsmAppPrivate *priv = gsm_app_get_instance_private (app); + GError *error; + GsmExportedApp *skeleton; + + error = NULL; + priv->connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + if (error != NULL) { + g_critical ("error getting session bus: %s", error->message); + g_error_free (error); + return FALSE; + } + + skeleton = gsm_exported_app_skeleton_new (); + priv->skeleton = skeleton; + g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (skeleton), + priv->connection, priv->id, + &error); + + if (error != NULL) { + g_critical ("error registering app on session bus: %s", error->message); + g_error_free (error); + return FALSE; + } + + g_signal_connect (skeleton, "handle-get-app-id", + G_CALLBACK (gsm_app_get_app_id), app); + g_signal_connect (skeleton, "handle-get-phase", + G_CALLBACK (gsm_app_get_phase), app); + g_signal_connect (skeleton, "handle-get-startup-id", + G_CALLBACK (gsm_app_get_startup_id), app); + + return TRUE; +} + +static GObject * +gsm_app_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GsmApp *app; + GsmAppPrivate *priv; + gboolean res; + + app = GSM_APP (G_OBJECT_CLASS (gsm_app_parent_class)->constructor (type, + n_construct_properties, + construct_properties)); + priv = gsm_app_get_instance_private (app); + + g_free (priv->id); + priv->id = g_strdup_printf ("/org/gnome/SessionManager/App%u", get_next_app_serial ()); + + res = register_app (app); + if (! res) { + g_warning ("Unable to register app with session bus"); + } + + return G_OBJECT (app); +} static void gsm_app_init (GsmApp *app) @@ -71,13 +196,43 @@ gsm_app_init (GsmApp *app) } static void -gsm_app_set_inner (GsmApp *app, - GDesktopAppInfo *app_info) +gsm_app_set_phase (GsmApp *app, + int phase) { + GsmAppPrivate *priv = gsm_app_get_instance_private (app); + g_return_if_fail (GSM_IS_APP (app)); - g_return_if_fail (app_info == NULL || G_IS_DESKTOP_APP_INFO (app_info)); - g_set_object (&app->inner, app_info); + priv->phase = phase; +} + +static void +gsm_app_set_id (GsmApp *app, + const char *id) +{ + GsmAppPrivate *priv = gsm_app_get_instance_private (app); + + g_return_if_fail (GSM_IS_APP (app)); + + g_free (priv->id); + + priv->id = g_strdup (id); + g_object_notify (G_OBJECT (app), "id"); + +} +static void +gsm_app_set_startup_id (GsmApp *app, + const char *startup_id) +{ + GsmAppPrivate *priv = gsm_app_get_instance_private (app); + + g_return_if_fail (GSM_IS_APP (app)); + + g_free (priv->startup_id); + + priv->startup_id = g_strdup (startup_id); + g_object_notify (G_OBJECT (app), "startup-id"); + } static void @@ -89,8 +244,17 @@ gsm_app_set_property (GObject *object, GsmApp *app = GSM_APP (object); switch (prop_id) { - case PROP_INNER: - gsm_app_set_inner (app, g_value_get_object (value)); + case PROP_STARTUP_ID: + gsm_app_set_startup_id (app, g_value_get_string (value)); + break; + case PROP_ID: + gsm_app_set_id (app, g_value_get_string (value)); + break; + case PROP_PHASE: + gsm_app_set_phase (app, g_value_get_int (value)); + break; + case PROP_REGISTERED: + gsm_app_set_registered (app, g_value_get_boolean (value)); break; default: break; @@ -104,10 +268,20 @@ gsm_app_get_property (GObject *object, GParamSpec *pspec) { GsmApp *app = GSM_APP (object); + GsmAppPrivate *priv = gsm_app_get_instance_private (app); switch (prop_id) { - case PROP_INNER: - g_value_set_object (value, app->inner); + case PROP_STARTUP_ID: + g_value_set_string (value, priv->startup_id); + break; + case PROP_ID: + g_value_set_string (value, priv->id); + break; + case PROP_PHASE: + g_value_set_int (value, priv->phase); + break; + case PROP_REGISTERED: + g_value_set_boolean (value, priv->registered); break; default: break; @@ -118,8 +292,21 @@ static void gsm_app_dispose (GObject *object) { GsmApp *app = GSM_APP (object); + GsmAppPrivate *priv = gsm_app_get_instance_private (app); + + g_free (priv->startup_id); + priv->startup_id = NULL; - g_clear_object (&app->inner); + g_free (priv->id); + priv->id = NULL; + + if (priv->skeleton != NULL) { + g_dbus_interface_skeleton_unexport_from_connection (G_DBUS_INTERFACE_SKELETON (priv->skeleton), + priv->connection); + g_clear_object (&priv->skeleton); + } + + g_clear_object (&priv->connection); G_OBJECT_CLASS (gsm_app_parent_class)->dispose (object); } @@ -132,22 +319,103 @@ gsm_app_class_init (GsmAppClass *klass) object_class->set_property = gsm_app_set_property; object_class->get_property = gsm_app_get_property; object_class->dispose = gsm_app_dispose; + object_class->constructor = gsm_app_constructor; + + klass->impl_start = NULL; + klass->impl_get_app_id = NULL; + klass->impl_get_autorestart = NULL; + klass->impl_provides = NULL; + klass->impl_get_provides = NULL; + klass->impl_is_running = NULL; + + g_object_class_install_property (object_class, + PROP_PHASE, + g_param_spec_int ("phase", + "Phase", + "Phase", + -1, + G_MAXINT, + -1, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_ID, + g_param_spec_string ("id", + "ID", + "ID", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_STARTUP_ID, + g_param_spec_string ("startup-id", + "startup ID", + "Session management startup ID", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, - PROP_INNER, - g_param_spec_object ("inner", - "Inner", - "Inner", - G_TYPE_DESKTOP_APP_INFO, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + PROP_REGISTERED, + g_param_spec_boolean ("registered", + "Registered", + "Registered", + FALSE, + G_PARAM_READWRITE)); + + signals[EXITED] = + g_signal_new ("exited", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsmAppClass, exited), + NULL, NULL, NULL, + G_TYPE_NONE, + 1, G_TYPE_UCHAR); + signals[DIED] = + g_signal_new ("died", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsmAppClass, died), + NULL, NULL, NULL, + G_TYPE_NONE, + 1, G_TYPE_INT); +} + +const char * +gsm_app_peek_id (GsmApp *app) +{ + GsmAppPrivate *priv = gsm_app_get_instance_private (app); + + return priv->id; } const char * gsm_app_peek_app_id (GsmApp *app) { - g_return_val_if_fail (GSM_IS_APP (app), NULL); + return GSM_APP_GET_CLASS (app)->impl_get_app_id (app); +} + +const char * +gsm_app_peek_startup_id (GsmApp *app) +{ + GsmAppPrivate *priv = gsm_app_get_instance_private (app); - return g_app_info_get_id (G_APP_INFO (app->inner)); + return priv->startup_id; +} + +/** + * gsm_app_peek_phase: + * @app: a %GsmApp + * + * Returns @app's startup phase. + * + * Return value: @app's startup phase + **/ +GsmManagerPhase +gsm_app_peek_phase (GsmApp *app) +{ + GsmAppPrivate *priv = gsm_app_get_instance_private (app); + + g_return_val_if_fail (GSM_IS_APP (app), GSM_MANAGER_PHASE_APPLICATION); + + return priv->phase; } gboolean @@ -155,146 +423,171 @@ gsm_app_peek_is_disabled (GsmApp *app) { g_return_val_if_fail (GSM_IS_APP (app), FALSE); - /* GSM_AUTOSTART_APP_ENABLED_KEY key, used by old gnome-session */ - if (g_desktop_app_info_has_key (app->inner, GSM_APP_ENABLED_KEY) && - !g_desktop_app_info_get_boolean (app->inner, GSM_APP_ENABLED_KEY)) { - g_debug ("App %s is disabled by " GSM_APP_ENABLED_KEY, - gsm_app_peek_app_id (app)); - return TRUE; + if (GSM_APP_GET_CLASS (app)->impl_is_disabled) { + return GSM_APP_GET_CLASS (app)->impl_is_disabled (app); + } else { + return FALSE; } +} - /* Hidden key, used by fdo Desktop Entry spec */ - if (g_desktop_app_info_get_is_hidden (app->inner)) { - g_debug ("App %s is disabled by Hidden", - gsm_app_peek_app_id (app)); - return TRUE; - } +gboolean +gsm_app_peek_is_conditionally_disabled (GsmApp *app) +{ + g_return_val_if_fail (GSM_IS_APP (app), FALSE); - /* Check OnlyShowIn/NotShowIn/TryExec */ - if (!g_desktop_app_info_get_show_in (app->inner, NULL)) { - g_debug ("App %s is not for the current desktop", - gsm_app_peek_app_id (app)); - return TRUE; + if (GSM_APP_GET_CLASS (app)->impl_is_conditionally_disabled) { + return GSM_APP_GET_CLASS (app)->impl_is_conditionally_disabled (app); + } else { + return FALSE; } +} - /* Check if app is systemd enabled */ - if (g_desktop_app_info_get_boolean (app->inner, GSM_APP_SYSTEMD_HIDDEN_KEY)) { - g_debug ("App %s is disabled by " GSM_APP_SYSTEMD_HIDDEN_KEY, - gsm_app_peek_app_id (app)); - return TRUE; - } - if (g_desktop_app_info_get_boolean (app->inner, GSM_APP_SYSTEMD_SKIP_KEY)) { - g_debug ("App %s is disabled by " GSM_APP_SYSTEMD_SKIP_KEY, - gsm_app_peek_app_id (app)); - return TRUE; +gboolean +gsm_app_is_running (GsmApp *app) +{ + g_return_val_if_fail (GSM_IS_APP (app), FALSE); + + if (GSM_APP_GET_CLASS (app)->impl_is_running) { + return GSM_APP_GET_CLASS (app)->impl_is_running (app); + } else { + return FALSE; } +} + +gboolean +gsm_app_peek_autorestart (GsmApp *app) +{ + g_return_val_if_fail (GSM_IS_APP (app), FALSE); - return FALSE; + if (GSM_APP_GET_CLASS (app)->impl_get_autorestart) { + return GSM_APP_GET_CLASS (app)->impl_get_autorestart (app); + } else { + return FALSE; + } } -static void -app_launched (GAppLaunchContext *ctx, - GAppInfo *appinfo, - GVariant *platform_data, - gpointer data) +gboolean +gsm_app_provides (GsmApp *app, const char *service) { - GsmApp *app = data; + if (GSM_APP_GET_CLASS (app)->impl_provides) { + return GSM_APP_GET_CLASS (app)->impl_provides (app, service); + } else { + return FALSE; + } +} - gint pid = 0; - g_variant_lookup (platform_data, "pid", "i", &pid); +char ** +gsm_app_get_provides (GsmApp *app) +{ + if (GSM_APP_GET_CLASS (app)->impl_get_provides) { + return GSM_APP_GET_CLASS (app)->impl_get_provides (app); + } else { + return NULL; + } +} - /* If pid == 0 the application was launched through D-Bus - * activation, therefore it's already in its own unit */ - if (pid == 0) - return; +gboolean +gsm_app_has_autostart_condition (GsmApp *app, + const char *condition) +{ - /* We are not interested in the result. */ - gnome_start_systemd_scope (gsm_app_peek_app_id (app), - pid, - NULL, - NULL, - NULL, NULL, NULL); + if (GSM_APP_GET_CLASS (app)->impl_has_autostart_condition) { + return GSM_APP_GET_CLASS (app)->impl_has_autostart_condition (app, condition); + } else { + return FALSE; + } } gboolean gsm_app_start (GsmApp *app, GError **error) { - g_autoptr (GAppLaunchContext) ctx = NULL; - GError *local_error = NULL; - const char * const *variable_denylist; - const char * const *child_environment; - guint handler; - gboolean success; - - g_debug ("GsmApp: starting %s (command=%s)", - gsm_app_peek_app_id (app), - g_app_info_get_commandline (G_APP_INFO (app->inner))); - - ctx = g_app_launch_context_new (); + GsmAppPrivate *priv = gsm_app_get_instance_private (app); - variable_denylist = gsm_util_get_variable_blacklist (); - for (size_t i = 0; variable_denylist[i] != NULL; i++) - g_app_launch_context_unsetenv (ctx, variable_denylist[i]); + g_debug ("Starting app: %s", priv->id); + return GSM_APP_GET_CLASS (app)->impl_start (app, error); +} - child_environment = gsm_util_listenv (); - for (size_t i = 0; child_environment[i] != NULL; i++) { - g_auto (GStrv) split = g_strsplit (child_environment[i], "=", 2); - if (split[1] != NULL) - g_app_launch_context_setenv (ctx, split[0], split[1]); +gboolean +gsm_app_restart (GsmApp *app, + GError **error) +{ + GsmAppPrivate *priv = gsm_app_get_instance_private (app); + gint64 current_time; + g_debug ("Re-starting app: %s", priv->id); + + current_time = g_get_monotonic_time (); + if (priv->last_restart_time > 0 + && (current_time - priv->last_restart_time) < _GSM_APP_RESPAWN_RATELIMIT_USEC) { + g_warning ("App '%s' respawning too quickly", gsm_app_peek_app_id (app)); + g_set_error (error, + GSM_APP_ERROR, + GSM_APP_ERROR_RESTART_LIMIT, + "Component '%s' crashing too quickly", + gsm_app_peek_app_id (app)); + return FALSE; } + priv->last_restart_time = current_time; - handler = g_signal_connect (ctx, "launched", G_CALLBACK (app_launched), app); - success = g_desktop_app_info_launch_uris_as_manager (app->inner, - NULL, - ctx, - G_SPAWN_SEARCH_PATH, - NULL, NULL, NULL, NULL, - &local_error); - if (!success) - g_propagate_prefixed_error (error, local_error, - "Unable to start app (%s): ", - gsm_app_peek_app_id (app)); + return GSM_APP_GET_CLASS (app)->impl_restart (app, error); +} - g_signal_handler_disconnect (ctx, handler); +gboolean +gsm_app_stop (GsmApp *app, + GError **error) +{ + return GSM_APP_GET_CLASS (app)->impl_stop (app, error); +} + +void +gsm_app_exited (GsmApp *app, + guchar exit_code) +{ + g_return_if_fail (GSM_IS_APP (app)); - return success; + g_signal_emit (app, signals[EXITED], 0, exit_code); } -GsmApp * -gsm_app_new (GDesktopAppInfo *info, - GError **error) +void +gsm_app_died (GsmApp *app, + int signal) { - g_autofree char *app_phase = NULL; + g_return_if_fail (GSM_IS_APP (app)); - g_return_val_if_fail (info != NULL, NULL); + g_signal_emit (app, signals[DIED], 0, signal); +} - app_phase = g_desktop_app_info_get_string (info, GSM_APP_PHASE_KEY); - if (app_phase && strcmp (app_phase, "Application") != 0) { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "App %s sets " GSM_APP_PHASE_KEY ", but gnome-session no longer manages session services", - g_app_info_get_id (G_APP_INFO(info))); - return NULL; - } +gboolean +gsm_app_get_registered (GsmApp *app) +{ + GsmAppPrivate *priv = gsm_app_get_instance_private (app); + + g_return_val_if_fail (GSM_IS_APP (app), FALSE); - return g_object_new (GSM_TYPE_APP, "inner", info, NULL); + return priv->registered; } -GsmApp * -gsm_app_new_for_path (const char *path, - GError **error) +void +gsm_app_set_registered (GsmApp *app, + gboolean registered) { - g_autoptr (GDesktopAppInfo) info = NULL; + GsmAppPrivate *priv = gsm_app_get_instance_private (app); - g_return_val_if_fail (path != NULL, NULL); + g_return_if_fail (GSM_IS_APP (app)); - info = g_desktop_app_info_new_from_filename (path); - if (info == NULL) { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Desktop file %s couldn't be parsed, or references a missing TryExec binary", - path); - return NULL; + if (priv->registered != registered) { + priv->registered = registered; + g_object_notify (G_OBJECT (app), "registered"); } +} + +gboolean +gsm_app_save_to_keyfile (GsmApp *app, + GKeyFile *keyfile, + GError **error) +{ + GsmAppPrivate *priv = gsm_app_get_instance_private (app); - return gsm_app_new (info, error); + g_debug ("Saving app: %s", priv->id); + return GSM_APP_GET_CLASS (app)->impl_save_to_keyfile (app, keyfile, error); } diff --git a/gnome-session/gsm-app.h b/gnome-session/gsm-app.h index a3099709..765a70da 100644 --- a/gnome-session/gsm-app.h +++ b/gnome-session/gsm-app.h @@ -21,27 +21,98 @@ #define __GSM_APP_H__ #include -#include #include + #include "gsm-manager.h" #include "gsm-client.h" G_BEGIN_DECLS -#define GSM_TYPE_APP (gsm_app_get_type ()) -G_DECLARE_FINAL_TYPE (GsmApp, gsm_app, GSM, APP, GObject) +#define GSM_TYPE_APP (gsm_app_get_type ()) +G_DECLARE_DERIVABLE_TYPE (GsmApp, gsm_app, GSM, APP, GObject) + +struct _GsmAppClass +{ + GObjectClass parent_class; + + /* signals */ + void (*exited) (GsmApp *app, + guchar exit_code); + void (*died) (GsmApp *app, + int signal); + + /* virtual methods */ + gboolean (*impl_start) (GsmApp *app, + GError **error); + gboolean (*impl_restart) (GsmApp *app, + GError **error); + gboolean (*impl_stop) (GsmApp *app, + GError **error); + gboolean (*impl_provides) (GsmApp *app, + const char *service); + char ** (*impl_get_provides) (GsmApp *app); + gboolean (*impl_has_autostart_condition) (GsmApp *app, + const char *service); + gboolean (*impl_is_running) (GsmApp *app); + + gboolean (*impl_get_autorestart) (GsmApp *app); + const char *(*impl_get_app_id) (GsmApp *app); + gboolean (*impl_is_disabled) (GsmApp *app); + gboolean (*impl_is_conditionally_disabled) (GsmApp *app); + + gboolean (*impl_save_to_keyfile) (GsmApp *app, + GKeyFile *keyfile, + GError **error); +}; + +typedef enum +{ + GSM_APP_ERROR_GENERAL = 0, + GSM_APP_ERROR_RESTART_LIMIT, + GSM_APP_ERROR_START, + GSM_APP_ERROR_STOP, + GSM_APP_NUM_ERRORS +} GsmAppError; + +#define GSM_APP_ERROR gsm_app_error_quark () -GsmApp *gsm_app_new (GDesktopAppInfo *info, - GError **error); -GsmApp *gsm_app_new_for_path (const char *path, - GError **error); +GQuark gsm_app_error_quark (void); + +gboolean gsm_app_peek_autorestart (GsmApp *app); + +const char *gsm_app_peek_id (GsmApp *app); const char *gsm_app_peek_app_id (GsmApp *app); +const char *gsm_app_peek_startup_id (GsmApp *app); GsmManagerPhase gsm_app_peek_phase (GsmApp *app); gboolean gsm_app_peek_is_disabled (GsmApp *app); +gboolean gsm_app_peek_is_conditionally_disabled (GsmApp *app); gboolean gsm_app_start (GsmApp *app, GError **error); +gboolean gsm_app_restart (GsmApp *app, + GError **error); +gboolean gsm_app_stop (GsmApp *app, + GError **error); +gboolean gsm_app_is_running (GsmApp *app); + +void gsm_app_exited (GsmApp *app, + guchar exit_code); +void gsm_app_died (GsmApp *app, + int signal); + +gboolean gsm_app_provides (GsmApp *app, + const char *service); +char **gsm_app_get_provides (GsmApp *app); +gboolean gsm_app_has_autostart_condition (GsmApp *app, + const char *condition); +gboolean gsm_app_get_registered (GsmApp *app); +void gsm_app_set_registered (GsmApp *app, + gboolean registered); + +gboolean gsm_app_save_to_keyfile (GsmApp *app, + GKeyFile *keyfile, + GError **error); G_END_DECLS diff --git a/gnome-session/gsm-autostart-app.c b/gnome-session/gsm-autostart-app.c new file mode 100644 index 00000000..fbac8e71 --- /dev/null +++ b/gnome-session/gsm-autostart-app.c @@ -0,0 +1,1505 @@ +/* -*- 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 + * 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 + +#include +#include + +#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); +} + +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); + } +} + +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); + } + + if (sd_booted () > 0) { + child_setup_func = (GSpawnChildSetupFunc) on_child_setup; + child_setup_data = app; + } + + 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, + "Desktop file %s for application %s could not be parsed or references a missing TryExec binary", + priv->desktop_filename, + 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); +} diff --git a/gnome-session/gsm-autostart-app.h b/gnome-session/gsm-autostart-app.h new file mode 100644 index 00000000..91a74a67 --- /dev/null +++ b/gnome-session/gsm-autostart-app.h @@ -0,0 +1,59 @@ +/* -*- 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 + * 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 . + */ + +#ifndef __GSM_AUTOSTART_APP_H__ +#define __GSM_AUTOSTART_APP_H__ + +#include "gsm-app.h" + +G_BEGIN_DECLS + +#define GSM_TYPE_AUTOSTART_APP (gsm_autostart_app_get_type ()) +G_DECLARE_DERIVABLE_TYPE (GsmAutostartApp, gsm_autostart_app, GSM, AUTOSTART_APP, GsmApp) + +struct _GsmAutostartAppClass +{ + GsmAppClass parent_class; + + /* signals */ + void (*condition_changed) (GsmApp *app, + gboolean condition); +}; + +GsmApp *gsm_autostart_app_new (const char *desktop_file, + gboolean mask_systemd, + GError **error); + +void gsm_autostart_app_add_provides (GsmAutostartApp *aapp, + const char *provides); + +#define GSM_AUTOSTART_APP_SYSTEMD_KEY "X-GNOME-HiddenUnderSystemd" +#define GSM_AUTOSTART_APP_ENABLED_KEY "X-GNOME-Autostart-enabled" +#define GSM_AUTOSTART_APP_PHASE_KEY "X-GNOME-Autostart-Phase" +#define GSM_AUTOSTART_APP_PROVIDES_KEY "X-GNOME-Provides" +#define GSM_AUTOSTART_APP_STARTUP_ID_KEY "X-GNOME-Autostart-startup-id" +#define GSM_AUTOSTART_APP_AUTORESTART_KEY "X-GNOME-AutoRestart" +#define GSM_AUTOSTART_APP_DBUS_NAME_KEY "X-GNOME-DBus-Name" +#define GSM_AUTOSTART_APP_DBUS_PATH_KEY "X-GNOME-DBus-Path" +#define GSM_AUTOSTART_APP_DBUS_ARGS_KEY "X-GNOME-DBus-Start-Arguments" +#define GSM_AUTOSTART_APP_DISCARD_KEY "X-GNOME-Autostart-discard-exec" + +G_END_DECLS + +#endif /* __GSM_AUTOSTART_APP_H__ */ diff --git a/gnome-session/gsm-client.c b/gnome-session/gsm-client.c index 7e0e3f20..c8123eab 100644 --- a/gnome-session/gsm-client.c +++ b/gnome-session/gsm-client.c @@ -19,32 +19,29 @@ #include "config.h" -#include - #include "gsm-client.h" -#include "org.gnome.SessionManager.ClientPrivate.h" - -struct _GsmClient -{ - GObject parent_instance; +#include "org.gnome.SessionManager.Client.h" - char *id; - char *app_id; - char *bus_name; +static guint32 client_serial = 1; - GPid caller_pid; - - GDBusConnection *connection; - GsmExportedClientPrivate *skeleton; - guint watch_id; -}; +typedef struct +{ + char *id; + char *startup_id; + char *app_id; + guint status; + GsmExportedClient *skeleton; + GDBusConnection *connection; +} GsmClientPrivate; typedef enum { - PROP_0, + PROP_STARTUP_ID = 1, PROP_APP_ID, - PROP_BUS_NAME, + PROP_STATUS, } GsmClientProperty; +static GParamSpec *props[PROP_STATUS + 1] = { NULL, }; + enum { DISCONNECTED, END_SESSION_RESPONSE, @@ -53,81 +50,156 @@ enum { static guint signals[LAST_SIGNAL] = { 0 }; -G_DEFINE_TYPE (GsmClient, gsm_client, G_TYPE_OBJECT); +G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GsmClient, gsm_client, G_TYPE_OBJECT) + +#define GSM_CLIENT_DBUS_IFACE "org.gnome.SessionManager.Client" + +static const GDBusErrorEntry gsm_client_error_entries[] = { + { GSM_CLIENT_ERROR_GENERAL, GSM_CLIENT_DBUS_IFACE ".GeneralError" }, + { GSM_CLIENT_ERROR_NOT_REGISTERED, GSM_CLIENT_DBUS_IFACE ".NotRegistered" } +}; + +GQuark +gsm_client_error_quark (void) +{ + static volatile gsize quark_volatile = 0; + + g_dbus_error_register_error_domain ("gsm_client_error", + &quark_volatile, + gsm_client_error_entries, + G_N_ELEMENTS (gsm_client_error_entries)); + return quark_volatile; +} static guint32 get_next_client_serial (void) { - static guint32 next_serial = 1; guint32 serial; - serial = next_serial++; + serial = client_serial++; - if ((gint32)next_serial < 0) - next_serial = 1; + if ((gint32)client_serial < 0) { + client_serial = 1; + } return serial; } static gboolean -handle_end_session_response (GsmExportedClientPrivate *skeleton, - GDBusMethodInvocation *invocation, - gboolean is_ok, - const char *reason, - GsmClient *client) +gsm_client_get_startup_id (GsmExportedClient *skeleton, + GDBusMethodInvocation *invocation, + GsmClient *client) { - g_debug ("GsmClient(%s): got EndSessionResponse is-ok:%d reason=%s", - client->app_id, is_ok, reason); + GsmClientPrivate *priv = gsm_client_get_instance_private (client); - g_signal_emit (client, signals[END_SESSION_RESPONSE], 0, is_ok, reason); + gsm_exported_client_complete_get_startup_id (skeleton, invocation, priv->startup_id); + return TRUE; +} + +static gboolean +gsm_client_get_app_id (GsmExportedClient *skeleton, + GDBusMethodInvocation *invocation, + GsmClient *client) +{ + GsmClientPrivate *priv = gsm_client_get_instance_private (client); - gsm_exported_client_private_complete_end_session_response (skeleton, invocation); + gsm_exported_client_complete_get_app_id (skeleton, invocation, priv->app_id); return TRUE; } static gboolean -register_client (GsmClient *client) +gsm_client_get_restart_style_hint (GsmExportedClient *skeleton, + GDBusMethodInvocation *invocation, + GsmClient *client) { - g_autoptr (GError) error = NULL; + guint hint; - client->connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); - if (error != NULL) { - g_critical ("GsmClient: Couldn't connect to session bus: %s", - error->message); - return FALSE; - } + hint = GSM_CLIENT_GET_CLASS (client)->impl_get_restart_style_hint (client); + gsm_exported_client_complete_get_restart_style_hint (skeleton, invocation, hint); + return TRUE; +} + +static gboolean +gsm_client_get_status (GsmExportedClient *skeleton, + GDBusMethodInvocation *invocation, + GsmClient *client) +{ + GsmClientPrivate *priv = gsm_client_get_instance_private (client); + + gsm_exported_client_complete_get_status (skeleton, invocation, priv->status); + return TRUE; +} + +static gboolean +gsm_client_get_unix_process_id (GsmExportedClient *skeleton, + GDBusMethodInvocation *invocation, + GsmClient *client) +{ + guint pid; + + pid = GSM_CLIENT_GET_CLASS (client)->impl_get_unix_process_id (client); + gsm_exported_client_complete_get_unix_process_id (skeleton, invocation, pid); + return TRUE; +} + +static gboolean +gsm_client_stop_dbus (GsmExportedClient *skeleton, + GDBusMethodInvocation *invocation, + GsmClient *client) +{ + GError *error = NULL; + gsm_client_stop (client, &error); - client->skeleton = gsm_exported_client_private_skeleton_new (); - g_debug ("exporting client to object path: %s", client->id); - g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (client->skeleton), - client->connection, client->id, &error); if (error != NULL) { - g_critical ("GsmClient: error exporting on session bus: %s", - error->message); - return FALSE; + g_dbus_method_invocation_take_error (invocation, error); + } else { + gsm_exported_client_complete_stop (skeleton, invocation); } - g_signal_connect (client->skeleton, - "handle-end-session-response", - G_CALLBACK (handle_end_session_response), - client); - return TRUE; } -static void -on_client_vanished (GDBusConnection *connection, - const char *name, - gpointer user_data) +static gboolean +register_client (GsmClient *client) { - GsmClient *client = user_data; + GsmClientPrivate *priv = gsm_client_get_instance_private (client); + GError *error = NULL; + GsmExportedClient *skeleton; + + priv->connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + if (priv->connection == NULL) { + g_critical ("error getting session bus: %s", error->message); + g_error_free (error); + return FALSE; + } + + skeleton = gsm_exported_client_skeleton_new (); + priv->skeleton = skeleton; + g_debug ("exporting client to object path: %s", priv->id); + g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (skeleton), + priv->connection, + priv->id, &error); - g_bus_unwatch_name (client->watch_id); - client->watch_id = 0; + if (error != NULL) { + g_critical ("error exporting client on session bus: %s", error->message); + g_error_free (error); + return FALSE; + } - client->caller_pid = 0; + g_signal_connect (skeleton, "handle-get-app-id", + G_CALLBACK (gsm_client_get_app_id), client); + g_signal_connect (skeleton, "handle-get-restart-style-hint", + G_CALLBACK (gsm_client_get_restart_style_hint), client); + g_signal_connect (skeleton, "handle-get-startup-id", + G_CALLBACK (gsm_client_get_startup_id), client); + g_signal_connect (skeleton, "handle-get-status", + G_CALLBACK (gsm_client_get_status), client); + g_signal_connect (skeleton, "handle-get-unix-process-id", + G_CALLBACK (gsm_client_get_unix_process_id), client); + g_signal_connect (skeleton, "handle-stop", + G_CALLBACK (gsm_client_stop_dbus), client); - g_signal_emit (client, signals[DISCONNECTED], 0); + return TRUE; } static GObject * @@ -135,27 +207,23 @@ gsm_client_constructor (GType type, guint n_construct_properties, GObjectConstructParam *construct_properties) { + GsmClientPrivate *priv; GsmClient *client; + gboolean res; client = GSM_CLIENT (G_OBJECT_CLASS (gsm_client_parent_class)->constructor (type, n_construct_properties, construct_properties)); + priv = gsm_client_get_instance_private (client); - g_free (client->id); - client->id = g_strdup_printf ("/org/gnome/SessionManager/Client%u", get_next_client_serial ()); + g_free (priv->id); + priv->id = g_strdup_printf ("/org/gnome/SessionManager/Client%u", get_next_client_serial ()); - if (!register_client (client)) { + res = register_client (client); + if (! res) { g_warning ("Unable to register client with session bus"); } - client->watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION, - client->bus_name, - G_BUS_NAME_WATCHER_FLAGS_NONE, - NULL, - on_client_vanished, - client, - NULL); - return G_OBJECT (client); } @@ -164,35 +232,82 @@ gsm_client_init (GsmClient *client) { } -void -gsm_client_set_app_id (GsmClient *client, - const char *app_id) +static void +gsm_client_finalize (GObject *object) { - g_return_if_fail (GSM_IS_CLIENT (client)); + GsmClient *client; + GsmClientPrivate *priv; + + g_return_if_fail (object != NULL); + g_return_if_fail (GSM_IS_CLIENT (object)); - if (g_strcmp0 (client->app_id, app_id) == 0) - return; + client = GSM_CLIENT (object); + priv = gsm_client_get_instance_private (client); + + g_return_if_fail (priv != NULL); + + g_free (priv->id); + g_free (priv->startup_id); + g_free (priv->app_id); + + if (priv->skeleton != NULL) { + g_dbus_interface_skeleton_unexport_from_connection (G_DBUS_INTERFACE_SKELETON (priv->skeleton), + priv->connection); + g_clear_object (&priv->skeleton); + } - g_free (client->app_id); - client->app_id = g_strdup (app_id ?: ""); + g_clear_object (&priv->connection); - g_object_notify (G_OBJECT (client), "app-id"); + G_OBJECT_CLASS (gsm_client_parent_class)->finalize (object); +} + +void +gsm_client_set_status (GsmClient *client, + guint status) +{ + GsmClientPrivate *priv = gsm_client_get_instance_private (client); + + g_return_if_fail (GSM_IS_CLIENT (client)); + if (priv->status != status) { + priv->status = status; + g_object_notify_by_pspec (G_OBJECT (client), props[PROP_STATUS]); + } } static void -gsm_client_set_bus_name (GsmClient *client, - const char *bus_name) +gsm_client_set_startup_id (GsmClient *client, + const char *startup_id) { + GsmClientPrivate *priv = gsm_client_get_instance_private (client); + g_return_if_fail (GSM_IS_CLIENT (client)); - g_return_if_fail (bus_name != NULL); - if (g_strcmp0 (client->bus_name, bus_name) == 0) - return; + g_free (priv->startup_id); + + if (startup_id != NULL) { + priv->startup_id = g_strdup (startup_id); + } else { + priv->startup_id = g_strdup (""); + } + g_object_notify_by_pspec (G_OBJECT (client), props[PROP_STARTUP_ID]); +} + +void +gsm_client_set_app_id (GsmClient *client, + const char *app_id) +{ + GsmClientPrivate *priv = gsm_client_get_instance_private (client); - g_free (client->bus_name); - client->bus_name = g_strdup (bus_name); + g_return_if_fail (GSM_IS_CLIENT (client)); - g_object_notify (G_OBJECT (client), "bus-name"); + g_free (priv->app_id); + + if (app_id != NULL) { + priv->app_id = g_strdup (app_id); + } else { + priv->app_id = g_strdup (""); + } + g_object_notify_by_pspec (G_OBJECT (client), props[PROP_APP_ID]); } static void @@ -201,14 +316,19 @@ gsm_client_set_property (GObject *object, const GValue *value, GParamSpec *pspec) { - GsmClient *self = GSM_CLIENT (object); + GsmClient *self; + + self = GSM_CLIENT (object); switch ((GsmClientProperty) prop_id) { + case PROP_STARTUP_ID: + gsm_client_set_startup_id (self, g_value_get_string (value)); + break; case PROP_APP_ID: gsm_client_set_app_id (self, g_value_get_string (value)); break; - case PROP_BUS_NAME: - gsm_client_set_bus_name (self, g_value_get_string (value)); + case PROP_STATUS: + gsm_client_set_status (self, g_value_get_uint (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -223,13 +343,17 @@ gsm_client_get_property (GObject *object, GParamSpec *pspec) { GsmClient *self = GSM_CLIENT (object); + GsmClientPrivate *priv = gsm_client_get_instance_private (self); switch ((GsmClientProperty) prop_id) { + case PROP_STARTUP_ID: + g_value_set_string (value, priv->startup_id); + break; case PROP_APP_ID: - g_value_set_string (value, self->app_id); + g_value_set_string (value, priv->app_id); break; - case PROP_BUS_NAME: - g_value_set_string (value, self->bus_name); + case PROP_STATUS: + g_value_set_uint (value, priv->status); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -237,29 +361,30 @@ gsm_client_get_property (GObject *object, } } +static gboolean +default_stop (GsmClient *client, + GError **error) +{ + g_return_val_if_fail (GSM_IS_CLIENT (client), FALSE); + + g_warning ("Stop not implemented"); + + return TRUE; +} + static void gsm_client_dispose (GObject *object) { GsmClient *client; + GsmClientPrivate *priv; g_return_if_fail (object != NULL); g_return_if_fail (GSM_IS_CLIENT (object)); - client = GSM_CLIENT (object); - - g_free (client->id); - g_free (client->app_id); - g_free (client->bus_name); - - if (client->skeleton != NULL) { - g_dbus_interface_skeleton_unexport_from_connection (G_DBUS_INTERFACE_SKELETON (client->skeleton), - client->connection); - g_clear_object (&client->skeleton); - } - g_clear_object (&client->connection); + client = GSM_CLIENT (object); + priv = gsm_client_get_instance_private (client); - if (client->watch_id != 0) - g_bus_unwatch_name (client->watch_id); + g_debug ("GsmClient: disposing %s", priv->id); G_OBJECT_CLASS (gsm_client_parent_class)->dispose (object); } @@ -272,51 +397,60 @@ gsm_client_class_init (GsmClientClass *klass) object_class->get_property = gsm_client_get_property; object_class->set_property = gsm_client_set_property; object_class->constructor = gsm_client_constructor; + object_class->finalize = gsm_client_finalize; object_class->dispose = gsm_client_dispose; + klass->impl_stop = default_stop; + signals[DISCONNECTED] = g_signal_new ("disconnected", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, - 0, NULL, NULL, NULL, + G_STRUCT_OFFSET (GsmClientClass, disconnected), + NULL, NULL, NULL, G_TYPE_NONE, 0); signals[END_SESSION_RESPONSE] = g_signal_new ("end-session-response", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, - 0, NULL, NULL, NULL, + G_STRUCT_OFFSET (GsmClientClass, end_session_response), + NULL, NULL, NULL, G_TYPE_NONE, - 2, G_TYPE_BOOLEAN, G_TYPE_STRING); - - g_object_class_install_property (object_class, - PROP_APP_ID, - g_param_spec_string ("app-id", - "app-id", - "app-id", - "", - G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY)); - g_object_class_install_property (object_class, - PROP_BUS_NAME, - g_param_spec_string ("bus-name", - "bus-name", - "bus-name", - NULL, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY)); + 4, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, G_TYPE_STRING); + + props[PROP_STARTUP_ID] = + g_param_spec_string ("startup-id", + "startup-id", + "startup-id", + "", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + props[PROP_APP_ID] = + g_param_spec_string ("app-id", + "app-id", + "app-id", + "", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + props[PROP_STATUS] = + g_param_spec_uint ("status", + "status", + "status", + 0, + G_MAXINT, + GSM_CLIENT_UNREGISTERED, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (object_class, G_N_ELEMENTS (props), props); } const char * gsm_client_peek_id (GsmClient *client) { - g_return_val_if_fail (GSM_IS_CLIENT (client), NULL); - return client->id; -} + GsmClientPrivate *priv = gsm_client_get_instance_private (client); -const char * -gsm_client_peek_bus_name (GsmClient *client) -{ g_return_val_if_fail (GSM_IS_CLIENT (client), NULL); - return client->bus_name; + + return priv->id; } /** @@ -332,8 +466,54 @@ gsm_client_peek_bus_name (GsmClient *client) const char * gsm_client_peek_app_id (GsmClient *client) { + GsmClientPrivate *priv = gsm_client_get_instance_private (client); + + g_return_val_if_fail (GSM_IS_CLIENT (client), NULL); + + return priv->app_id; +} + +const char * +gsm_client_peek_startup_id (GsmClient *client) +{ + GsmClientPrivate *priv = gsm_client_get_instance_private (client); + g_return_val_if_fail (GSM_IS_CLIENT (client), NULL); - return client->app_id; + + return priv->startup_id; +} + +guint +gsm_client_peek_status (GsmClient *client) +{ + GsmClientPrivate *priv = gsm_client_get_instance_private (client); + + g_return_val_if_fail (GSM_IS_CLIENT (client), GSM_CLIENT_UNREGISTERED); + + return priv->status; +} + +guint +gsm_client_peek_restart_style_hint (GsmClient *client) +{ + g_return_val_if_fail (GSM_IS_CLIENT (client), GSM_CLIENT_RESTART_NEVER); + + return GSM_CLIENT_GET_CLASS (client)->impl_get_restart_style_hint (client); +} + +/** + * gsm_client_get_app_name: + * @client: a #GsmClient. + * + * Returns: a copy of the application name of the client, or %NULL if no such + * name is known. + **/ +char * +gsm_client_get_app_name (GsmClient *client) +{ + g_return_val_if_fail (GSM_IS_CLIENT (client), NULL); + + return GSM_CLIENT_GET_CLASS (client)->impl_get_app_name (client); } gboolean @@ -342,10 +522,7 @@ gsm_client_cancel_end_session (GsmClient *client, { g_return_val_if_fail (GSM_IS_CLIENT (client), FALSE); - g_debug ("GsmClient: sending CancelEndSession signal to %s", client->bus_name); - - gsm_exported_client_private_emit_cancel_end_session (client->skeleton); - return TRUE; + return GSM_CLIENT_GET_CLASS (client)->impl_cancel_end_session (client, error); } @@ -356,10 +533,7 @@ gsm_client_query_end_session (GsmClient *client, { g_return_val_if_fail (GSM_IS_CLIENT (client), FALSE); - g_debug ("GsmClient: sending QueryEndSession signal to %s", client->bus_name); - - gsm_exported_client_private_emit_query_end_session (client->skeleton, flags); - return TRUE; + return GSM_CLIENT_GET_CLASS (client)->impl_query_end_session (client, flags, error); } gboolean @@ -369,10 +543,7 @@ gsm_client_end_session (GsmClient *client, { g_return_val_if_fail (GSM_IS_CLIENT (client), FALSE); - g_debug ("GsmClient: sending EndSession signal to %s", client->bus_name); - - gsm_exported_client_private_emit_end_session (client->skeleton, flags); - return TRUE; + return GSM_CLIENT_GET_CLASS (client)->impl_end_session (client, flags, error); } gboolean @@ -381,18 +552,32 @@ gsm_client_stop (GsmClient *client, { g_return_val_if_fail (GSM_IS_CLIENT (client), FALSE); - g_debug ("GsmClient: sending Stop signal to %s", client->bus_name); + return GSM_CLIENT_GET_CLASS (client)->impl_stop (client, error); +} - gsm_exported_client_private_emit_stop (client->skeleton); - return TRUE; +void +gsm_client_disconnected (GsmClient *client) +{ + g_signal_emit (client, signals[DISCONNECTED], 0); } -GsmClient * -gsm_client_new (const char *app_id, - const char *bus_name) +GKeyFile * +gsm_client_save (GsmClient *client, + GsmApp *app, + GError **error) +{ + g_return_val_if_fail (GSM_IS_CLIENT (client), NULL); + + return GSM_CLIENT_GET_CLASS (client)->impl_save (client, app, error); +} + +void +gsm_client_end_session_response (GsmClient *client, + gboolean is_ok, + gboolean do_last, + gboolean cancel, + const char *reason) { - return g_object_new (GSM_TYPE_CLIENT, - "app-id", app_id, - "bus-name", bus_name, - NULL); + g_signal_emit (client, signals[END_SESSION_RESPONSE], 0, + is_ok, do_last, cancel, reason); } diff --git a/gnome-session/gsm-client.h b/gnome-session/gsm-client.h index 120dbda7..dbd9da41 100644 --- a/gnome-session/gsm-client.h +++ b/gnome-session/gsm-client.h @@ -26,24 +26,88 @@ G_BEGIN_DECLS +#define GSM_TYPE_CLIENT (gsm_client_get_type ()) +G_DECLARE_DERIVABLE_TYPE (GsmClient, gsm_client, GSM, CLIENT, GObject) + +typedef struct _GsmApp GsmApp; +typedef struct _GsmClient GsmClient; +typedef struct _GsmClientClass GsmClientClass; + +typedef enum { + GSM_CLIENT_UNREGISTERED = 0, + GSM_CLIENT_REGISTERED, + GSM_CLIENT_FINISHED, + GSM_CLIENT_FAILED +} GsmClientStatus; + +typedef enum { + GSM_CLIENT_RESTART_NEVER = 0, + GSM_CLIENT_RESTART_IF_RUNNING, + GSM_CLIENT_RESTART_ANYWAY, + GSM_CLIENT_RESTART_IMMEDIATELY +} GsmClientRestartStyle; + typedef enum { - GSM_CLIENT_END_SESSION_FLAG_NONE = 0, GSM_CLIENT_END_SESSION_FLAG_FORCEFUL = 1 << 0, + GSM_CLIENT_END_SESSION_FLAG_SAVE = 1 << 1, + GSM_CLIENT_END_SESSION_FLAG_LAST = 1 << 2 } GsmClientEndSessionFlag; -#define GSM_TYPE_CLIENT (gsm_client_get_type ()) -G_DECLARE_FINAL_TYPE (GsmClient, gsm_client, GSM, CLIENT, GObject) +struct _GsmClientClass +{ + GObjectClass parent_class; + + /* signals */ + void (*disconnected) (GsmClient *client); + void (*end_session_response) (GsmClient *client, + gboolean ok, + gboolean do_last, + gboolean cancel, + const char *reason); + + /* virtual methods */ + char * (*impl_get_app_name) (GsmClient *client); + GsmClientRestartStyle (*impl_get_restart_style_hint) (GsmClient *client); + guint (*impl_get_unix_process_id) (GsmClient *client); + gboolean (*impl_query_end_session) (GsmClient *client, + GsmClientEndSessionFlag flags, + GError **error); + gboolean (*impl_end_session) (GsmClient *client, + GsmClientEndSessionFlag flags, + GError **error); + gboolean (*impl_cancel_end_session) (GsmClient *client, + GError **error); + gboolean (*impl_stop) (GsmClient *client, + GError **error); + GKeyFile * (*impl_save) (GsmClient *client, + GsmApp *app, + GError **error); +}; -GsmClient * gsm_client_new (const char *app_id, - const char *bus_name); +typedef enum +{ + GSM_CLIENT_ERROR_GENERAL = 0, + GSM_CLIENT_ERROR_NOT_REGISTERED, + GSM_CLIENT_NUM_ERRORS +} GsmClientError; + +#define GSM_CLIENT_ERROR gsm_client_error_quark () +GQuark gsm_client_error_quark (void); const char *gsm_client_peek_id (GsmClient *client); -const char * gsm_client_peek_bus_name (GsmClient *client); + +const char * gsm_client_peek_startup_id (GsmClient *client); const char * gsm_client_peek_app_id (GsmClient *client); +guint gsm_client_peek_restart_style_hint (GsmClient *client); +guint gsm_client_peek_status (GsmClient *client); + +char *gsm_client_get_app_name (GsmClient *client); void gsm_client_set_app_id (GsmClient *client, const char *app_id); +void gsm_client_set_status (GsmClient *client, + guint status); gboolean gsm_client_end_session (GsmClient *client, GsmClientEndSessionFlag flags, @@ -54,8 +118,23 @@ gboolean gsm_client_query_end_session (GsmClient gboolean gsm_client_cancel_end_session (GsmClient *client, GError **error); +void gsm_client_disconnected (GsmClient *client); + +GKeyFile *gsm_client_save (GsmClient *client, + GsmApp *app, + GError **error); + gboolean gsm_client_stop (GsmClient *client, GError **error); + +/* private */ + +void gsm_client_end_session_response (GsmClient *client, + gboolean is_ok, + gboolean do_last, + gboolean cancel, + const char *reason); + G_END_DECLS #endif /* __GSM_CLIENT_H__ */ diff --git a/gnome-session/gsm-dbus-client.c b/gnome-session/gsm-dbus-client.c new file mode 100644 index 00000000..746b1e02 --- /dev/null +++ b/gnome-session/gsm-dbus-client.c @@ -0,0 +1,439 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * 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 + * 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 "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "org.gnome.SessionManager.ClientPrivate.h" +#include "gsm-dbus-client.h" + +#include "gsm-manager.h" +#include "gsm-util.h" + +#define SM_DBUS_NAME "org.gnome.SessionManager" +#define SM_DBUS_CLIENT_PRIVATE_INTERFACE "org.gnome.SessionManager.ClientPrivate" + +struct _GsmDBusClient +{ + GObject parent_instance; + + char *bus_name; + GPid caller_pid; + GsmClientRestartStyle restart_style_hint; + + GDBusConnection *connection; + GsmExportedClientPrivate *skeleton; + guint watch_id; +}; + +typedef enum { + PROP_BUS_NAME = 1, +} GsmDBusClientProperty; + +static GParamSpec *props[PROP_BUS_NAME + 1] = { NULL, }; + +G_DEFINE_TYPE (GsmDBusClient, gsm_dbus_client, GSM_TYPE_CLIENT) + +static gboolean +setup_connection (GsmDBusClient *client) +{ + GError *error = NULL; + + if (client->connection == NULL) { + client->connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + if (error != NULL) { + g_debug ("GsmDbusClient: Couldn't connect to session bus: %s", + error->message); + g_error_free (error); + return FALSE; + } + } + + return TRUE; +} + +static gboolean +handle_end_session_response (GsmExportedClientPrivate *skeleton, + GDBusMethodInvocation *invocation, + gboolean is_ok, + const char *reason, + GsmDBusClient *client) +{ + g_debug ("GsmDBusClient: got EndSessionResponse is-ok:%d reason=%s", is_ok, reason); + gsm_client_end_session_response (GSM_CLIENT (client), + is_ok, FALSE, FALSE, reason); + + gsm_exported_client_private_complete_end_session_response (skeleton, invocation); + return TRUE; +} + +static GObject * +gsm_dbus_client_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GsmDBusClient *client; + GError *error = NULL; + GsmExportedClientPrivate *skeleton; + + client = GSM_DBUS_CLIENT (G_OBJECT_CLASS (gsm_dbus_client_parent_class)->constructor (type, + n_construct_properties, + construct_properties)); + + if (! setup_connection (client)) { + g_object_unref (client); + return NULL; + } + + skeleton = gsm_exported_client_private_skeleton_new (); + client->skeleton = skeleton; + g_debug ("exporting dbus client to object path: %s", gsm_client_peek_id (GSM_CLIENT (client))); + g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (skeleton), + client->connection, + gsm_client_peek_id (GSM_CLIENT (client)), + &error); + + if (error != NULL) { + g_critical ("error exporting client private on session bus: %s", error->message); + g_error_free (error); + g_object_unref (client); + return NULL; + } + + g_signal_connect (skeleton, "handle-end-session-response", + G_CALLBACK (handle_end_session_response), client); + + return G_OBJECT (client); +} + +static void +gsm_dbus_client_init (GsmDBusClient *client) +{ +} + +/* adapted from PolicyKit */ +static gboolean +get_caller_info (GsmDBusClient *client, + const char *sender, + uid_t *calling_uid_out, + pid_t *calling_pid_out) +{ + g_autoptr(GDBusConnection) connection = NULL; + GError *error; + g_autoptr(GVariant) uid_variant = NULL; + g_autoptr(GVariant) pid_variant = NULL; + uid_t uid; + pid_t pid; + + if (sender == NULL) { + return FALSE; + } + + error = NULL; + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + + if (error != NULL) { + g_warning ("error getting session bus: %s", error->message); + g_error_free (error); + return FALSE; + } + + uid_variant = g_dbus_connection_call_sync (connection, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "GetConnectionUnixUser", + g_variant_new ("(s)", sender), + G_VARIANT_TYPE ("(u)"), + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, &error); + + if (error != NULL) { + g_debug ("GetConnectionUnixUser() failed: %s", error->message); + g_error_free (error); + return FALSE; + } + + pid_variant = g_dbus_connection_call_sync (connection, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "GetConnectionUnixProcessID", + g_variant_new ("(s)", sender), + G_VARIANT_TYPE ("(u)"), + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, &error); + + if (error != NULL) { + g_debug ("GetConnectionUnixProcessID() failed: %s", error->message); + g_error_free (error); + return FALSE; + } + + g_variant_get (uid_variant, "(u)", &uid); + g_variant_get (pid_variant, "(u)", &pid); + + if (calling_uid_out != NULL) { + *calling_uid_out = uid; + } + if (calling_pid_out != NULL) { + *calling_pid_out = pid; + } + + g_debug ("uid = %d", uid); + g_debug ("pid = %d", pid); + + return TRUE; +} + +static void +on_client_vanished (GDBusConnection *connection, + const char *name, + gpointer user_data) +{ + GsmDBusClient *client = user_data; + + g_bus_unwatch_name (client->watch_id); + client->watch_id = 0; + + gsm_client_disconnected (GSM_CLIENT (client)); +} + +static void +gsm_dbus_client_set_bus_name (GsmDBusClient *client, + const char *bus_name) +{ + g_return_if_fail (GSM_IS_DBUS_CLIENT (client)); + + g_free (client->bus_name); + + client->bus_name = g_strdup (bus_name); + g_object_notify_by_pspec (G_OBJECT (client), props[PROP_BUS_NAME]); + + if (!get_caller_info (client, bus_name, NULL, &client->caller_pid)) { + client->caller_pid = 0; + } + + client->watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION, + bus_name, + G_BUS_NAME_WATCHER_FLAGS_NONE, + NULL, + on_client_vanished, + client, + NULL); +} + +const char * +gsm_dbus_client_get_bus_name (GsmDBusClient *client) +{ + g_return_val_if_fail (GSM_IS_DBUS_CLIENT (client), NULL); + + return client->bus_name; +} + +static void +gsm_dbus_client_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GsmDBusClient *self = GSM_DBUS_CLIENT (object); + + switch ((GsmDBusClientProperty) prop_id) { + case PROP_BUS_NAME: + gsm_dbus_client_set_bus_name (self, g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gsm_dbus_client_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GsmDBusClient *self = GSM_DBUS_CLIENT (object); + + switch ((GsmDBusClientProperty) prop_id) { + case PROP_BUS_NAME: + g_value_set_string (value, self->bus_name); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gsm_dbus_client_finalize (GObject *object) +{ + GsmDBusClient *client = (GsmDBusClient *) object; + + g_free (client->bus_name); + + if (client->skeleton != NULL) { + g_dbus_interface_skeleton_unexport_from_connection (G_DBUS_INTERFACE_SKELETON (client->skeleton), + client->connection); + g_clear_object (&client->skeleton); + } + + g_clear_object (&client->connection); + + if (client->watch_id != 0) + g_bus_unwatch_name (client->watch_id); + + G_OBJECT_CLASS (gsm_dbus_client_parent_class)->finalize (object); +} + +static GKeyFile * +dbus_client_save (GsmClient *client, + GsmApp *app, + GError **error) +{ + g_debug ("GsmDBusClient: saving client with id %s", + gsm_client_peek_id (client)); + + /* FIXME: We still don't support client saving for D-Bus + * session clients */ + + return NULL; +} + +static gboolean +dbus_client_stop (GsmClient *client, + GError **error) +{ + GsmDBusClient *dbus_client = (GsmDBusClient *) client; + gsm_exported_client_private_emit_stop (dbus_client->skeleton); + return TRUE; +} + +static char * +dbus_client_get_app_name (GsmClient *client) +{ + /* Always use app-id instead */ + return NULL; +} + +static GsmClientRestartStyle +dbus_client_get_restart_style_hint (GsmClient *client) +{ + return (GSM_DBUS_CLIENT (client)->restart_style_hint); +} + +static guint +dbus_client_get_unix_process_id (GsmClient *client) +{ + return (GSM_DBUS_CLIENT (client)->caller_pid); +} + +static gboolean +dbus_client_query_end_session (GsmClient *client, + GsmClientEndSessionFlag flags, + GError **error) +{ + GsmDBusClient *dbus_client = (GsmDBusClient *) client; + + if (dbus_client->bus_name == NULL) { + g_set_error (error, + GSM_CLIENT_ERROR, + GSM_CLIENT_ERROR_NOT_REGISTERED, + "Client is not registered"); + return FALSE; + } + + g_debug ("GsmDBusClient: sending QueryEndSession signal to %s", dbus_client->bus_name); + + gsm_exported_client_private_emit_query_end_session (dbus_client->skeleton, flags); + return TRUE; +} + +static gboolean +dbus_client_end_session (GsmClient *client, + GsmClientEndSessionFlag flags, + GError **error) +{ + GsmDBusClient *dbus_client = (GsmDBusClient *) client; + + gsm_exported_client_private_emit_end_session (dbus_client->skeleton, flags); + return TRUE; +} + +static gboolean +dbus_client_cancel_end_session (GsmClient *client, + GError **error) +{ + GsmDBusClient *dbus_client = (GsmDBusClient *) client; + gsm_exported_client_private_emit_cancel_end_session (dbus_client->skeleton); + return TRUE; +} + +static void +gsm_dbus_client_class_init (GsmDBusClientClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GsmClientClass *client_class = GSM_CLIENT_CLASS (klass); + + object_class->finalize = gsm_dbus_client_finalize; + object_class->constructor = gsm_dbus_client_constructor; + object_class->get_property = gsm_dbus_client_get_property; + object_class->set_property = gsm_dbus_client_set_property; + + client_class->impl_save = dbus_client_save; + client_class->impl_stop = dbus_client_stop; + client_class->impl_query_end_session = dbus_client_query_end_session; + client_class->impl_end_session = dbus_client_end_session; + client_class->impl_cancel_end_session = dbus_client_cancel_end_session; + client_class->impl_get_app_name = dbus_client_get_app_name; + client_class->impl_get_restart_style_hint = dbus_client_get_restart_style_hint; + client_class->impl_get_unix_process_id = dbus_client_get_unix_process_id; + + props[PROP_BUS_NAME] = + g_param_spec_string ("bus-name", + "bus-name", + "bus-name", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (object_class, G_N_ELEMENTS (props), props); +} + +GsmClient * +gsm_dbus_client_new (const char *startup_id, + const char *bus_name) +{ + GsmDBusClient *client; + + client = g_object_new (GSM_TYPE_DBUS_CLIENT, + "startup-id", startup_id, + "bus-name", bus_name, + NULL); + + return GSM_CLIENT (client); +} diff --git a/gnome-session/gsm-dbus-client.h b/gnome-session/gsm-dbus-client.h new file mode 100644 index 00000000..459d4963 --- /dev/null +++ b/gnome-session/gsm-dbus-client.h @@ -0,0 +1,35 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * 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 + * 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 . + */ + +#ifndef __GSM_DBUS_CLIENT_H__ +#define __GSM_DBUS_CLIENT_H__ + +#include "gsm-client.h" + +G_BEGIN_DECLS + +#define GSM_TYPE_DBUS_CLIENT (gsm_dbus_client_get_type ()) +G_DECLARE_FINAL_TYPE (GsmDBusClient, gsm_dbus_client, GSM, DBUS_CLIENT, GsmClient) + +GsmClient * gsm_dbus_client_new (const char *startup_id, + const char *bus_name); +const char * gsm_dbus_client_get_bus_name (GsmDBusClient *client); + +G_END_DECLS + +#endif /* __GSM_DBUS_CLIENT_H__ */ diff --git a/gnome-session/gsm-fail-whale-dialog.c b/gnome-session/gsm-fail-whale-dialog.c new file mode 100644 index 00000000..9b2bca4f --- /dev/null +++ b/gnome-session/gsm-fail-whale-dialog.c @@ -0,0 +1,195 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2011,2025 Red Hat, Inc. + * Copyright (C) 2019 Canonical Ltd. + * + * 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 + * 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 . + * + * Authors: + * Colin Walters + * Marco Trevisan + * Adrian Vovk + */ + +#include + +#include + +#include +#include + +#include "gsm-icon-names.h" + +typedef struct GsmFailContext { + GMainLoop *loop; + GList *monitors; + + gboolean debug_mode; + gboolean allow_logout; + gboolean extensions; +} GsmFailContext; + +static gboolean +on_close_req (void) +{ + /* Suppress any further handling, including destruction of the window. */ + return TRUE; +} + +static void +on_logout_clicked (GtkWidget *button, + GsmFailContext *ctx) +{ + if (!ctx->debug_mode) { + g_spawn_command_line_async ("gnome-session-quit --force", NULL); + } + g_main_loop_quit (ctx->loop); +} + +static void +create_fail_dialog (GdkMonitor *monitor, + GsmFailContext *ctx) +{ + GtkWindow *dialog; + GtkBox *box; + GtkImage *icon; + gchar *markup; + GtkLabel *title; + gchar *message_text; + GtkLabel *message; + + dialog = GTK_WINDOW (gtk_window_new ()); + + gtk_window_set_title (dialog, ""); + gtk_window_set_icon_name (dialog, GSM_ICON_COMPUTER_FAIL); + + /* we only allow the window to be closed via on_monitors_changed */ + gtk_window_set_deletable (dialog, FALSE); + g_signal_connect(dialog, "close-request", G_CALLBACK (on_close_req), NULL); + + box = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 12)); + gtk_widget_set_valign (GTK_WIDGET (box), GTK_ALIGN_CENTER); + gtk_window_set_child (dialog, GTK_WIDGET (box)); + + icon = GTK_IMAGE (gtk_image_new_from_icon_name (GSM_ICON_COMPUTER_FAIL)); + gtk_image_set_pixel_size (icon, 128); + gtk_box_append (box, GTK_WIDGET (icon)); + + + markup = g_strdup_printf ("%s", _("Oh no! Something has gone wrong.")); + title = GTK_LABEL (gtk_label_new (markup)); + gtk_label_set_use_markup (title, TRUE); + gtk_box_append (box, GTK_WIDGET (title)); + g_free (markup); + + if (!ctx->allow_logout) + message_text = _("A problem has occurred and the system can’t recover. Please contact a system administrator"); + else if (ctx->extensions) + message_text = _("A problem has occurred and the system can’t recover. All extensions have been disabled as a precaution."); + else + message_text = _("A problem has occurred and the system can’t recover.\nPlease log out and try again."); + message = GTK_LABEL (gtk_label_new (message_text)); + gtk_label_set_justify (message, GTK_JUSTIFY_CENTER); + gtk_label_set_wrap (message, TRUE); + gtk_box_append (box, GTK_WIDGET (message)); + + if (ctx->allow_logout) { + GtkButton *btn; + btn = GTK_BUTTON (gtk_button_new_with_mnemonic (_("_Log Out"))); + gtk_widget_set_halign (GTK_WIDGET (btn), GTK_ALIGN_CENTER); + g_signal_connect (btn, "clicked", + G_CALLBACK (on_logout_clicked), ctx); + gtk_box_append (box, GTK_WIDGET (btn)); + } + + gtk_window_present (dialog); + gtk_window_fullscreen_on_monitor (dialog, monitor); + + g_object_set_data_full (G_OBJECT (monitor), "gsm_fail_whale", + dialog, (GDestroyNotify) gtk_window_destroy); +} + +static void +on_monitors_changed (GListModel *monitors, + guint position, + guint removed, + guint added, + GsmFailContext *ctx) +{ + GList *cursor; + guint i; + + cursor = g_list_nth (ctx->monitors, position); + + for (i = position; i < position + added; i++) { + GdkMonitor *monitor = GDK_MONITOR (g_list_model_get_item (monitors, i)); + create_fail_dialog(monitor, ctx); + ctx->monitors = g_list_insert_before (ctx->monitors, cursor, monitor); + } + + for (i = 0; i < removed; i++) { + GList *next = cursor->next; + g_object_unref (G_OBJECT (cursor->data)); + ctx->monitors = g_list_delete_link (ctx->monitors, cursor); + cursor = next; + } +} + +int main (int argc, char *argv[]) +{ + GsmFailContext ctx = {}; + GOptionEntry entries[] = { + { "debug", 0, 0, G_OPTION_ARG_NONE, &ctx.debug_mode, N_("Enable debugging code"), NULL }, + { "allow-logout", 0, 0, G_OPTION_ARG_NONE, &ctx.allow_logout, N_("Allow logout"), NULL }, + { "extensions", 0, 0, G_OPTION_ARG_NONE, &ctx.extensions, N_("Show extension warning"), NULL }, + { NULL, 0, 0, 0, NULL, NULL, NULL } + }; + + GError *error = NULL; + GOptionContext *opts; + GListModel *monitors; + + bindtextdomain (GETTEXT_PACKAGE, LOCALE_DIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + opts = g_option_context_new (" - fail whale"); + g_option_context_add_main_entries (opts, entries, GETTEXT_PACKAGE); + if (!g_option_context_parse (opts, &argc, &argv, &error)) { + g_warning ("%s", error->message); + exit (1); + } + g_option_context_free (opts); + + /* Force-off allow_logout when running inside GDM, this is needed + * because the systemd service always passes --allow-logout + */ + if (g_strcmp0 (g_getenv ("RUNNING_UNDER_GDM"), "true") == 0) + ctx.allow_logout = FALSE; + + gtk_init (); + ctx.loop = g_main_loop_new (NULL, TRUE); + + monitors = gdk_display_get_monitors (gdk_display_get_default ()); + on_monitors_changed(monitors, 0, 0, g_list_model_get_n_items (monitors), &ctx); + g_signal_connect (monitors, "items-changed", G_CALLBACK (on_monitors_changed), &ctx); + + g_main_loop_run (ctx.loop); + + g_main_loop_unref (ctx.loop); + g_list_free_full (ctx.monitors, g_object_unref); + + return 0; +} + diff --git a/gnome-session/gsm-fail-whale.c b/gnome-session/gsm-fail-whale.c new file mode 100644 index 00000000..a0304f28 --- /dev/null +++ b/gnome-session/gsm-fail-whale.c @@ -0,0 +1,66 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * gsm-fail-whale.c + * Copyright (C) 2012 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 + * 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 "gsm-fail-whale.h" +#include "gsm-util.h" + +static void +on_fail_whale_failed (void) +{ + raise (SIGTERM); +} + +void +gsm_fail_whale_dialog_we_failed (gboolean debug_mode, + gboolean allow_logout, + GsmShellExtensions *extensions) +{ + gint i; + gchar *argv[5]; + static GPid pid = 0; + + if (pid != 0) { + return; + } + + i = 0; + argv[i++] = LIBEXECDIR "/gnome-session-failed"; + if (debug_mode) + argv[i++] = "--debug"; + if (allow_logout) + argv[i++] = "--allow-logout"; + if (extensions != NULL && gsm_shell_extensions_n_extensions (extensions) > 0) + argv[i++] = "--extensions"; + argv[i++] = NULL; + + if (!g_spawn_async (NULL, argv, (char **) gsm_util_listenv (), G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &pid, NULL)) { + exit (1); + } + + g_child_watch_add (pid, + (GChildWatchFunc)on_fail_whale_failed, + NULL); +} diff --git a/gnome-session/gsm-fail-whale.h b/gnome-session/gsm-fail-whale.h new file mode 100644 index 00000000..5b93dcab --- /dev/null +++ b/gnome-session/gsm-fail-whale.h @@ -0,0 +1,34 @@ +/* gsm-fail-whale.h + * Copyright (C) 2012 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 + * 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 . + */ + +#ifndef __GSM_FAIL_WHALE_H__ +#define __GSM_FAIL_WHALE_H__ + +#include + +#include "gsm-shell-extensions.h" + +G_BEGIN_DECLS + +void gsm_fail_whale_dialog_we_failed (gboolean debug_mode, + gboolean allow_logout, + GsmShellExtensions *extensions); + +G_END_DECLS + +#endif /* __GSM_FAIL_WHALE_H__ */ + diff --git a/gnome-session/gsm-icon-names.h b/gnome-session/gsm-icon-names.h new file mode 100644 index 00000000..82f28345 --- /dev/null +++ b/gnome-session/gsm-icon-names.h @@ -0,0 +1,28 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2011 Novell, 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 + * 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 . + */ + +#ifndef __GSM_ICON_NAMES_H__ +#define __GSM_ICON_NAMES_H__ + +#define GSM_ICON_COMPUTER_FAIL "computer-fail-symbolic" +#define GSM_ICON_INHIBITOR_DEFAULT "gnome-windows" +#define GSM_ICON_LOGOUT "system-log-out" +#define GSM_ICON_SHUTDOWN "system-shutdown" +#define GSM_ICON_XSMP_DEFAULT "system-run" + +#endif /*__GSM_ICON_NAMES_H__ */ diff --git a/gnome-session/gsm-inhibitor.c b/gnome-session/gsm-inhibitor.c index f1768578..13440beb 100644 --- a/gnome-session/gsm-inhibitor.c +++ b/gnome-session/gsm-inhibitor.c @@ -39,6 +39,7 @@ struct GsmInhibitorPrivate char *client_id; char *reason; guint flags; + guint toplevel_xid; guint cookie; GDBusConnection *connection; GsmExportedInhibitor *skeleton; @@ -53,6 +54,7 @@ enum { PROP_APP_ID, PROP_CLIENT_ID, PROP_FLAGS, + PROP_TOPLEVEL_XID, PROP_COOKIE }; @@ -148,6 +150,15 @@ gsm_inhibitor_get_flags (GsmExportedInhibitor *skeleton, return TRUE; } +static gboolean +gsm_inhibitor_get_toplevel_xid (GsmExportedInhibitor *skeleton, + GDBusMethodInvocation *invocation, + GsmInhibitor *inhibitor) +{ + gsm_exported_inhibitor_complete_get_toplevel_xid (skeleton, invocation, inhibitor->priv->toplevel_xid); + return TRUE; +} + static guint32 get_next_inhibitor_serial (void) { @@ -197,6 +208,8 @@ register_inhibitor (GsmInhibitor *inhibitor) G_CALLBACK (gsm_inhibitor_get_flags), inhibitor); g_signal_connect (skeleton, "handle-get-reason", G_CALLBACK (gsm_inhibitor_get_reason), inhibitor); + g_signal_connect (skeleton, "handle-get-toplevel-xid", + G_CALLBACK (gsm_inhibitor_get_toplevel_xid), inhibitor); return TRUE; } @@ -336,6 +349,18 @@ gsm_inhibitor_set_flags (GsmInhibitor *inhibitor, } } +static void +gsm_inhibitor_set_toplevel_xid (GsmInhibitor *inhibitor, + guint xid) +{ + g_return_if_fail (GSM_IS_INHIBITOR (inhibitor)); + + if (inhibitor->priv->toplevel_xid != xid) { + inhibitor->priv->toplevel_xid = xid; + g_object_notify (G_OBJECT (inhibitor), "toplevel-xid"); + } +} + const char * gsm_inhibitor_peek_bus_name (GsmInhibitor *inhibitor) { @@ -384,6 +409,14 @@ gsm_inhibitor_peek_flags (GsmInhibitor *inhibitor) return inhibitor->priv->flags; } +guint +gsm_inhibitor_peek_toplevel_xid (GsmInhibitor *inhibitor) +{ + g_return_val_if_fail (GSM_IS_INHIBITOR (inhibitor), 0); + + return inhibitor->priv->toplevel_xid; +} + guint gsm_inhibitor_peek_cookie (GsmInhibitor *inhibitor) { @@ -421,6 +454,9 @@ gsm_inhibitor_set_property (GObject *object, case PROP_COOKIE: gsm_inhibitor_set_cookie (self, g_value_get_uint (value)); break; + case PROP_TOPLEVEL_XID: + gsm_inhibitor_set_toplevel_xid (self, g_value_get_uint (value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -456,6 +492,9 @@ gsm_inhibitor_get_property (GObject *object, case PROP_COOKIE: g_value_set_uint (value, self->priv->cookie); break; + case PROP_TOPLEVEL_XID: + g_value_set_uint (value, self->priv->toplevel_xid); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -542,6 +581,15 @@ gsm_inhibitor_class_init (GsmInhibitorClass *klass) G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_TOPLEVEL_XID, + g_param_spec_uint ("toplevel-xid", + "toplevel-xid", + "toplevel-xid", + 0, + G_MAXINT, + 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_COOKIE, g_param_spec_uint ("cookie", @@ -555,6 +603,7 @@ gsm_inhibitor_class_init (GsmInhibitorClass *klass) GsmInhibitor * gsm_inhibitor_new (const char *app_id, + guint toplevel_xid, guint flags, const char *reason, const char *bus_name, @@ -567,6 +616,7 @@ gsm_inhibitor_new (const char *app_id, "reason", reason, "bus-name", bus_name, "flags", flags, + "toplevel-xid", toplevel_xid, "cookie", cookie, NULL); diff --git a/gnome-session/gsm-inhibitor.h b/gnome-session/gsm-inhibitor.h index 7b50271d..519b70cf 100644 --- a/gnome-session/gsm-inhibitor.h +++ b/gnome-session/gsm-inhibitor.h @@ -62,6 +62,7 @@ GQuark gsm_inhibitor_error_quark (void); GType gsm_inhibitor_get_type (void) G_GNUC_CONST; GsmInhibitor * gsm_inhibitor_new (const char *app_id, + guint toplevel_xid, guint flags, const char *reason, const char *bus_name, @@ -80,6 +81,7 @@ const char * gsm_inhibitor_peek_reason (GsmInhibitor *inhibitor); const char * gsm_inhibitor_peek_bus_name (GsmInhibitor *inhibitor); guint gsm_inhibitor_peek_cookie (GsmInhibitor *inhibitor); guint gsm_inhibitor_peek_flags (GsmInhibitor *inhibitor); +guint gsm_inhibitor_peek_toplevel_xid (GsmInhibitor *inhibitor); G_END_DECLS diff --git a/gnome-session/gsm-manager.c b/gnome-session/gsm-manager.c index b7f1788e..743a372e 100644 --- a/gnome-session/gsm-manager.c +++ b/gnome-session/gsm-manager.c @@ -44,62 +44,108 @@ #include -#include "gsm-app.h" -#include "gsm-client.h" +#include "gsm-store.h" #include "gsm-inhibitor.h" #include "gsm-presence.h" #include "gsm-shell.h" -#include "gsm-store.h" -#include "gsm-system.h" + +#include "gsm-dbus-client.h" + +#include "gsm-autostart-app.h" + #include "gsm-util.h" +#include "gsm-icon-names.h" +#include "gsm-system.h" +#include "gsm-shell-extensions.h" +#include "gsm-fail-whale.h" + +/* UUIDs for log messages */ +#define GSM_MANAGER_STARTUP_SUCCEEDED_MSGID "0ce153587afa4095832d233c17a88001" +#define GSM_MANAGER_UNRECOVERABLE_FAILURE_MSGID "10dd2dc188b54a5e98970f56499d1f73" #define GSM_MANAGER_DBUS_PATH "/org/gnome/SessionManager" #define GSM_MANAGER_DBUS_NAME "org.gnome.SessionManager" #define GSM_MANAGER_DBUS_IFACE "org.gnome.SessionManager" +/* Probably about the longest amount of time someone could reasonably + * want to wait, at least for something happening more than once. + * We can get deployed on very slow media though like CDROM devices, + * often with complex stacking/compressing filesystems on top, which + * is not a recipie for speed. Particularly now that we throw up + * a fail whale if required components don't show up quickly enough, + * let's make this fairly long. + */ +#define GSM_MANAGER_PHASE_TIMEOUT 90 /* seconds */ + +#define GDM_FLEXISERVER_COMMAND "gdmflexiserver" +#define GDM_FLEXISERVER_ARGS "--startnew Standard" + #define SESSION_SCHEMA "org.gnome.desktop.session" #define KEY_IDLE_DELAY "idle-delay" +#define KEY_SESSION_NAME "session-name" #define GSM_MANAGER_SCHEMA "org.gnome.SessionManager" #define KEY_LOGOUT_PROMPT "logout-prompt" +#define KEY_SHOW_FALLBACK_WARNING "show-fallback-warning" + +#define SCREENSAVER_SCHEMA "org.gnome.desktop.screensaver" +#define KEY_SLEEP_LOCK "lock-enabled" #define LOCKDOWN_SCHEMA "org.gnome.desktop.lockdown" #define KEY_DISABLE_LOG_OUT "disable-log-out" #define KEY_DISABLE_USER_SWITCHING "disable-user-switching" +static void app_registered (GsmApp *app, GParamSpec *spec, GsmManager *manager); + typedef enum { GSM_MANAGER_LOGOUT_NONE, GSM_MANAGER_LOGOUT_LOGOUT, GSM_MANAGER_LOGOUT_REBOOT, + GSM_MANAGER_LOGOUT_REBOOT_INTERACT, GSM_MANAGER_LOGOUT_SHUTDOWN, + GSM_MANAGER_LOGOUT_SHUTDOWN_INTERACT, } GsmManagerLogoutType; -struct _GsmManager +typedef struct { - GObject parent; - + gboolean failsafe; + gboolean systemd_managed; gboolean systemd_initialized; + gboolean manager_initialized; GsmStore *clients; GsmStore *inhibitors; GsmInhibitorFlag inhibited_actions; GsmStore *apps; GsmPresence *presence; char *session_name; + gboolean is_fallback_session : 1; /* Current status */ GsmManagerPhase phase; guint phase_timeout_id; + GSList *required_apps; + GSList *pending_apps; GsmManagerLogoutMode logout_mode; GSList *query_clients; + guint query_timeout_id; + /* This is used for GSM_MANAGER_PHASE_END_SESSION only at the moment, + * since it uses a sublist of all running client that replied in a + * specific way */ + GSList *next_query_clients; /* This is the action that will be done just before we exit */ GsmManagerLogoutType logout_type; + /* List of clients which were disconnected due to disabled condition + * and shouldn't be automatically restarted */ + GSList *condition_clients; + GSList *pending_end_session_tasks; GCancellable *end_session_cancellable; GSettings *settings; GSettings *session_settings; + GSettings *screensaver_settings; GSettings *lockdown_settings; GsmSystem *system; @@ -108,33 +154,45 @@ struct _GsmManager gboolean dbus_disconnected : 1; GsmShell *shell; - gulong shell_end_session_dialog_canceled_id; - gulong shell_end_session_dialog_open_failed_id; - gulong shell_end_session_dialog_confirmed_logout_id; - gulong shell_end_session_dialog_confirmed_shutdown_id; - gulong shell_end_session_dialog_confirmed_reboot_id; -}; + guint shell_end_session_dialog_canceled_id; + guint shell_end_session_dialog_open_failed_id; + guint shell_end_session_dialog_confirmed_logout_id; + guint shell_end_session_dialog_confirmed_shutdown_id; + guint shell_end_session_dialog_confirmed_reboot_id; +} GsmManagerPrivate; enum { PROP_0, + PROP_CLIENT_STORE, PROP_SESSION_NAME, + PROP_FALLBACK, + PROP_FAILSAFE, + PROP_SYSTEMD_MANAGED }; +enum { + PHASE_CHANGED, + LAST_SIGNAL +}; + +static guint signals [LAST_SIGNAL] = { 0 }; + static void gsm_manager_class_init (GsmManagerClass *klass); static void gsm_manager_init (GsmManager *manager); static gboolean _log_out_is_locked_down (GsmManager *manager); -static void on_client_end_session_response (GsmClient *client, - gboolean is_ok, - const char *reason, - GsmManager *manager); - +static void _handle_client_end_session_response (GsmManager *manager, + GsmClient *client, + gboolean is_ok, + gboolean do_last, + gboolean cancel, + const char *reason); static void show_shell_end_session_dialog (GsmManager *manager, GsmShellEndSessionDialogType type); static gpointer manager_object = NULL; -G_DEFINE_TYPE (GsmManager, gsm_manager, G_TYPE_OBJECT) +G_DEFINE_TYPE_WITH_PRIVATE (GsmManager, gsm_manager, G_TYPE_OBJECT) static const GDBusErrorEntry gsm_manager_error_entries[] = { { GSM_MANAGER_ERROR_GENERAL, GSM_MANAGER_DBUS_IFACE ".GeneralError" }, @@ -165,7 +223,7 @@ start_app_or_warn (GsmManager *manager, gboolean res; GError *error = NULL; - g_debug ("GsmManager: starting app '%s'", gsm_app_peek_app_id (app)); + g_debug ("GsmManager: starting app '%s'", gsm_app_peek_id (app)); res = gsm_app_start (app, &error); if (error != NULL) { @@ -175,6 +233,75 @@ start_app_or_warn (GsmManager *manager, return res; } +static gboolean +is_app_required (GsmManager *manager, + GsmApp *app) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + return g_slist_find (priv->required_apps, app) != NULL; +} + +static void +on_required_app_failure (GsmManager *manager, + GsmApp *app) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + const gchar *app_id; + gboolean allow_logout; + GsmShellExtensions *extensions; + + app_id = gsm_app_peek_app_id (app); + + if (g_str_equal (app_id, "org.gnome.Shell.desktop")) { + extensions = g_object_new (GSM_TYPE_SHELL_EXTENSIONS, NULL); + gsm_shell_extensions_disable_all (extensions); + } else { + extensions = NULL; + } + + if (gsm_system_is_login_session (priv->system)) { + allow_logout = FALSE; + } else { + allow_logout = !_log_out_is_locked_down (manager); + } + + sd_journal_send ("MESSAGE_ID=%s", GSM_MANAGER_UNRECOVERABLE_FAILURE_MSGID, + "PRIORITY=%d", 3, + "MESSAGE=Unrecoverable failure in required component %s", app_id, + NULL); + + gsm_fail_whale_dialog_we_failed (FALSE, + allow_logout, + extensions); +} + +static void +on_display_server_failure (GsmManager *manager, + GsmApp *app) +{ + const gchar *app_id; + GsmShellExtensions *extensions; + + app_id = gsm_app_peek_app_id (app); + + if (g_str_equal (app_id, "org.gnome.Shell.desktop")) { + extensions = g_object_new (GSM_TYPE_SHELL_EXTENSIONS, NULL); + gsm_shell_extensions_disable_all (extensions); + + g_object_unref (extensions); + } else { + extensions = NULL; + } + + sd_journal_send ("MESSAGE_ID=%s", GSM_MANAGER_UNRECOVERABLE_FAILURE_MSGID, + "PRIORITY=%d", 3, + "MESSAGE=Unrecoverable failure in required component %s", app_id, + NULL); + + gsm_quit (); +} + static gboolean _debug_client (const char *id, GsmClient *client, @@ -187,7 +314,9 @@ _debug_client (const char *id, static void debug_clients (GsmManager *manager) { - gsm_store_foreach (manager->clients, + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + gsm_store_foreach (priv->clients, (GsmStoreFunc)_debug_client, manager); } @@ -204,25 +333,129 @@ _find_by_cookie (const char *id, return (*cookie_ap == cookie_b); } +static gboolean +_client_has_startup_id (const char *id, + GsmClient *client, + const char *startup_id_a) +{ + const char *startup_id_b; + + startup_id_b = gsm_client_peek_startup_id (client); + if (IS_STRING_EMPTY (startup_id_b)) { + return FALSE; + } + + return (strcmp (startup_id_a, startup_id_b) == 0); +} + +static void +app_condition_changed (GsmApp *app, + gboolean condition, + GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + GsmClient *client; + + g_debug ("GsmManager: app:%s condition changed condition:%d", + gsm_app_peek_id (app), + condition); + + client = (GsmClient *)gsm_store_find (priv->clients, + (GsmStoreFunc)_client_has_startup_id, + (char *)gsm_app_peek_startup_id (app)); + + if (condition) { + if (!gsm_app_is_running (app) && client == NULL) { + start_app_or_warn (manager, app); + } else { + g_debug ("GsmManager: not starting - app still running '%s'", gsm_app_peek_id (app)); + } + } else { + GError *error; + gboolean res; + + if (client != NULL) { + /* Kill client in case condition if false and make sure it won't + * be automatically restarted by adding the client to + * condition_clients */ + priv->condition_clients = + g_slist_prepend (priv->condition_clients, client); + + g_debug ("GsmManager: stopping client %s for app", gsm_client_peek_id (client)); + + error = NULL; + res = gsm_client_stop (client, &error); + if (! res) { + g_warning ("Not able to stop app client from its condition: %s", + error->message); + g_error_free (error); + } + } else { + g_debug ("GsmManager: stopping app %s", gsm_app_peek_id (app)); + + /* If we don't have a client then we should try to kill the app */ + error = NULL; + res = gsm_app_stop (app, &error); + if (! res) { + g_warning ("Not able to stop app from its condition: %s", + error->message); + g_error_free (error); + } + } + } +} + static const char * phase_num_to_name (guint phase) { + const char *name; + switch (phase) { + case GSM_MANAGER_PHASE_STARTUP: + name = "STARTUP"; + break; + case GSM_MANAGER_PHASE_EARLY_INITIALIZATION: + name = "EARLY_INITIALIZATION"; + break; + case GSM_MANAGER_PHASE_PRE_DISPLAY_SERVER: + name = "PRE_DISPLAY_SERVER"; + break; + case GSM_MANAGER_PHASE_DISPLAY_SERVER: + name = "DISPLAY_SERVER"; + break; case GSM_MANAGER_PHASE_INITIALIZATION: - return "INITIALIZATION"; + name = "INITIALIZATION"; + break; + case GSM_MANAGER_PHASE_WINDOW_MANAGER: + name = "WINDOW_MANAGER"; + break; + case GSM_MANAGER_PHASE_PANEL: + name = "PANEL"; + break; + case GSM_MANAGER_PHASE_DESKTOP: + name = "DESKTOP"; + break; case GSM_MANAGER_PHASE_APPLICATION: - return "APPLICATION"; + name = "APPLICATION"; + break; case GSM_MANAGER_PHASE_RUNNING: - return "RUNNING"; + name = "RUNNING"; + break; case GSM_MANAGER_PHASE_QUERY_END_SESSION: - return "QUERY_END_SESSION"; + name = "QUERY_END_SESSION"; + break; case GSM_MANAGER_PHASE_END_SESSION: - return "END_SESSION"; + name = "END_SESSION"; + break; case GSM_MANAGER_PHASE_EXIT: - return "EXIT"; + name = "EXIT"; + break; default: g_assert_not_reached (); + break; } + + return name; } static void start_phase (GsmManager *manager); @@ -230,17 +463,24 @@ static void start_phase (GsmManager *manager); static void gsm_manager_quit (GsmManager *manager) { - switch (manager->logout_type) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + /* See the comment in request_reboot() for some more details about how + * this works. */ + + switch (priv->logout_type) { case GSM_MANAGER_LOGOUT_LOGOUT: case GSM_MANAGER_LOGOUT_NONE: gsm_quit (); break; case GSM_MANAGER_LOGOUT_REBOOT: - gsm_system_complete_shutdown (manager->system); + case GSM_MANAGER_LOGOUT_REBOOT_INTERACT: + gsm_system_complete_shutdown (priv->system); gsm_quit (); break; case GSM_MANAGER_LOGOUT_SHUTDOWN: - gsm_system_complete_shutdown (manager->system); + case GSM_MANAGER_LOGOUT_SHUTDOWN_INTERACT: + gsm_system_complete_shutdown (priv->system); gsm_quit (); break; default: @@ -254,18 +494,47 @@ static gboolean do_query_end_session_exit (GsmManager *manager); static void end_phase (GsmManager *manager) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); gboolean start_next_phase = TRUE; g_debug ("GsmManager: ending phase %s", - phase_num_to_name (manager->phase)); + phase_num_to_name (priv->phase)); - g_slist_free (manager->query_clients); - manager->query_clients = NULL; + g_slist_free (priv->pending_apps); + priv->pending_apps = NULL; - g_clear_handle_id (&manager->phase_timeout_id, g_source_remove); + g_slist_free (priv->query_clients); + priv->query_clients = NULL; - switch (manager->phase) { + g_slist_free (priv->next_query_clients); + priv->next_query_clients = NULL; + + if (priv->query_timeout_id > 0) { + g_source_remove (priv->query_timeout_id); + priv->query_timeout_id = 0; + } + if (priv->phase_timeout_id > 0) { + g_source_remove (priv->phase_timeout_id); + priv->phase_timeout_id = 0; + } + + switch (priv->phase) { + case GSM_MANAGER_PHASE_STARTUP: + case GSM_MANAGER_PHASE_EARLY_INITIALIZATION: + case GSM_MANAGER_PHASE_PRE_DISPLAY_SERVER: + case GSM_MANAGER_PHASE_DISPLAY_SERVER: + break; case GSM_MANAGER_PHASE_INITIALIZATION: + priv->manager_initialized = TRUE; + /* Wait for systemd if it isn't initialized yet*/ + if (priv->systemd_managed && !priv->systemd_initialized) { + sd_notify (0, "STATUS=GNOME Session Manager waiting for gnome-session-initialized.target (via signal)"); + start_next_phase = FALSE; + } + break; + case GSM_MANAGER_PHASE_WINDOW_MANAGER: + case GSM_MANAGER_PHASE_PANEL: + case GSM_MANAGER_PHASE_DESKTOP: case GSM_MANAGER_PHASE_APPLICATION: break; case GSM_MANAGER_PHASE_RUNNING: @@ -290,17 +559,199 @@ end_phase (GsmManager *manager) } if (start_next_phase) { - manager->phase++; + priv->phase++; start_phase (manager); } } +static void +app_event_during_startup (GsmManager *manager, + GsmApp *app) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + if (!(priv->phase < GSM_MANAGER_PHASE_APPLICATION)) + return; + + priv->pending_apps = g_slist_remove (priv->pending_apps, app); + + if (priv->pending_apps == NULL) { + if (priv->phase_timeout_id > 0) { + g_source_remove (priv->phase_timeout_id); + priv->phase_timeout_id = 0; + } + + end_phase (manager); + } +} + +static gboolean +is_app_display_server (GsmManager *manager, + GsmApp *app) +{ + GsmManagerPhase phase; + + /* Apps can only really act as a display server if + * we're a wayland session. + */ + if (g_strcmp0 (g_getenv ("XDG_SESSION_TYPE"), "wayland") != 0) + return FALSE; + + phase = gsm_app_peek_phase (app); + + return (phase == GSM_MANAGER_PHASE_DISPLAY_SERVER && + is_app_required (manager, app)); +} + +static void +_restart_app (GsmManager *manager, + GsmApp *app) +{ + GError *error = NULL; + + if (is_app_display_server (manager, app)) { + on_display_server_failure (manager, app); + return; + } + + if (!gsm_app_restart (app, &error)) { + if (is_app_required (manager, app)) { + on_required_app_failure (manager, app); + } else { + g_warning ("Error on restarting session managed app: %s", error->message); + } + g_clear_error (&error); + + app_event_during_startup (manager, app); + } +} + +static void +app_died (GsmApp *app, + int signal, + GsmManager *manager) +{ + g_warning ("Application '%s' killed by signal %d", gsm_app_peek_app_id (app), signal); + + if (gsm_app_get_registered (app) && gsm_app_peek_autorestart (app)) { + g_debug ("Component '%s' is autorestart, ignoring died signal", + gsm_app_peek_app_id (app)); + return; + } + + _restart_app (manager, app); + + /* For now, we don't do anything with crashes from + * non-required apps after they hit the restart limit. + * + * Note that both required and not-required apps will be + * caught by ABRT/apport type infrastructure, and it'd be + * better to pick up the crash from there and do something + * un-intrusive about it generically. + */ +} + +static void +app_exited (GsmApp *app, + guchar exit_code, + GsmManager *manager) +{ + if (exit_code != 0) + g_warning ("App '%s' exited with code %d", gsm_app_peek_app_id (app), exit_code); + else + g_debug ("App %s exited successfully", gsm_app_peek_app_id (app)); + + /* Consider that non-success exit status means "crash" for required components */ + if (exit_code != 0 && is_app_required (manager, app)) { + if (gsm_app_get_registered (app) && gsm_app_peek_autorestart (app)) { + g_debug ("Component '%s' is autorestart, ignoring non-successful exit", + gsm_app_peek_app_id (app)); + return; + } + + _restart_app (manager, app); + } else { + app_event_during_startup (manager, app); + } +} + +static void +app_registered (GsmApp *app, + GParamSpec *spec, + GsmManager *manager) +{ + if (!gsm_app_get_registered (app)) { + return; + } + + g_debug ("App %s registered", gsm_app_peek_app_id (app)); + + app_event_during_startup (manager, app); +} + +static gboolean +on_phase_timeout (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + GSList *a; + + priv->phase_timeout_id = 0; + + switch (priv->phase) { + case GSM_MANAGER_PHASE_STARTUP: + case GSM_MANAGER_PHASE_EARLY_INITIALIZATION: + case GSM_MANAGER_PHASE_PRE_DISPLAY_SERVER: + case GSM_MANAGER_PHASE_DISPLAY_SERVER: + case GSM_MANAGER_PHASE_INITIALIZATION: + case GSM_MANAGER_PHASE_WINDOW_MANAGER: + case GSM_MANAGER_PHASE_PANEL: + case GSM_MANAGER_PHASE_DESKTOP: + case GSM_MANAGER_PHASE_APPLICATION: + for (a = priv->pending_apps; a; a = a->next) { + GsmApp *app = a->data; + g_warning ("Application '%s' failed to register before timeout", + gsm_app_peek_app_id (app)); + if (is_app_required (manager, app)) + on_required_app_failure (manager, app); + } + break; + case GSM_MANAGER_PHASE_RUNNING: + break; + case GSM_MANAGER_PHASE_QUERY_END_SESSION: + case GSM_MANAGER_PHASE_END_SESSION: + break; + case GSM_MANAGER_PHASE_EXIT: + break; + default: + g_assert_not_reached (); + break; + } + + end_phase (manager); + + return FALSE; +} + static gboolean _start_app (const char *id, GsmApp *app, GsmManager *manager) { - if (gsm_app_peek_is_disabled (app)) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + if (gsm_app_peek_phase (app) != priv->phase) { + goto out; + } + + /* Keep track of app autostart condition in order to react + * accordingly in the future. */ + g_signal_connect (app, + "condition-changed", + G_CALLBACK (app_condition_changed), + manager); + + if (gsm_app_peek_is_disabled (app) + || gsm_app_peek_is_conditionally_disabled (app)) { g_debug ("GsmManager: Skipping disabled app: %s", id); goto out; } @@ -308,6 +759,30 @@ _start_app (const char *id, if (!start_app_or_warn (manager, app)) goto out; + if (priv->phase < GSM_MANAGER_PHASE_APPLICATION) { + /* Historical note - apparently, + * e.g. gnome-settings-daemon used to "daemonize", and + * so gnome-session assumes process exit means "ok + * we're done". Of course this is broken, we don't + * even distinguish between exit code 0 versus not-0, + * nor do we have any metadata which tells us a + * process is going to "daemonize" or not (and + * basically nothing should be anyways). + */ + g_signal_connect (app, + "exited", + G_CALLBACK (app_exited), + manager); + g_signal_connect (app, + "notify::registered", + G_CALLBACK (app_registered), + manager); + g_signal_connect (app, + "died", + G_CALLBACK (app_died), + manager); + priv->pending_apps = g_slist_prepend (priv->pending_apps, app); + } out: return FALSE; } @@ -315,11 +790,21 @@ _start_app (const char *id, static void do_phase_startup (GsmManager *manager) { - gsm_store_foreach (manager->apps, + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + gsm_store_foreach (priv->apps, (GsmStoreFunc)_start_app, manager); - end_phase (manager); + if (priv->pending_apps != NULL) { + if (priv->phase < GSM_MANAGER_PHASE_APPLICATION) { + priv->phase_timeout_id = g_timeout_add_seconds (GSM_MANAGER_PHASE_TIMEOUT, + (GSourceFunc)on_phase_timeout, + manager); + } + } else { + end_phase (manager); + } } typedef struct { @@ -329,10 +814,10 @@ typedef struct { static gboolean -_client_end_session (const char *id, - GsmClient *client, +_client_end_session (GsmClient *client, ClientEndSessionData *data) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (data->manager); gboolean ret; GError *error; @@ -348,18 +833,27 @@ _client_end_session (const char *id, /* FIXME: what should we do if we can't communicate with client? */ } else { g_debug ("GsmManager: adding client to end-session clients: %s", gsm_client_peek_id (client)); - data->manager->query_clients = g_slist_prepend (data->manager->query_clients, client); + priv->query_clients = g_slist_prepend (priv->query_clients, client); } return FALSE; } +static gboolean +_client_end_session_helper (const char *id, + GsmClient *client, + ClientEndSessionData *data) +{ + return _client_end_session (client, data); +} + static void complete_end_session_tasks (GsmManager *manager) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); GSList *l; - for (l = manager->pending_end_session_tasks; + for (l = priv->pending_end_session_tasks; l != NULL; l = l->next) { GTask *task = G_TASK (l->data); @@ -367,49 +861,68 @@ complete_end_session_tasks (GsmManager *manager) g_task_return_boolean (task, TRUE); } - g_slist_free_full (manager->pending_end_session_tasks, + g_slist_free_full (priv->pending_end_session_tasks, (GDestroyNotify) g_object_unref); - manager->pending_end_session_tasks = NULL; + priv->pending_end_session_tasks = NULL; } -static gboolean -on_end_session_timeout (GsmManager *manager) +static void +do_phase_end_session (GsmManager *manager) { - manager->phase_timeout_id = 0; + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + ClientEndSessionData data; - for (GSList *l = manager->query_clients; l != NULL; l = l->next) { - g_warning ("Client '%s' failed to reply before timeout", - gsm_client_peek_id (l->data)); + complete_end_session_tasks (manager); + + data.manager = manager; + data.flags = 0; + + if (priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE) { + data.flags |= GSM_CLIENT_END_SESSION_FLAG_FORCEFUL; } - end_phase (manager); - return FALSE; + if (priv->phase_timeout_id > 0) { + g_source_remove (priv->phase_timeout_id); + priv->phase_timeout_id = 0; + } + + if (gsm_store_size (priv->clients) > 0) { + priv->phase_timeout_id = g_timeout_add_seconds (GSM_MANAGER_PHASE_TIMEOUT, + (GSourceFunc)on_phase_timeout, + manager); + + gsm_store_foreach (priv->clients, + (GsmStoreFunc)_client_end_session_helper, + &data); + } else { + end_phase (manager); + } } static void -do_phase_end_session (GsmManager *manager) +do_phase_end_session_part_2 (GsmManager *manager) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); ClientEndSessionData data; - complete_end_session_tasks (manager); - data.manager = manager; - data.flags = GSM_CLIENT_END_SESSION_FLAG_NONE; + data.flags = 0; - if (manager->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE) { + if (priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE) { data.flags |= GSM_CLIENT_END_SESSION_FLAG_FORCEFUL; } + data.flags |= GSM_CLIENT_END_SESSION_FLAG_LAST; - g_clear_handle_id (&manager->phase_timeout_id, g_source_remove); + /* keep the timeout that was started at the beginning of the + * GSM_MANAGER_PHASE_END_SESSION phase */ - if (gsm_store_size (manager->clients) > 0) { - manager->phase_timeout_id = g_timeout_add_seconds (10, - (GSourceFunc)on_end_session_timeout, - manager); + if (g_slist_length (priv->next_query_clients) > 0) { + g_slist_foreach (priv->next_query_clients, + (GFunc)_client_end_session, + &data); - gsm_store_foreach (manager->clients, - (GsmStoreFunc)_client_end_session, - &data); + g_slist_free (priv->next_query_clients); + priv->next_query_clients = NULL; } else { end_phase (manager); } @@ -436,15 +949,53 @@ _client_stop (const char *id, return FALSE; } +static void +maybe_restart_user_bus (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + GsmSystem *system; + g_autoptr(GVariant) reply = NULL; + g_autoptr(GError) error = NULL; + + if (priv->dbus_disconnected) + return; + + system = gsm_get_system (); + + if (!gsm_system_is_last_session_for_user (system)) + return; + + reply = g_dbus_connection_call_sync (priv->connection, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StopUnit", + g_variant_new ("(ss)", "dbus.service", "fail"), + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + + if (error != NULL) { + g_debug ("GsmManager: reloading user bus failed: %s", error->message); + } +} + static void do_phase_exit (GsmManager *manager) { - if (gsm_store_size (manager->clients) > 0) { - gsm_store_foreach (manager->clients, + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + if (gsm_store_size (priv->clients) > 0) { + gsm_store_foreach (priv->clients, (GsmStoreFunc)_client_stop, NULL); } + if (!priv->systemd_managed) + maybe_restart_user_bus (manager); + end_phase (manager); } @@ -453,6 +1004,7 @@ _client_query_end_session (const char *id, GsmClient *client, ClientEndSessionData *data) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (data->manager); gboolean ret; GError *error; @@ -468,7 +1020,7 @@ _client_query_end_session (const char *id, /* FIXME: what should we do if we can't communicate with client? */ } else { g_debug ("GsmManager: adding client to query clients: %s", gsm_client_peek_id (client)); - data->manager->query_clients = g_slist_prepend (data->manager->query_clients, client); + priv->query_clients = g_slist_prepend (priv->query_clients, client); } return FALSE; @@ -492,17 +1044,18 @@ inhibitor_has_flag (gpointer key, static gboolean gsm_manager_is_logout_inhibited (GsmManager *manager) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); GsmInhibitor *inhibitor; - if (manager->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE) { + if (priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE) { return FALSE; } - if (manager->inhibitors == NULL) { + if (priv->inhibitors == NULL) { return FALSE; } - inhibitor = (GsmInhibitor *)gsm_store_find (manager->inhibitors, + inhibitor = (GsmInhibitor *)gsm_store_find (priv->inhibitors, (GsmStoreFunc)inhibitor_has_flag, GUINT_TO_POINTER (GSM_INHIBITOR_FLAG_LOGOUT)); if (inhibitor == NULL) { @@ -514,13 +1067,14 @@ gsm_manager_is_logout_inhibited (GsmManager *manager) static gboolean gsm_manager_is_idle_inhibited (GsmManager *manager) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); GsmInhibitor *inhibitor; - if (manager->inhibitors == NULL) { + if (priv->inhibitors == NULL) { return FALSE; } - inhibitor = (GsmInhibitor *)gsm_store_find (manager->inhibitors, + inhibitor = (GsmInhibitor *)gsm_store_find (priv->inhibitors, (GsmStoreFunc)inhibitor_has_flag, GUINT_TO_POINTER (GSM_INHIBITOR_FLAG_IDLE)); if (inhibitor == NULL) { @@ -565,27 +1119,29 @@ inhibitor_is_jit (gpointer key, static void cancel_end_session (GsmManager *manager) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + /* just ignore if received outside of shutdown */ - if (manager->phase < GSM_MANAGER_PHASE_QUERY_END_SESSION) { + if (priv->phase < GSM_MANAGER_PHASE_QUERY_END_SESSION) { return; } /* switch back to running phase */ g_debug ("GsmManager: Cancelling the end of session"); - g_cancellable_cancel (manager->end_session_cancellable); + g_cancellable_cancel (priv->end_session_cancellable); gsm_manager_set_phase (manager, GSM_MANAGER_PHASE_RUNNING); - manager->logout_mode = GSM_MANAGER_LOGOUT_MODE_NORMAL; + priv->logout_mode = GSM_MANAGER_LOGOUT_MODE_NORMAL; - manager->logout_type = GSM_MANAGER_LOGOUT_NONE; + priv->logout_type = GSM_MANAGER_LOGOUT_NONE; /* clear all JIT inhibitors */ - gsm_store_foreach_remove (manager->inhibitors, + gsm_store_foreach_remove (priv->inhibitors, (GsmStoreFunc)inhibitor_is_jit, (gpointer)manager); - gsm_store_foreach (manager->clients, + gsm_store_foreach (priv->clients, (GsmStoreFunc)_client_cancel_end_session, NULL); @@ -595,32 +1151,35 @@ cancel_end_session (GsmManager *manager) static void end_session_or_show_shell_dialog (GsmManager *manager) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); gboolean logout_prompt; GsmShellEndSessionDialogType type; gboolean logout_inhibited; - switch (manager->logout_type) { + switch (priv->logout_type) { case GSM_MANAGER_LOGOUT_LOGOUT: type = GSM_SHELL_END_SESSION_DIALOG_TYPE_LOGOUT; break; case GSM_MANAGER_LOGOUT_REBOOT: + case GSM_MANAGER_LOGOUT_REBOOT_INTERACT: type = GSM_SHELL_END_SESSION_DIALOG_TYPE_RESTART; break; case GSM_MANAGER_LOGOUT_SHUTDOWN: + case GSM_MANAGER_LOGOUT_SHUTDOWN_INTERACT: type = GSM_SHELL_END_SESSION_DIALOG_TYPE_SHUTDOWN; break; default: g_warning ("Unexpected logout type %d when creating end session dialog", - manager->logout_type); + priv->logout_type); type = GSM_SHELL_END_SESSION_DIALOG_TYPE_LOGOUT; break; } logout_inhibited = gsm_manager_is_logout_inhibited (manager); - logout_prompt = g_settings_get_boolean (manager->settings, + logout_prompt = g_settings_get_boolean (priv->settings, KEY_LOGOUT_PROMPT); - switch (manager->logout_mode) { + switch (priv->logout_mode) { case GSM_MANAGER_LOGOUT_MODE_NORMAL: if (logout_inhibited || logout_prompt) { show_shell_end_session_dialog (manager, type); @@ -650,11 +1209,16 @@ end_session_or_show_shell_dialog (GsmManager *manager) static void query_end_session_complete (GsmManager *manager) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + g_debug ("GsmManager: query end session complete"); /* Remove the timeout since this can be called from outside the timer * and we don't want to have it called twice */ - g_clear_handle_id (&manager->phase_timeout_id, g_source_remove); + if (priv->query_timeout_id > 0) { + g_source_remove (priv->query_timeout_id); + priv->query_timeout_id = 0; + } end_session_or_show_shell_dialog (manager); } @@ -672,11 +1236,12 @@ generate_cookie (void) static guint32 _generate_unique_cookie (GsmManager *manager) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); guint32 cookie; do { cookie = generate_cookie (); - } while (gsm_store_find (manager->inhibitors, (GsmStoreFunc)_find_by_cookie, &cookie) != NULL); + } while (gsm_store_find (priv->inhibitors, (GsmStoreFunc)_find_by_cookie, &cookie) != NULL); return cookie; } @@ -684,37 +1249,56 @@ _generate_unique_cookie (GsmManager *manager) static gboolean _on_query_end_session_timeout (GsmManager *manager) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); GSList *l; - manager->phase_timeout_id = 0; + priv->query_timeout_id = 0; g_debug ("GsmManager: query end session timed out"); - for (l = manager->query_clients; l != NULL; l = l->next) { + for (l = priv->query_clients; l != NULL; l = l->next) { + guint cookie; GsmInhibitor *inhibitor; + const char *bus_name; + char *app_id; g_warning ("Client '%s' failed to reply before timeout", gsm_client_peek_id (l->data)); /* Don't add "not responding" inhibitors if logout is forced */ - if (manager->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE) { + if (priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE) { continue; } /* Add JIT inhibit for unresponsive client */ + if (GSM_IS_DBUS_CLIENT (l->data)) { + bus_name = gsm_dbus_client_get_bus_name (l->data); + } else { + bus_name = NULL; + } + + app_id = g_strdup (gsm_client_peek_app_id (l->data)); + if (IS_STRING_EMPTY (app_id)) { + /* XSMP clients don't give us an app id unless we start them */ + g_free (app_id); + app_id = gsm_client_get_app_name (l->data); + } + + cookie = _generate_unique_cookie (manager); inhibitor = gsm_inhibitor_new_for_client (gsm_client_peek_id (l->data), - gsm_client_peek_app_id (l->data), + app_id, GSM_INHIBITOR_FLAG_LOGOUT, _("Not responding"), - gsm_client_peek_bus_name (l->data), - _generate_unique_cookie (manager)); - gsm_store_add (manager->inhibitors, gsm_inhibitor_peek_id (inhibitor), G_OBJECT (inhibitor)); + bus_name, + cookie); + g_free (app_id); + gsm_store_add (priv->inhibitors, gsm_inhibitor_peek_id (inhibitor), G_OBJECT (inhibitor)); g_object_unref (inhibitor); } - g_slist_free (manager->query_clients); - manager->query_clients = NULL; + g_slist_free (priv->query_clients); + priv->query_clients = NULL; query_end_session_complete (manager); @@ -724,81 +1308,112 @@ _on_query_end_session_timeout (GsmManager *manager) static void do_phase_query_end_session (GsmManager *manager) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); ClientEndSessionData data; data.manager = manager; - data.flags = GSM_CLIENT_END_SESSION_FLAG_NONE; + data.flags = 0; - if (manager->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE) { + if (priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE) { data.flags |= GSM_CLIENT_END_SESSION_FLAG_FORCEFUL; } + /* We only query if an app is ready to log out, so we don't use + * GSM_CLIENT_END_SESSION_FLAG_SAVE here. + */ debug_clients (manager); g_debug ("GsmManager: sending query-end-session to clients (logout mode: %s)", - manager->logout_mode == GSM_MANAGER_LOGOUT_MODE_NORMAL? "normal" : - manager->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE? "forceful": + priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_NORMAL? "normal" : + priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE? "forceful": "no confirmation"); - gsm_store_foreach (manager->clients, + gsm_store_foreach (priv->clients, (GsmStoreFunc)_client_query_end_session, &data); - manager->phase_timeout_id = g_timeout_add_seconds (1, (GSourceFunc)_on_query_end_session_timeout, manager); + /* This phase doesn't time out unless logout is forced. Typically, this + * separate timer is only used to show UI. */ + priv->query_timeout_id = g_timeout_add_seconds (1, (GSourceFunc)_on_query_end_session_timeout, manager); } static void update_idle (GsmManager *manager) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + if (gsm_manager_is_idle_inhibited (manager)) { - gsm_presence_set_idle_enabled (manager->presence, FALSE); + gsm_presence_set_idle_enabled (priv->presence, FALSE); } else { - gsm_presence_set_idle_enabled (manager->presence, TRUE); + gsm_presence_set_idle_enabled (priv->presence, TRUE); } } static void start_phase (GsmManager *manager) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + g_debug ("GsmManager: starting phase %s\n", - phase_num_to_name (manager->phase)); + phase_num_to_name (priv->phase)); /* reset state */ - g_slist_free (manager->query_clients); - manager->query_clients = NULL; + g_slist_free (priv->pending_apps); + priv->pending_apps = NULL; + g_slist_free (priv->query_clients); + priv->query_clients = NULL; + g_slist_free (priv->next_query_clients); + priv->next_query_clients = NULL; + + if (priv->query_timeout_id > 0) { + g_source_remove (priv->query_timeout_id); + priv->query_timeout_id = 0; + } + if (priv->phase_timeout_id > 0) { + g_source_remove (priv->phase_timeout_id); + priv->phase_timeout_id = 0; + } - g_clear_handle_id (&manager->phase_timeout_id, g_source_remove); + sd_notifyf (0, "STATUS=GNOME Session Manager phase is %s", phase_num_to_name (priv->phase)); - switch (manager->phase) { - case GSM_MANAGER_PHASE_INITIALIZATION: - sd_notify (0, "READY=1\nSTATUS=Waiting for session to start"); + switch (priv->phase) { + case GSM_MANAGER_PHASE_STARTUP: + case GSM_MANAGER_PHASE_EARLY_INITIALIZATION: + case GSM_MANAGER_PHASE_PRE_DISPLAY_SERVER: + do_phase_startup (manager); break; + case GSM_MANAGER_PHASE_DISPLAY_SERVER: + sd_notify (0, "READY=1"); + do_phase_startup (manager); + break; + case GSM_MANAGER_PHASE_INITIALIZATION: + case GSM_MANAGER_PHASE_WINDOW_MANAGER: + case GSM_MANAGER_PHASE_PANEL: + case GSM_MANAGER_PHASE_DESKTOP: case GSM_MANAGER_PHASE_APPLICATION: - sd_notify (0, "STATUS=Starting applications"); - gsm_exported_manager_emit_session_running (manager->skeleton); do_phase_startup (manager); break; case GSM_MANAGER_PHASE_RUNNING: - sd_notify (0, "STATUS=Running"); sd_journal_send ("MESSAGE_ID=%s", GSM_MANAGER_STARTUP_SUCCEEDED_MSGID, "PRIORITY=%d", 5, "MESSAGE=Entering running state", NULL); - if (manager->pending_end_session_tasks != NULL) + if (priv->pending_end_session_tasks != NULL) complete_end_session_tasks (manager); - g_object_unref (manager->end_session_cancellable); - manager->end_session_cancellable = g_cancellable_new (); + g_object_unref (priv->end_session_cancellable); + priv->end_session_cancellable = g_cancellable_new (); + gsm_exported_manager_emit_session_running (priv->skeleton); update_idle (manager); break; case GSM_MANAGER_PHASE_QUERY_END_SESSION: - sd_notify (0, "STATUS=Querying end of session"); do_phase_query_end_session (manager); break; case GSM_MANAGER_PHASE_END_SESSION: - sd_notify (0, "STOPPING=1\nSTATUS=Logging out"); - gsm_exported_manager_emit_session_over (manager->skeleton); + sd_notify (0, "STOPPING=1"); + do_phase_end_session (manager); break; case GSM_MANAGER_PHASE_EXIT: - sd_notify (0, "STOPPING=1\nSTATUS=Quitting"); + sd_notify (0, "STOPPING=1"); + do_phase_exit (manager); break; default: @@ -812,9 +1427,19 @@ _debug_app_for_phase (const char *id, GsmApp *app, gpointer data) { - g_debug ("GsmManager:\tapp-id:%s\tis-disabled:%d", + guint phase; + + phase = GPOINTER_TO_UINT (data); + + if (gsm_app_peek_phase (app) != phase) { + return FALSE; + } + + g_debug ("GsmManager:\tID: %s\tapp-id:%s\tis-disabled:%d\tis-conditionally-disabled:%d", + gsm_app_peek_id (app), gsm_app_peek_app_id (app), - gsm_app_peek_is_disabled (app)); + gsm_app_peek_is_disabled (app), + gsm_app_peek_is_conditionally_disabled (app)); return FALSE; } @@ -822,32 +1447,80 @@ _debug_app_for_phase (const char *id, static void debug_app_summary (GsmManager *manager) { - g_debug ("GsmManager: Autostart app summary"); - gsm_store_foreach (manager->apps, - (GsmStoreFunc)_debug_app_for_phase, - NULL); + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + guint phase; + + g_debug ("GsmManager: App startup summary"); + for (phase = GSM_MANAGER_PHASE_EARLY_INITIALIZATION; phase < GSM_MANAGER_PHASE_RUNNING; phase++) { + g_debug ("GsmManager: Phase %s", phase_num_to_name (phase)); + gsm_store_foreach (priv->apps, + (GsmStoreFunc)_debug_app_for_phase, + GUINT_TO_POINTER (phase)); + } } void gsm_manager_start (GsmManager *manager) { - g_debug ("GsmManager: Starting"); + g_debug ("GsmManager: GSM starting to manage"); g_return_if_fail (GSM_IS_MANAGER (manager)); - g_return_if_fail (manager->phase == GSM_MANAGER_PHASE_INITIALIZATION); + gsm_manager_set_phase (manager, GSM_MANAGER_PHASE_EARLY_INITIALIZATION); debug_app_summary (manager); start_phase (manager); } +char * +_gsm_manager_get_default_session (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + g_autoptr(GSettings) session_settings = NULL; + + if (manager) + session_settings = g_object_ref (priv->session_settings); + else + session_settings = g_settings_new (SESSION_SCHEMA); + return g_settings_get_string (session_settings, + KEY_SESSION_NAME); +} + void _gsm_manager_set_active_session (GsmManager *manager, - const char *session_name) + const char *session_name, + gboolean is_fallback) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + g_free (priv->session_name); + priv->session_name = g_strdup (session_name); + priv->is_fallback_session = is_fallback; + + gsm_exported_manager_set_session_name (priv->skeleton, session_name); +} + +static gboolean +_app_has_app_id (const char *id, + GsmApp *app, + const char *app_id_a) +{ + const char *app_id_b; + + app_id_b = gsm_app_peek_app_id (app); + return (app_id_b != NULL && strcmp (app_id_a, app_id_b) == 0); +} + +static GsmApp * +find_app_for_app_id (GsmManager *manager, + const char *app_id) { - g_free (manager->session_name); - manager->session_name = g_strdup (session_name); + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + GsmApp *app; - gsm_exported_manager_set_session_name (manager->skeleton, session_name); + app = (GsmApp *)gsm_store_find (priv->apps, + (GsmStoreFunc)_app_has_app_id, + (char *)app_id); + return app; } static gboolean @@ -873,23 +1546,109 @@ inhibitor_has_client_id (gpointer key, return matches; } +static gboolean +_app_has_startup_id (const char *id, + GsmApp *app, + const char *startup_id_a) +{ + const char *startup_id_b; + + startup_id_b = gsm_app_peek_startup_id (app); + + if (IS_STRING_EMPTY (startup_id_b)) { + return FALSE; + } + + return (strcmp (startup_id_a, startup_id_b) == 0); +} + +static GsmApp * +find_app_for_startup_id (GsmManager *manager, + const char *startup_id) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + GsmApp *found_app; + GSList *a; + + found_app = NULL; + + /* If we're starting up the session, try to match the new client + * with one pending apps for the current phase. If not, try to match + * with any of the autostarted apps. */ + if (priv->phase < GSM_MANAGER_PHASE_APPLICATION) { + for (a = priv->pending_apps; a != NULL; a = a->next) { + GsmApp *app = GSM_APP (a->data); + + if (strcmp (startup_id, gsm_app_peek_startup_id (app)) == 0) { + found_app = app; + goto out; + } + } + } else { + GsmApp *app; + + app = (GsmApp *)gsm_store_find (priv->apps, + (GsmStoreFunc)_app_has_startup_id, + (char *)startup_id); + if (app != NULL) { + found_app = app; + goto out; + } + } + out: + return found_app; +} + static void _disconnect_client (GsmManager *manager, GsmClient *client) { - g_debug ("GsmManager: disconnect client: %s (app: %s)", - gsm_client_peek_id (client), - gsm_client_peek_app_id (client)); + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + gboolean is_condition_client; + GsmApp *app; + const char *app_id; + const char *startup_id; + gboolean app_restart; + GsmClientRestartStyle client_restart_hint; + + g_debug ("GsmManager: disconnect client: %s", gsm_client_peek_id (client)); /* take a ref so it doesn't get finalized */ g_object_ref (client); + gsm_client_set_status (client, GSM_CLIENT_FINISHED); + + is_condition_client = FALSE; + if (g_slist_find (priv->condition_clients, client)) { + priv->condition_clients = g_slist_remove (priv->condition_clients, client); + + is_condition_client = TRUE; + } + /* remove any inhibitors for this client */ - gsm_store_foreach_remove (manager->inhibitors, + gsm_store_foreach_remove (priv->inhibitors, (GsmStoreFunc)inhibitor_has_client_id, (gpointer)gsm_client_peek_id (client)); - switch (manager->phase) { + app = NULL; + + /* first try to match on startup ID */ + startup_id = gsm_client_peek_startup_id (client); + if (! IS_STRING_EMPTY (startup_id)) { + app = find_app_for_startup_id (manager, startup_id); + + } + + /* then try to find matching app-id */ + if (app == NULL) { + app_id = gsm_client_peek_app_id (client); + if (! IS_STRING_EMPTY (app_id)) { + g_debug ("GsmManager: disconnect for app '%s'", app_id); + app = find_app_for_app_id (manager, app_id); + } + } + + switch (priv->phase) { case GSM_MANAGER_PHASE_QUERY_END_SESSION: /* Instead of answering our end session query, the client just exited. * Treat that as an "okay, end the session" answer. @@ -897,15 +1656,18 @@ _disconnect_client (GsmManager *manager, * This call implicitly removes any inhibitors for the client, along * with removing the client from the pending query list. */ - on_client_end_session_response (client, - TRUE, - "Client exited in query end " - "session phase instead of end " - "session phase", - manager); + _handle_client_end_session_response (manager, + client, + TRUE, + FALSE, + FALSE, + "Client exited in " + "query end session phase " + "instead of end session " + "phase"); break; case GSM_MANAGER_PHASE_END_SESSION: - if (! g_slist_find (manager->query_clients, client)) { + if (! g_slist_find (priv->query_clients, client)) { /* the client sent its EndSessionResponse and we already * processed it. */ @@ -918,17 +1680,54 @@ _disconnect_client (GsmManager *manager, * in library code after the callback. Or maybe the application * crashed while handling EndSession. Or it was lazy. */ - on_client_end_session_response (client, - TRUE, - "Client exited in end session " - "phase without sending " - "EndSessionResponse", - manager); + _handle_client_end_session_response (manager, + client, + TRUE, + FALSE, + FALSE, + "Client exited in " + "end session phase without " + "sending EndSessionResponse"); default: /* do nothing */ break; } + if (priv->dbus_disconnected && GSM_IS_DBUS_CLIENT (client)) { + g_debug ("GsmManager: dbus disconnected, not restarting application"); + goto out; + } + + if (app == NULL) { + g_debug ("GsmManager: unable to find application for client - not restarting"); + goto out; + } + + if (priv->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION) { + g_debug ("GsmManager: in shutdown, not restarting application"); + goto out; + } + + app_restart = gsm_app_peek_autorestart (app); + client_restart_hint = gsm_client_peek_restart_style_hint (client); + + /* allow legacy clients to override the app info */ + if (! app_restart + && client_restart_hint != GSM_CLIENT_RESTART_IMMEDIATELY) { + g_debug ("GsmManager: autorestart not set, not restarting application"); + goto out; + } + + if (is_condition_client) { + g_debug ("GsmManager: app conditionally disabled, not restarting application"); + goto out; + } + + g_debug ("GsmManager: restarting app"); + + _restart_app (manager, app); + + out: g_object_unref (client); } @@ -944,13 +1743,17 @@ _disconnect_dbus_client (const char *id, { const char *name; + if (! GSM_IS_DBUS_CLIENT (client)) { + return FALSE; + } + /* If no service name, then we simply disconnect all clients */ if (!data->service_name) { _disconnect_client (data->manager, client); return TRUE; } - name = gsm_client_peek_bus_name (client); + name = gsm_dbus_client_get_bus_name (GSM_DBUS_CLIENT (client)); if (IS_STRING_EMPTY (name)) { return FALSE; } @@ -976,96 +1779,195 @@ static void remove_clients_for_connection (GsmManager *manager, const char *service_name) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); RemoveClientData data; data.service_name = service_name; data.manager = manager; /* disconnect dbus clients for name */ - gsm_store_foreach_remove (manager->clients, + gsm_store_foreach_remove (priv->clients, (GsmStoreFunc)_disconnect_dbus_client, &data); - if (manager->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION - && gsm_store_size (manager->clients) == 0) { + if (priv->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION + && gsm_store_size (priv->clients) == 0) { g_debug ("GsmManager: last client disconnected - exiting"); end_phase (manager); } } +static void +gsm_manager_set_failsafe (GsmManager *manager, + gboolean enabled) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + g_return_if_fail (GSM_IS_MANAGER (manager)); + + priv->failsafe = enabled; +} + gboolean gsm_manager_get_dbus_disconnected (GsmManager *manager) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + g_return_val_if_fail (GSM_IS_MANAGER (manager), FALSE); - return manager->dbus_disconnected; + return priv->dbus_disconnected; +} + +gboolean +gsm_manager_get_failsafe (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + g_return_val_if_fail (GSM_IS_MANAGER (manager), FALSE); + + return priv->failsafe; +} + +gboolean +gsm_manager_get_systemd_managed (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + g_return_val_if_fail (GSM_IS_MANAGER (manager), FALSE); + + return priv->systemd_managed; } static void on_client_disconnected (GsmClient *client, GsmManager *manager) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + g_debug ("GsmManager: disconnect client"); _disconnect_client (manager, client); - gsm_store_remove (manager->clients, gsm_client_peek_id (client)); - if (manager->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION - && gsm_store_size (manager->clients) == 0) { + gsm_store_remove (priv->clients, gsm_client_peek_id (client)); + if (priv->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION + && gsm_store_size (priv->clients) == 0) { g_debug ("GsmManager: last client disconnected - exiting"); end_phase (manager); } } static void -on_client_end_session_response (GsmClient *client, - gboolean is_ok, - const char *reason, - GsmManager *manager) +_handle_client_end_session_response (GsmManager *manager, + GsmClient *client, + gboolean is_ok, + gboolean do_last, + gboolean cancel, + const char *reason) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + /* just ignore if received outside of shutdown */ - if (manager->phase < GSM_MANAGER_PHASE_QUERY_END_SESSION) { + if (priv->phase < GSM_MANAGER_PHASE_QUERY_END_SESSION) { return; } - g_debug ("GsmManager: Response from end session request: is-ok=%d reason=%s", is_ok, reason ?: "(none)"); + g_debug ("GsmManager: Response from end session request: is-ok=%d do-last=%d cancel=%d reason=%s", is_ok, do_last, cancel, reason ? reason :""); + + if (cancel) { + cancel_end_session (manager); + return; + } - manager->query_clients = g_slist_remove (manager->query_clients, client); + priv->query_clients = g_slist_remove (priv->query_clients, client); - if (!is_ok && manager->logout_mode != GSM_MANAGER_LOGOUT_MODE_FORCE) { + if (! is_ok && priv->logout_mode != GSM_MANAGER_LOGOUT_MODE_FORCE) { + guint cookie; GsmInhibitor *inhibitor; + char *app_id; + const char *bus_name; + + /* FIXME: do we support updating the reason? */ /* Create JIT inhibit */ + if (GSM_IS_DBUS_CLIENT (client)) { + bus_name = gsm_dbus_client_get_bus_name (GSM_DBUS_CLIENT (client)); + } else { + bus_name = NULL; + } + + app_id = g_strdup (gsm_client_peek_app_id (client)); + if (IS_STRING_EMPTY (app_id)) { + /* XSMP clients don't give us an app id unless we start them */ + g_free (app_id); + app_id = gsm_client_get_app_name (client); + } + + cookie = _generate_unique_cookie (manager); inhibitor = gsm_inhibitor_new_for_client (gsm_client_peek_id (client), - gsm_client_peek_app_id (client), + app_id, GSM_INHIBITOR_FLAG_LOGOUT, reason != NULL ? reason : _("Not responding"), - gsm_client_peek_bus_name (client), - _generate_unique_cookie (manager)); - gsm_store_add (manager->inhibitors, gsm_inhibitor_peek_id (inhibitor), G_OBJECT (inhibitor)); + bus_name, + cookie); + g_free (app_id); + gsm_store_add (priv->inhibitors, gsm_inhibitor_peek_id (inhibitor), G_OBJECT (inhibitor)); g_object_unref (inhibitor); } else { - gsm_store_foreach_remove (manager->inhibitors, + gsm_store_foreach_remove (priv->inhibitors, (GsmStoreFunc)inhibitor_has_client_id, (gpointer)gsm_client_peek_id (client)); } - if (manager->phase == GSM_MANAGER_PHASE_QUERY_END_SESSION) { - if (manager->query_clients == NULL) + if (priv->phase == GSM_MANAGER_PHASE_QUERY_END_SESSION) { + if (priv->query_clients == NULL) { query_end_session_complete (manager); - } else if (manager->phase == GSM_MANAGER_PHASE_END_SESSION) { + } + } else if (priv->phase == GSM_MANAGER_PHASE_END_SESSION) { + if (do_last) { + /* This only makes sense if we're in part 1 of + * GSM_MANAGER_PHASE_END_SESSION. Doing this in part 2 + * can only happen because of a buggy client that loops + * wanting to be last again and again. The phase + * timeout will take care of this issue. */ + priv->next_query_clients = g_slist_prepend (priv->next_query_clients, + client); + } + /* we can continue to the next step if all clients have replied * and if there's no inhibitor */ - if (manager->query_clients != NULL || gsm_manager_is_logout_inhibited (manager)) + if (priv->query_clients != NULL + || gsm_manager_is_logout_inhibited (manager)) { return; + } - end_phase (manager); + if (priv->next_query_clients != NULL) { + do_phase_end_session_part_2 (manager); + } else { + end_phase (manager); + } } } +static void +on_client_end_session_response (GsmClient *client, + gboolean is_ok, + gboolean do_last, + gboolean cancel, + const char *reason, + GsmManager *manager) +{ + _handle_client_end_session_response (manager, + client, + is_ok, + do_last, + cancel, + reason); +} + static void on_store_client_added (GsmStore *store, const char *id, GsmManager *manager) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); GsmClient *client; g_debug ("GsmManager: Client added: %s", id); @@ -1077,7 +1979,7 @@ on_store_client_added (GsmStore *store, G_CALLBACK (on_client_end_session_response), manager); - gsm_exported_manager_emit_client_added (manager->skeleton, id); + gsm_exported_manager_emit_client_added (priv->skeleton, id); /* FIXME: disconnect signal handler */ } @@ -1086,9 +1988,51 @@ on_store_client_removed (GsmStore *store, const char *id, GsmManager *manager) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + g_debug ("GsmManager: Client removed: %s", id); - gsm_exported_manager_emit_client_removed (manager->skeleton, id); + gsm_exported_manager_emit_client_removed (priv->skeleton, id); +} + +static void +gsm_manager_set_client_store (GsmManager *manager, + GsmStore *store) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + g_return_if_fail (GSM_IS_MANAGER (manager)); + + if (store != NULL) { + g_object_ref (store); + } + + if (priv->clients != NULL) { + g_signal_handlers_disconnect_by_func (priv->clients, + on_store_client_added, + manager); + g_signal_handlers_disconnect_by_func (priv->clients, + on_store_client_removed, + manager); + + g_object_unref (priv->clients); + } + + + g_debug ("GsmManager: setting client store %p", store); + + priv->clients = store; + + if (priv->clients != NULL) { + g_signal_connect (priv->clients, + "added", + G_CALLBACK (on_store_client_added), + manager); + g_signal_connect (priv->clients, + "removed", + G_CALLBACK (on_store_client_removed), + manager); + } } static void @@ -1097,7 +2041,26 @@ gsm_manager_set_property (GObject *object, const GValue *value, GParamSpec *pspec) { - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + GsmManager *self = GSM_MANAGER (object); + GsmManagerPrivate *priv = gsm_manager_get_instance_private (self); + + switch (prop_id) { + case PROP_FAILSAFE: + gsm_manager_set_failsafe (self, g_value_get_boolean (value)); + break; + case PROP_FALLBACK: + priv->is_fallback_session = g_value_get_boolean (value); + break; + case PROP_CLIENT_STORE: + gsm_manager_set_client_store (self, g_value_get_object (value)); + break; + case PROP_SYSTEMD_MANAGED: + priv->systemd_managed = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } } static void @@ -1107,10 +2070,20 @@ gsm_manager_get_property (GObject *object, GParamSpec *pspec) { GsmManager *self = GSM_MANAGER (object); + GsmManagerPrivate *priv = gsm_manager_get_instance_private (self); switch (prop_id) { + case PROP_FAILSAFE: + g_value_set_boolean (value, priv->failsafe); + break; case PROP_SESSION_NAME: - g_value_set_string (value, self->session_name); + g_value_set_string (value, priv->session_name); + break; + case PROP_FALLBACK: + g_value_set_boolean (value, priv->is_fallback_session); + break; + case PROP_CLIENT_STORE: + g_value_set_object (value, priv->clients); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -1118,6 +2091,14 @@ gsm_manager_get_property (GObject *object, } } +static gboolean +_find_app_provides (const char *id, + GsmApp *app, + const char *service) +{ + return gsm_app_provides (app, service); +} + static GObject * gsm_manager_constructor (GType type, guint n_construct_properties, @@ -1135,21 +2116,25 @@ static void update_inhibited_actions (GsmManager *manager, GsmInhibitorFlag new_inhibited_actions) { - if (manager->inhibited_actions == new_inhibited_actions) + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + if (priv->inhibited_actions == new_inhibited_actions) return; - gsm_system_set_inhibitors (manager->system, new_inhibited_actions); + gsm_system_set_inhibitors (priv->system, new_inhibited_actions); - manager->inhibited_actions = new_inhibited_actions; - gsm_exported_manager_set_inhibited_actions (manager->skeleton, - manager->inhibited_actions); + priv->inhibited_actions = new_inhibited_actions; + gsm_exported_manager_set_inhibited_actions (priv->skeleton, + priv->inhibited_actions); } static void on_inhibitor_vanished (GsmInhibitor *inhibitor, GsmManager *manager) { - gsm_store_remove (manager->inhibitors, gsm_inhibitor_peek_id (inhibitor)); + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + gsm_store_remove (priv->inhibitors, gsm_inhibitor_peek_id (inhibitor)); } static void @@ -1157,6 +2142,7 @@ on_store_inhibitor_added (GsmStore *store, const char *id, GsmManager *manager) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); GsmInhibitor *i; GsmInhibitorFlag new_inhibited_actions; @@ -1164,12 +2150,12 @@ on_store_inhibitor_added (GsmStore *store, i = GSM_INHIBITOR (gsm_store_lookup (store, id)); - new_inhibited_actions = manager->inhibited_actions | gsm_inhibitor_peek_flags (i); + new_inhibited_actions = priv->inhibited_actions | gsm_inhibitor_peek_flags (i); update_inhibited_actions (manager, new_inhibited_actions); g_signal_connect_object (i, "vanished", G_CALLBACK (on_inhibitor_vanished), manager, 0); - gsm_exported_manager_emit_inhibitor_added (manager->skeleton, id); + gsm_exported_manager_emit_inhibitor_added (priv->skeleton, id); update_idle (manager); } @@ -1191,21 +2177,22 @@ on_store_inhibitor_removed (GsmStore *store, const char *id, GsmManager *manager) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); GsmInhibitorFlag new_inhibited_actions; g_debug ("GsmManager: Inhibitor removed: %s", id); new_inhibited_actions = 0; - gsm_store_foreach (manager->inhibitors, + gsm_store_foreach (priv->inhibitors, collect_inhibition_flags, &new_inhibited_actions); update_inhibited_actions (manager, new_inhibited_actions); - gsm_exported_manager_emit_inhibitor_removed (manager->skeleton, id); + gsm_exported_manager_emit_inhibitor_removed (priv->skeleton, id); update_idle (manager); - if (manager->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION) { + if (priv->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION) { end_session_or_show_shell_dialog (manager); } } @@ -1214,51 +2201,57 @@ static void gsm_manager_dispose (GObject *object) { GsmManager *manager = GSM_MANAGER (object); + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); g_debug ("GsmManager: disposing manager"); - g_clear_object (&manager->end_session_cancellable); - g_clear_pointer (&manager->session_name, g_free); + g_clear_object (&priv->end_session_cancellable); + g_clear_pointer (&priv->session_name, g_free); - if (manager->clients != NULL) { - g_signal_handlers_disconnect_by_func (manager->clients, + if (priv->clients != NULL) { + g_signal_handlers_disconnect_by_func (priv->clients, on_store_client_added, manager); - g_signal_handlers_disconnect_by_func (manager->clients, + g_signal_handlers_disconnect_by_func (priv->clients, on_store_client_removed, manager); - g_object_unref (manager->clients); - manager->clients = NULL; + g_object_unref (priv->clients); + priv->clients = NULL; } - g_clear_object (&manager->apps); + g_clear_object (&priv->apps); + g_slist_free (priv->required_apps); + priv->required_apps = NULL; + g_slist_free (priv->pending_apps); + priv->pending_apps = NULL; - if (manager->inhibitors != NULL) { - g_signal_handlers_disconnect_by_func (manager->inhibitors, + if (priv->inhibitors != NULL) { + g_signal_handlers_disconnect_by_func (priv->inhibitors, on_store_inhibitor_added, manager); - g_signal_handlers_disconnect_by_func (manager->inhibitors, + g_signal_handlers_disconnect_by_func (priv->inhibitors, on_store_inhibitor_removed, manager); - g_object_unref (manager->inhibitors); - manager->inhibitors = NULL; + g_object_unref (priv->inhibitors); + priv->inhibitors = NULL; } - g_clear_object (&manager->presence); - g_clear_object (&manager->settings); - g_clear_object (&manager->session_settings); - g_clear_object (&manager->lockdown_settings); - g_clear_object (&manager->system); - g_clear_object (&manager->shell); + g_clear_object (&priv->presence); + g_clear_object (&priv->settings); + g_clear_object (&priv->session_settings); + g_clear_object (&priv->screensaver_settings); + g_clear_object (&priv->lockdown_settings); + g_clear_object (&priv->system); + g_clear_object (&priv->shell); - if (manager->skeleton != NULL) { - g_dbus_interface_skeleton_unexport_from_connection (G_DBUS_INTERFACE_SKELETON (manager->skeleton), - manager->connection); - g_clear_object (&manager->skeleton); + if (priv->skeleton != NULL) { + g_dbus_interface_skeleton_unexport_from_connection (G_DBUS_INTERFACE_SKELETON (priv->skeleton), + priv->connection); + g_clear_object (&priv->skeleton); } - g_clear_object (&manager->connection); + g_clear_object (&priv->connection); G_OBJECT_CLASS (gsm_manager_parent_class)->dispose (object); } @@ -1273,6 +2266,29 @@ gsm_manager_class_init (GsmManagerClass *klass) object_class->constructor = gsm_manager_constructor; object_class->dispose = gsm_manager_dispose; + signals [PHASE_CHANGED] = + g_signal_new ("phase-changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsmManagerClass, phase_changed), + NULL, NULL, NULL, + G_TYPE_NONE, + 1, G_TYPE_STRING); + + g_object_class_install_property (object_class, + PROP_FAILSAFE, + g_param_spec_boolean ("failsafe", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + /** + * GsmManager::session-name + * + * Then name of the currently active session, typically "gnome" or "gnome-fallback". + * This may be the name of the configured default session, or the name of a fallback + * session in case we fell back. + */ g_object_class_install_property (object_class, PROP_SESSION_NAME, g_param_spec_string ("session-name", @@ -1280,6 +2296,36 @@ gsm_manager_class_init (GsmManagerClass *klass) NULL, NULL, G_PARAM_READABLE)); + + /** + * GsmManager::fallback + * + * If %TRUE, the current session is running in the "fallback" mode; + * this is distinct from whether or not it was configured as default. + */ + g_object_class_install_property (object_class, + PROP_FALLBACK, + g_param_spec_boolean ("fallback", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, + PROP_CLIENT_STORE, + g_param_spec_object ("client-store", + NULL, + NULL, + GSM_TYPE_STORE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, + PROP_SYSTEMD_MANAGED, + g_param_spec_boolean ("systemd-managed", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); } static void @@ -1298,20 +2344,23 @@ on_presence_status_changed (GsmPresence *presence, static void on_gsm_system_active_changed (GsmSystem *system, GParamSpec *pspec, - GsmManager *manager) + GsmManager *self) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (self); gboolean is_active; - is_active = gsm_system_is_active (manager->system); + is_active = gsm_system_is_active (priv->system); g_debug ("emitting SessionIsActive"); - gsm_exported_manager_set_session_is_active (manager->skeleton, is_active); + gsm_exported_manager_set_session_is_active (priv->skeleton, is_active); } static gboolean _log_out_is_locked_down (GsmManager *manager) { - return g_settings_get_boolean (manager->lockdown_settings, + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + return g_settings_get_boolean (priv->lockdown_settings, KEY_DISABLE_LOG_OUT); } @@ -1328,16 +2377,60 @@ complete_end_session_task (GsmManager *manager, g_dbus_method_invocation_return_value (invocation, NULL); } +static void +request_reboot (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + g_debug ("GsmManager: requesting reboot"); + + /* FIXME: We need to support a more structured shutdown here, + * but that's blocking on an improved ConsoleKit api. + * + * See https://bugzilla.gnome.org/show_bug.cgi?id=585614 + */ + priv->logout_type = GSM_MANAGER_LOGOUT_REBOOT_INTERACT; + end_phase (manager); +} + +static void +request_shutdown (GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + g_debug ("GsmManager: requesting shutdown"); + + /* See the comment in request_reboot() for some more details about + * what work needs to be done here. */ + priv->logout_type = GSM_MANAGER_LOGOUT_SHUTDOWN_INTERACT; + end_phase (manager); +} + +static void +request_logout (GsmManager *manager, + GsmManagerLogoutMode mode) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + g_debug ("GsmManager: requesting logout"); + + priv->logout_mode = mode; + priv->logout_type = GSM_MANAGER_LOGOUT_LOGOUT; + + end_phase (manager); +} + static gboolean gsm_manager_shutdown (GsmExportedManager *skeleton, GDBusMethodInvocation *invocation, GsmManager *manager) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); GTask *task; g_debug ("GsmManager: Shutdown called"); - if (manager->phase < GSM_MANAGER_PHASE_RUNNING) { + if (priv->phase < GSM_MANAGER_PHASE_RUNNING) { g_dbus_method_invocation_return_error (invocation, GSM_MANAGER_ERROR, GSM_MANAGER_ERROR_NOT_IN_RUNNING, @@ -1353,15 +2446,12 @@ gsm_manager_shutdown (GsmExportedManager *skeleton, return TRUE; } - task = g_task_new (manager, manager->end_session_cancellable, (GAsyncReadyCallback) complete_end_session_task, invocation); + task = g_task_new (manager, priv->end_session_cancellable, (GAsyncReadyCallback) complete_end_session_task, invocation); - manager->pending_end_session_tasks = g_slist_prepend (manager->pending_end_session_tasks, + priv->pending_end_session_tasks = g_slist_prepend (priv->pending_end_session_tasks, task); - g_debug ("GsmManager: requesting shutdown"); - - manager->logout_type = GSM_MANAGER_LOGOUT_SHUTDOWN; - end_phase (manager); + request_shutdown (manager); return TRUE; } @@ -1371,11 +2461,12 @@ gsm_manager_reboot (GsmExportedManager *skeleton, GDBusMethodInvocation *invocation, GsmManager *manager) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); GTask *task; g_debug ("GsmManager: Reboot called"); - if (manager->phase < GSM_MANAGER_PHASE_RUNNING) { + if (priv->phase < GSM_MANAGER_PHASE_RUNNING) { g_dbus_method_invocation_return_error (invocation, GSM_MANAGER_ERROR, GSM_MANAGER_ERROR_NOT_IN_RUNNING, @@ -1391,15 +2482,12 @@ gsm_manager_reboot (GsmExportedManager *skeleton, return TRUE; } - task = g_task_new (manager, manager->end_session_cancellable, (GAsyncReadyCallback) complete_end_session_task, invocation); + task = g_task_new (manager, priv->end_session_cancellable, (GAsyncReadyCallback) complete_end_session_task, invocation); - manager->pending_end_session_tasks = g_slist_prepend (manager->pending_end_session_tasks, + priv->pending_end_session_tasks = g_slist_prepend (priv->pending_end_session_tasks, task); - g_debug ("GsmManager: requesting reboot"); - - manager->logout_type = GSM_MANAGER_LOGOUT_REBOOT; - end_phase (manager); + request_reboot (manager); return TRUE; } @@ -1409,15 +2497,16 @@ gsm_manager_can_shutdown (GsmExportedManager *skeleton, GDBusMethodInvocation *invocation, GsmManager *manager) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); gboolean shutdown_available; g_debug ("GsmManager: CanShutdown called"); shutdown_available = !_log_out_is_locked_down (manager) && - (gsm_system_can_stop (manager->system) - || gsm_system_can_restart (manager->system) - || gsm_system_can_suspend (manager->system) - || gsm_system_can_hibernate (manager->system)); + (gsm_system_can_stop (priv->system) + || gsm_system_can_restart (priv->system) + || gsm_system_can_suspend (priv->system) + || gsm_system_can_hibernate (priv->system)); gsm_exported_manager_complete_can_shutdown (skeleton, invocation, shutdown_available); @@ -1429,12 +2518,13 @@ gsm_manager_can_reboot_to_firmware_setup (GsmExportedManager *skeleton, GDBusMethodInvocation *invocation, GsmManager *manager) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); gboolean reboot_to_firmware_available; g_debug ("GsmManager: CanRebootToFirmwareSetup called"); reboot_to_firmware_available = !_log_out_is_locked_down (manager) && - gsm_system_can_restart_to_firmware_setup (manager->system); + gsm_system_can_restart_to_firmware_setup (priv->system); gsm_exported_manager_complete_can_reboot_to_firmware_setup (skeleton, invocation, reboot_to_firmware_available); @@ -1447,9 +2537,11 @@ gsm_manager_set_reboot_to_firmware_setup (GsmExportedManager *skeleton, gboolean enable, GsmManager *manager) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + g_debug ("GsmManager: SetRebootToFirmwareSetup called"); - gsm_system_set_restart_to_firmware_setup (manager->system, enable); + gsm_system_set_restart_to_firmware_setup (priv->system, enable); gsm_exported_manager_complete_set_reboot_to_firmware_setup (skeleton, invocation); @@ -1463,11 +2555,13 @@ gsm_manager_setenv (GsmExportedManager *skeleton, const char *value, GsmManager *manager) { - if (manager->phase > GSM_MANAGER_PHASE_INITIALIZATION) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + if (priv->phase > GSM_MANAGER_PHASE_INITIALIZATION) { g_dbus_method_invocation_return_error (invocation, GSM_MANAGER_ERROR, GSM_MANAGER_ERROR_NOT_IN_INITIALIZATION, - "Setenv interface is only available during the Initialization phase"); + "Setenv interface is only available during the DisplayServer and Initialization phase"); } else { gsm_util_setenv (variable, value); gsm_exported_manager_complete_setenv (skeleton, invocation); @@ -1481,23 +2575,32 @@ gsm_manager_initialized (GsmExportedManager *skeleton, GDBusMethodInvocation *invocation, GsmManager *manager) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + /* Signaled by helper when gnome-session-initialized.target is reached. */ - if (manager->systemd_initialized) { + if (!priv->systemd_managed) { + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_GENERAL, + "Initialized interface is only available when gnome-session is managed by systemd"); + } else if (priv->systemd_initialized) { g_dbus_method_invocation_return_error (invocation, GSM_MANAGER_ERROR, GSM_MANAGER_ERROR_NOT_IN_INITIALIZATION, "Systemd initialization was already signaled"); - } else if (manager->phase > GSM_MANAGER_PHASE_INITIALIZATION) { + } else if (priv->phase > GSM_MANAGER_PHASE_INITIALIZATION) { g_dbus_method_invocation_return_error (invocation, GSM_MANAGER_ERROR, GSM_MANAGER_ERROR_NOT_IN_INITIALIZATION, "Initialized interface is only available during startup"); } else { - manager->systemd_initialized = TRUE; + priv->systemd_initialized = TRUE; - g_assert (manager->phase == GSM_MANAGER_PHASE_INITIALIZATION); - manager->phase++; - start_phase (manager); + if (priv->manager_initialized) { + g_assert (priv->phase == GSM_MANAGER_PHASE_INITIALIZATION); + priv->phase++; + start_phase (manager); + } gsm_exported_manager_complete_initialized (skeleton, invocation); } @@ -1574,7 +2677,9 @@ gsm_manager_initialization_error (GsmExportedManager *skeleton, gboolean fatal, GsmManager *manager) { - if (manager->phase != GSM_MANAGER_PHASE_INITIALIZATION) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + if (priv->phase != GSM_MANAGER_PHASE_INITIALIZATION) { g_dbus_method_invocation_return_error (invocation, GSM_MANAGER_ERROR, GSM_MANAGER_ERROR_NOT_IN_INITIALIZATION, @@ -1588,12 +2693,29 @@ gsm_manager_initialization_error (GsmExportedManager *skeleton, return TRUE; } +static void +user_logout (GsmManager *manager, + GsmManagerLogoutMode mode) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + if (priv->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION) { + priv->logout_mode = mode; + end_session_or_show_shell_dialog (manager); + return; + } + + request_logout (manager, mode); +} + gboolean gsm_manager_logout (GsmManager *manager, guint logout_mode, GError **error) { - if (manager->phase < GSM_MANAGER_PHASE_RUNNING) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + if (priv->phase < GSM_MANAGER_PHASE_RUNNING) { g_set_error (error, GSM_MANAGER_ERROR, GSM_MANAGER_ERROR_NOT_IN_RUNNING, @@ -1611,13 +2733,9 @@ gsm_manager_logout (GsmManager *manager, switch (logout_mode) { case GSM_MANAGER_LOGOUT_MODE_NORMAL: - g_debug ("GsmManager: requesting logout"); - break; case GSM_MANAGER_LOGOUT_MODE_NO_CONFIRMATION: - g_debug ("GsmManager: requesting no-confirmation logout"); - break; case GSM_MANAGER_LOGOUT_MODE_FORCE: - g_debug ("GsmManager: requesting forced logout"); + user_logout (manager, logout_mode); break; default: @@ -1630,18 +2748,6 @@ gsm_manager_logout (GsmManager *manager, return FALSE; } - manager->logout_mode = logout_mode; - - if (manager->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION) { - /* Someone can upgrade a normal logout to a forced logout - * while we're busy prompting. In this case, re-evaluate */ - end_session_or_show_shell_dialog (manager); - return TRUE; - } - - manager->logout_type = GSM_MANAGER_LOGOUT_LOGOUT; - end_phase (manager); - return TRUE; } @@ -1668,17 +2774,21 @@ static gboolean gsm_manager_register_client (GsmExportedManager *skeleton, GDBusMethodInvocation *invocation, const char *app_id, - const char *ignored, /* was startup_id */ + const char *startup_id, GsmManager *manager) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + char *new_startup_id; const char *sender; GsmClient *client; + GsmApp *app; + app = NULL; client = NULL; - g_debug ("GsmManager: RegisterClient %s", app_id); + g_debug ("GsmManager: RegisterClient %s", startup_id); - if (manager->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION) { + if (priv->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION) { g_debug ("Unable to register client: shutting down"); g_dbus_method_invocation_return_error (invocation, @@ -1688,8 +2798,39 @@ gsm_manager_register_client (GsmExportedManager *skeleton, return TRUE; } + if (IS_STRING_EMPTY (startup_id)) { + new_startup_id = gsm_util_generate_startup_id (); + } else { + + client = (GsmClient *)gsm_store_find (priv->clients, + (GsmStoreFunc)_client_has_startup_id, + (char *)startup_id); + /* We can't have two clients with the same startup id. */ + if (client != NULL) { + g_debug ("Unable to register client: already registered"); + + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_ALREADY_REGISTERED, + "Unable to register client"); + return TRUE; + } + + new_startup_id = g_strdup (startup_id); + } + + g_debug ("GsmManager: Adding new client %s to session", new_startup_id); + + if (app == NULL && !IS_STRING_EMPTY (startup_id)) { + app = find_app_for_startup_id (manager, startup_id); + } + if (app == NULL && !IS_STRING_EMPTY (app_id)) { + /* try to associate this app id with a known app */ + app = find_app_for_app_id (manager, app_id); + } + sender = g_dbus_method_invocation_get_sender (invocation); - client = gsm_client_new (app_id, sender); + client = gsm_dbus_client_new (new_startup_id, sender); if (client == NULL) { g_debug ("Unable to create client"); @@ -1700,14 +2841,29 @@ gsm_manager_register_client (GsmExportedManager *skeleton, return TRUE; } - gsm_store_add (manager->clients, gsm_client_peek_id (client), G_OBJECT (client)); - g_object_unref (client); /* the store will own the ref */ + gsm_store_add (priv->clients, gsm_client_peek_id (client), G_OBJECT (client)); + /* the store will own the ref */ + g_object_unref (client); g_signal_connect (client, "disconnected", G_CALLBACK (on_client_disconnected), manager); + if (app != NULL) { + gsm_client_set_app_id (client, gsm_app_peek_app_id (app)); + gsm_app_set_registered (app, TRUE); + } else { + /* if an app id is specified store it in the client + so we can save it later */ + gsm_client_set_app_id (client, app_id); + } + + gsm_client_set_status (client, GSM_CLIENT_REGISTERED); + + g_assert (new_startup_id != NULL); + g_free (new_startup_id); + gsm_exported_manager_complete_register_client (skeleton, invocation, gsm_client_peek_id (client)); return TRUE; @@ -1719,8 +2875,28 @@ gsm_manager_unregister_client (GsmExportedManager *skeleton, const char *client_id, GsmManager *manager) { - g_debug ("GsmManager: UnregisterClient %s (ignoring)", client_id); + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + GsmClient *client; + + g_debug ("GsmManager: UnregisterClient %s", client_id); + + client = (GsmClient *)gsm_store_lookup (priv->clients, client_id); + if (client == NULL) { + g_debug ("Unable to unregister client: not registered"); + + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_NOT_REGISTERED, + "Unable to unregister client"); + return TRUE; + } + + /* don't disconnect client here, only change the status. + Wait until it leaves the bus before disconnecting it */ + gsm_client_set_status (client, GSM_CLIENT_UNREGISTERED); + gsm_exported_manager_complete_unregister_client (skeleton, invocation); + return TRUE; } @@ -1728,41 +2904,62 @@ static gboolean gsm_manager_inhibit (GsmExportedManager *skeleton, GDBusMethodInvocation *invocation, const char *app_id, - guint unused, /* Was an X window ID */ + guint toplevel_xid, const char *reason, guint flags, GsmManager *manager) { - const char *validation_error = NULL; + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); GsmInhibitor *inhibitor; guint cookie; - g_debug ("GsmManager: Inhibit app_id=%s reason=%s flags=%u", - app_id, reason, flags); + g_debug ("GsmManager: Inhibit xid=%u app_id=%s reason=%s flags=%u", + toplevel_xid, + app_id, + reason, + flags); - if (manager->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE) - validation_error = "Forced logout cannot be inhibited"; - else if (IS_STRING_EMPTY (reason)) - validation_error = "Reason not specified"; - else if (flags == 0) - validation_error = "Invalid inhibit flags"; + if (priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE) { + GError *new_error; - if (validation_error != NULL) { - g_debug ("GsmManager: Unable to inhibit: %s", validation_error); - g_dbus_method_invocation_return_error (invocation, - GSM_MANAGER_ERROR, - GSM_MANAGER_ERROR_GENERAL, - "%s", validation_error); + new_error = g_error_new (GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_GENERAL, + "Forced logout cannot be inhibited"); + g_debug ("GsmManager: Unable to inhibit: %s", new_error->message); + g_dbus_method_invocation_take_error (invocation, new_error); + return TRUE; + } + + if (IS_STRING_EMPTY (reason)) { + GError *new_error; + + new_error = g_error_new (GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_GENERAL, + "Reason not specified"); + g_debug ("GsmManager: Unable to inhibit: %s", new_error->message); + g_dbus_method_invocation_take_error (invocation, new_error); + return TRUE; + } + + if (flags == 0) { + GError *new_error; + + new_error = g_error_new (GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_GENERAL, + "Invalid inhibit flags"); + g_debug ("GsmManager: Unable to inhibit: %s", new_error->message); + g_dbus_method_invocation_take_error (invocation, new_error); return TRUE; } cookie = _generate_unique_cookie (manager); inhibitor = gsm_inhibitor_new (app_id, + toplevel_xid, flags, reason, g_dbus_method_invocation_get_sender (invocation), cookie); - gsm_store_add (manager->inhibitors, gsm_inhibitor_peek_id (inhibitor), G_OBJECT (inhibitor)); + gsm_store_add (priv->inhibitors, gsm_inhibitor_peek_id (inhibitor), G_OBJECT (inhibitor)); g_object_unref (inhibitor); gsm_exported_manager_complete_inhibit (skeleton, invocation, cookie); @@ -1776,11 +2973,12 @@ gsm_manager_uninhibit (GsmExportedManager *skeleton, guint cookie, GsmManager *manager) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); GsmInhibitor *inhibitor; g_debug ("GsmManager: Uninhibit %u", cookie); - inhibitor = (GsmInhibitor *)gsm_store_find (manager->inhibitors, + inhibitor = (GsmInhibitor *)gsm_store_find (priv->inhibitors, (GsmStoreFunc)_find_by_cookie, &cookie); if (inhibitor == NULL) { @@ -1794,13 +2992,14 @@ gsm_manager_uninhibit (GsmExportedManager *skeleton, return TRUE; } - g_debug ("GsmManager: removing inhibitor %s reason '%s' %u connection %s", + g_debug ("GsmManager: removing inhibitor %s %u reason '%s' %u connection %s", gsm_inhibitor_peek_app_id (inhibitor), + gsm_inhibitor_peek_toplevel_xid (inhibitor), gsm_inhibitor_peek_reason (inhibitor), gsm_inhibitor_peek_flags (inhibitor), gsm_inhibitor_peek_bus_name (inhibitor)); - gsm_store_remove (manager->inhibitors, gsm_inhibitor_peek_id (inhibitor)); + gsm_store_remove (priv->inhibitors, gsm_inhibitor_peek_id (inhibitor)); gsm_exported_manager_complete_uninhibit (skeleton, invocation); @@ -1813,14 +3012,15 @@ gsm_manager_is_inhibited (GsmExportedManager *skeleton, guint flags, GsmManager *manager) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); GsmInhibitor *inhibitor; gboolean is_inhibited; - if (manager->inhibitors == NULL - || gsm_store_size (manager->inhibitors) == 0) { + if (priv->inhibitors == NULL + || gsm_store_size (priv->inhibitors) == 0) { is_inhibited = FALSE; } else { - inhibitor = (GsmInhibitor *) gsm_store_find (manager->inhibitors, + inhibitor = (GsmInhibitor *) gsm_store_find (priv->inhibitors, (GsmStoreFunc)inhibitor_has_flag, GUINT_TO_POINTER (flags)); if (inhibitor == NULL) { @@ -1844,15 +3044,37 @@ listify_store_ids (char *id, return FALSE; } +static gboolean +gsm_manager_get_clients (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + GPtrArray *clients; + + clients = g_ptr_array_new_with_free_func (g_free); + gsm_store_foreach (priv->clients, + (GsmStoreFunc) listify_store_ids, + &clients); + g_ptr_array_add (clients, NULL); + + gsm_exported_manager_complete_get_clients (skeleton, invocation, + (const gchar * const *) clients->pdata); + g_ptr_array_unref (clients); + + return TRUE; +} + static gboolean gsm_manager_get_inhibitors (GsmExportedManager *skeleton, GDBusMethodInvocation *invocation, GsmManager *manager) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); GPtrArray *inhibitors; inhibitors = g_ptr_array_new_with_free_func (g_free); - gsm_store_foreach (manager->inhibitors, + gsm_store_foreach (priv->inhibitors, (GsmStoreFunc) listify_store_ids, &inhibitors); g_ptr_array_add (inhibitors, NULL); @@ -1864,18 +3086,54 @@ gsm_manager_get_inhibitors (GsmExportedManager *skeleton, return TRUE; } +static gboolean +_app_has_autostart_condition (const char *id, + GsmApp *app, + const char *condition) +{ + gboolean has; + gboolean disabled; + + has = gsm_app_has_autostart_condition (app, condition); + disabled = gsm_app_peek_is_disabled (app); + + return has && !disabled; +} + +static gboolean +gsm_manager_is_autostart_condition_handled (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + const char *condition, + GsmManager *manager) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + GsmApp *app; + gboolean handled; + + app = (GsmApp *) gsm_store_find (priv->apps,( + GsmStoreFunc) _app_has_autostart_condition, + (char *)condition); + + if (app != NULL) { + handled = TRUE; + } else { + handled = FALSE; + } + + gsm_exported_manager_complete_is_autostart_condition_handled (skeleton, invocation, handled); + + return TRUE; +} + static gboolean gsm_manager_is_session_running (GsmExportedManager *skeleton, GDBusMethodInvocation *invocation, GsmManager *manager) { - gboolean running; + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); - running = (manager->phase == GSM_MANAGER_PHASE_APPLICATION || - manager->phase == GSM_MANAGER_PHASE_RUNNING || - manager->phase == GSM_MANAGER_PHASE_QUERY_END_SESSION); - - gsm_exported_manager_complete_is_session_running (skeleton, invocation, running); + gsm_exported_manager_complete_is_session_running (skeleton, invocation, + priv->phase == GSM_MANAGER_PHASE_RUNNING); return TRUE; } @@ -1885,15 +3143,21 @@ on_session_connection_closed (GDBusConnection *connection, GError *error, gpointer user_data) { - GsmManager *manager = GSM_MANAGER (user_data); + GsmManager *manager; + GsmManagerPrivate *priv; + + manager = GSM_MANAGER (user_data); + priv = gsm_manager_get_instance_private (manager); + g_debug ("GsmManager: dbus disconnected; disconnecting dbus clients..."); - manager->dbus_disconnected = TRUE; + priv->dbus_disconnected = TRUE; remove_clients_for_connection (manager, NULL); } static gboolean register_manager (GsmManager *manager) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); GDBusConnection *connection; GsmExportedManager *skeleton; GError *error = NULL; @@ -1923,6 +3187,8 @@ register_manager (GsmManager *manager) G_CALLBACK (gsm_manager_can_reboot_to_firmware_setup), manager); g_signal_connect (skeleton, "handle-can-shutdown", G_CALLBACK (gsm_manager_can_shutdown), manager); + g_signal_connect (skeleton, "handle-get-clients", + G_CALLBACK (gsm_manager_get_clients), manager); g_signal_connect (skeleton, "handle-get-inhibitors", G_CALLBACK (gsm_manager_get_inhibitors), manager); g_signal_connect (skeleton, "handle-get-locale", @@ -1931,6 +3197,8 @@ register_manager (GsmManager *manager) G_CALLBACK (gsm_manager_inhibit), manager); g_signal_connect (skeleton, "handle-initialization-error", G_CALLBACK (gsm_manager_initialization_error), manager); + g_signal_connect (skeleton, "handle-is-autostart-condition-handled", + G_CALLBACK (gsm_manager_is_autostart_condition_handled), manager); g_signal_connect (skeleton, "handle-is-inhibited", G_CALLBACK (gsm_manager_is_inhibited), manager); g_signal_connect (skeleton, "handle-is-session-running", @@ -1954,18 +3222,18 @@ register_manager (GsmManager *manager) g_signal_connect (skeleton, "handle-unregister-client", G_CALLBACK (gsm_manager_unregister_client), manager); - manager->dbus_disconnected = FALSE; + priv->dbus_disconnected = FALSE; g_signal_connect (connection, "closed", G_CALLBACK (on_session_connection_closed), manager); - manager->connection = connection; - manager->skeleton = skeleton; + priv->connection = connection; + priv->skeleton = skeleton; - g_signal_connect (manager->system, "notify::active", + g_signal_connect (priv->system, "notify::active", G_CALLBACK (on_gsm_system_active_changed), manager); /* cold-plug SessionIsActive */ - on_gsm_system_active_changed (manager->system, NULL, manager); + on_gsm_system_active_changed (priv->system, NULL, manager); return TRUE; } @@ -1986,50 +3254,43 @@ idle_timeout_get_mapping (GValue *value, static void gsm_manager_init (GsmManager *manager) { - manager->settings = g_settings_new (GSM_MANAGER_SCHEMA); - manager->session_settings = g_settings_new (SESSION_SCHEMA); - manager->lockdown_settings = g_settings_new (LOCKDOWN_SCHEMA); + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); - manager->clients = gsm_store_new (); - g_signal_connect (manager->clients, - "added", - G_CALLBACK (on_store_client_added), - manager); - g_signal_connect (manager->clients, - "removed", - G_CALLBACK (on_store_client_removed), - manager); + priv->settings = g_settings_new (GSM_MANAGER_SCHEMA); + priv->session_settings = g_settings_new (SESSION_SCHEMA); + priv->screensaver_settings = g_settings_new (SCREENSAVER_SCHEMA); + priv->lockdown_settings = g_settings_new (LOCKDOWN_SCHEMA); - manager->inhibitors = gsm_store_new (); - g_signal_connect (manager->inhibitors, + priv->inhibitors = gsm_store_new (); + g_signal_connect (priv->inhibitors, "added", G_CALLBACK (on_store_inhibitor_added), manager); - g_signal_connect (manager->inhibitors, + g_signal_connect (priv->inhibitors, "removed", G_CALLBACK (on_store_inhibitor_removed), manager); - manager->apps = gsm_store_new (); + priv->apps = gsm_store_new (); - manager->presence = gsm_presence_new (); - g_signal_connect (manager->presence, + priv->presence = gsm_presence_new (); + g_signal_connect (priv->presence, "status-changed", G_CALLBACK (on_presence_status_changed), manager); - g_settings_bind_with_mapping (manager->session_settings, + g_settings_bind_with_mapping (priv->session_settings, KEY_IDLE_DELAY, - manager->presence, + priv->presence, "idle-timeout", G_SETTINGS_BIND_GET, idle_timeout_get_mapping, NULL, NULL, NULL); - manager->system = gsm_get_system (); - manager->shell = gsm_get_shell (); - manager->end_session_cancellable = g_cancellable_new (); + priv->system = gsm_get_system (); + priv->shell = gsm_get_shell (); + priv->end_session_cancellable = g_cancellable_new (); } GsmManager * @@ -2039,18 +3300,23 @@ gsm_manager_get (void) } GsmManager * -gsm_manager_new (void) +gsm_manager_new (GsmStore *client_store, + gboolean failsafe, + gboolean systemd_managed) { if (manager_object != NULL) { g_object_ref (manager_object); } else { gboolean res; - manager_object = g_object_new (GSM_TYPE_MANAGER, NULL); + manager_object = g_object_new (GSM_TYPE_MANAGER, + "client-store", client_store, + "failsafe", failsafe, + "systemd-managed", systemd_managed, + NULL); g_object_add_weak_pointer (manager_object, (gpointer *) &manager_object); - res = register_manager (manager_object); if (! res) { g_object_unref (manager_object); @@ -2064,16 +3330,37 @@ gsm_manager_new (void) static void disconnect_shell_dialog_signals (GsmManager *manager) { - g_clear_signal_handler (&manager->shell_end_session_dialog_canceled_id, - manager->shell); - g_clear_signal_handler (&manager->shell_end_session_dialog_open_failed_id, - manager->shell); - g_clear_signal_handler (&manager->shell_end_session_dialog_confirmed_logout_id, - manager->shell); - g_clear_signal_handler (&manager->shell_end_session_dialog_confirmed_shutdown_id, - manager->shell); - g_clear_signal_handler (&manager->shell_end_session_dialog_confirmed_reboot_id, - manager->shell); + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + if (priv->shell_end_session_dialog_canceled_id != 0) { + g_signal_handler_disconnect (priv->shell, + priv->shell_end_session_dialog_canceled_id); + priv->shell_end_session_dialog_canceled_id = 0; + } + + if (priv->shell_end_session_dialog_confirmed_logout_id != 0) { + g_signal_handler_disconnect (priv->shell, + priv->shell_end_session_dialog_confirmed_logout_id); + priv->shell_end_session_dialog_confirmed_logout_id = 0; + } + + if (priv->shell_end_session_dialog_confirmed_shutdown_id != 0) { + g_signal_handler_disconnect (priv->shell, + priv->shell_end_session_dialog_confirmed_shutdown_id); + priv->shell_end_session_dialog_confirmed_shutdown_id = 0; + } + + if (priv->shell_end_session_dialog_confirmed_reboot_id != 0) { + g_signal_handler_disconnect (priv->shell, + priv->shell_end_session_dialog_confirmed_reboot_id); + priv->shell_end_session_dialog_confirmed_reboot_id = 0; + } + + if (priv->shell_end_session_dialog_open_failed_id != 0) { + g_signal_handler_disconnect (priv->shell, + priv->shell_end_session_dialog_open_failed_id); + priv->shell_end_session_dialog_open_failed_id = 0; + } } static void @@ -2085,20 +3372,27 @@ on_shell_end_session_dialog_canceled (GsmShell *shell, } static void -handle_end_session_dialog_confirmation (GsmManager *manager, - GsmManagerLogoutType logout_type) -{ - disconnect_shell_dialog_signals (manager); - - if (manager->phase >= GSM_MANAGER_PHASE_END_SESSION) - return; /* Already logging out, nothing to do */ - - if (manager->logout_type != logout_type) { - g_warning ("GsmManager: Shell confirmed unexpected logout type"); - return; /* Make it obvious that something went wrong */ +_handle_end_session_dialog_response (GsmManager *manager, + GsmManagerLogoutType logout_type) +{ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + /* Note we're checking for END_SESSION here and + * QUERY_END_SESSION in the fallback cases elsewhere. + * + * That's because they run at different times in the logout + * process. The shell combines the inhibit and + * confirmation dialogs, so it gets displayed after we've collected + * inhibitors. The fallback code has two distinct dialogs, once of + * which we can (and do show) before collecting the inhibitors. + */ + if (priv->phase >= GSM_MANAGER_PHASE_END_SESSION) { + /* Already shutting down, nothing more to do */ + return; } - manager->logout_mode = GSM_MANAGER_LOGOUT_MODE_FORCE; + priv->logout_mode = GSM_MANAGER_LOGOUT_MODE_FORCE; + priv->logout_type = logout_type; end_phase (manager); } @@ -2106,55 +3400,60 @@ static void on_shell_end_session_dialog_confirmed_logout (GsmShell *shell, GsmManager *manager) { - handle_end_session_dialog_confirmation (manager, GSM_MANAGER_LOGOUT_LOGOUT); + _handle_end_session_dialog_response (manager, GSM_MANAGER_LOGOUT_LOGOUT); + disconnect_shell_dialog_signals (manager); } static void on_shell_end_session_dialog_confirmed_shutdown (GsmShell *shell, GsmManager *manager) { - handle_end_session_dialog_confirmation (manager, GSM_MANAGER_LOGOUT_SHUTDOWN); + _handle_end_session_dialog_response (manager, GSM_MANAGER_LOGOUT_SHUTDOWN); + disconnect_shell_dialog_signals (manager); } static void on_shell_end_session_dialog_confirmed_reboot (GsmShell *shell, GsmManager *manager) { - handle_end_session_dialog_confirmation (manager, GSM_MANAGER_LOGOUT_REBOOT); + _handle_end_session_dialog_response (manager, GSM_MANAGER_LOGOUT_REBOOT); + disconnect_shell_dialog_signals (manager); } static void connect_shell_dialog_signals (GsmManager *manager) { - if (manager->shell_end_session_dialog_canceled_id != 0) + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + + if (priv->shell_end_session_dialog_canceled_id != 0) return; - manager->shell_end_session_dialog_canceled_id = - g_signal_connect (manager->shell, + priv->shell_end_session_dialog_canceled_id = + g_signal_connect (priv->shell, "end-session-dialog-canceled", G_CALLBACK (on_shell_end_session_dialog_canceled), manager); - manager->shell_end_session_dialog_open_failed_id = - g_signal_connect (manager->shell, + priv->shell_end_session_dialog_open_failed_id = + g_signal_connect (priv->shell, "end-session-dialog-open-failed", G_CALLBACK (on_shell_end_session_dialog_canceled), manager); - manager->shell_end_session_dialog_confirmed_logout_id = - g_signal_connect (manager->shell, + priv->shell_end_session_dialog_confirmed_logout_id = + g_signal_connect (priv->shell, "end-session-dialog-confirmed-logout", G_CALLBACK (on_shell_end_session_dialog_confirmed_logout), manager); - manager->shell_end_session_dialog_confirmed_shutdown_id = - g_signal_connect (manager->shell, + priv->shell_end_session_dialog_confirmed_shutdown_id = + g_signal_connect (priv->shell, "end-session-dialog-confirmed-shutdown", G_CALLBACK (on_shell_end_session_dialog_confirmed_shutdown), manager); - manager->shell_end_session_dialog_confirmed_reboot_id = - g_signal_connect (manager->shell, + priv->shell_end_session_dialog_confirmed_reboot_id = + g_signal_connect (priv->shell, "end-session-dialog-confirmed-reboot", G_CALLBACK (on_shell_end_session_dialog_confirmed_reboot), manager); @@ -2164,14 +3463,15 @@ static void show_shell_end_session_dialog (GsmManager *manager, GsmShellEndSessionDialogType type) { - if (!gsm_shell_is_running (manager->shell)) - return; + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); - connect_shell_dialog_signals (manager); + if (!gsm_shell_is_running (priv->shell)) + return; - gsm_shell_open_end_session_dialog (manager->shell, + gsm_shell_open_end_session_dialog (priv->shell, type, - manager->inhibitors); + priv->inhibitors); + connect_shell_dialog_signals (manager); } /* @@ -2185,57 +3485,151 @@ gboolean gsm_manager_set_phase (GsmManager *manager, GsmManagerPhase phase) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + g_return_val_if_fail (GSM_IS_MANAGER (manager), FALSE); - manager->phase = phase; + priv->phase = phase; return (TRUE); } static void append_app (GsmManager *manager, - GsmApp *app) + GsmApp *app, + gboolean is_required) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + const char *id; const char *app_id; GsmApp *dup; + id = gsm_app_peek_id (app); + if (IS_STRING_EMPTY (id)) { + g_debug ("GsmManager: not adding app: no id"); + return; + } + + dup = (GsmApp *)gsm_store_lookup (priv->apps, id); + if (dup != NULL) { + g_debug ("GsmManager: not adding app: already added"); + return; + } + app_id = gsm_app_peek_app_id (app); if (IS_STRING_EMPTY (app_id)) { g_debug ("GsmManager: not adding app: no app-id"); return; } - dup = (GsmApp *) gsm_store_lookup (manager->apps, app_id); + dup = find_app_for_app_id (manager, app_id); if (dup != NULL) { g_debug ("GsmManager: not adding app: app-id '%s' already exists", app_id); + + if (is_required && + !g_slist_find (priv->required_apps, dup)) { + g_debug ("GsmManager: making app '%s' required", gsm_app_peek_app_id (dup)); + priv->required_apps = g_slist_prepend (priv->required_apps, dup); + } + return; } - gsm_store_add (manager->apps, app_id, G_OBJECT (app)); + gsm_store_add (priv->apps, id, G_OBJECT (app)); + if (is_required) { + g_debug ("GsmManager: adding required app %s", gsm_app_peek_app_id (app)); + priv->required_apps = g_slist_prepend (priv->required_apps, app); + } } -gboolean -gsm_manager_add_autostart_app (GsmManager *manager, - const char *path) +static gboolean +add_autostart_app_internal (GsmManager *manager, + const char *path, + gboolean is_required) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); GsmApp *app; + char **internal_provides; GError *error = NULL; g_return_val_if_fail (GSM_IS_MANAGER (manager), FALSE); g_return_val_if_fail (path != NULL, FALSE); - app = gsm_app_new_for_path (path, &error); + /* Note: if we cannot add the app because its service is already + * provided, because its app-id is taken, or because of any other + * reason meaning there is already an app playing its role, then we + * should make sure that relevant properties (like + * provides/is_required) are set in the pre-existing app if needed. */ + app = gsm_autostart_app_new (path, priv->systemd_managed, &error); if (app == NULL) { g_warning ("%s", error->message); g_clear_error (&error); return FALSE; } + internal_provides = gsm_app_get_provides (app); + if (internal_provides) { + int i; + gboolean provided = FALSE; + + for (i = 0; internal_provides[i] != NULL; i++) { + GsmApp *dup; + + dup = (GsmApp *)gsm_store_find (priv->apps, + (GsmStoreFunc)_find_app_provides, + (char *)internal_provides[i]); + if (dup != NULL) { + g_debug ("GsmManager: service '%s' is already provided", internal_provides[i]); + + if (is_required && + !g_slist_find (priv->required_apps, dup)) { + g_debug ("GsmManager: making app '%s' required", gsm_app_peek_app_id (dup)); + priv->required_apps = g_slist_prepend (priv->required_apps, dup); + } + + provided = TRUE; + break; + } + } + + g_strfreev (internal_provides); + + if (provided) { + g_object_unref (app); + return FALSE; + } + } + g_debug ("GsmManager: read %s", path); - append_app (manager, app); + append_app (manager, app, is_required); g_object_unref (app); return TRUE; } +gboolean +gsm_manager_add_autostart_app (GsmManager *manager, + const char *path) +{ + return add_autostart_app_internal (manager, path, FALSE); +} + +/** + * gsm_manager_add_required_app: + * @manager: a #GsmManager + * @path: Path to desktop file + * + * Similar to gsm_manager_add_autostart_app(), except marks the + * component as being required; we then try harder to ensure + * it's running and inform the user if we can't. + * + */ +gboolean +gsm_manager_add_required_app (GsmManager *manager, + const char *path) +{ + return add_autostart_app_internal (manager, path, TRUE); +} + + gboolean gsm_manager_add_autostart_apps_from_dir (GsmManager *manager, const char *path) @@ -2275,17 +3669,19 @@ on_shutdown_prepared (GsmSystem *system, gboolean success, GsmManager *manager) { + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + g_debug ("GsmManager: on_shutdown_prepared, success: %d", success); g_signal_handlers_disconnect_by_func (system, on_shutdown_prepared, manager); if (success) { /* move to end-session phase */ - g_assert (manager->phase == GSM_MANAGER_PHASE_QUERY_END_SESSION); - manager->phase++; + g_assert (priv->phase == GSM_MANAGER_PHASE_QUERY_END_SESSION); + priv->phase++; start_phase (manager); } else { disconnect_shell_dialog_signals (manager); - gsm_shell_close_end_session_dialog (manager->shell); + gsm_shell_close_end_session_dialog (priv->shell); /* back to running phase */ cancel_end_session (manager); } @@ -2294,14 +3690,33 @@ on_shutdown_prepared (GsmSystem *system, static gboolean do_query_end_session_exit (GsmManager *manager) { - if (manager->logout_type != GSM_MANAGER_LOGOUT_SHUTDOWN && - manager->logout_type != GSM_MANAGER_LOGOUT_REBOOT) - return TRUE; /* Continue to end session phase */ + GsmManagerPrivate *priv = gsm_manager_get_instance_private (manager); + gboolean reboot = FALSE; + gboolean shutdown = FALSE; + + switch (priv->logout_type) { + case GSM_MANAGER_LOGOUT_LOGOUT: + break; + case GSM_MANAGER_LOGOUT_REBOOT: + case GSM_MANAGER_LOGOUT_REBOOT_INTERACT: + reboot = TRUE; + break; + case GSM_MANAGER_LOGOUT_SHUTDOWN: + case GSM_MANAGER_LOGOUT_SHUTDOWN_INTERACT: + shutdown = TRUE; + break; + default: + g_warning ("Unexpected logout type %d in do_query_end_session_exit()", + priv->logout_type); + break; + } - g_signal_connect (manager->system, "shutdown-prepared", - G_CALLBACK (on_shutdown_prepared), manager); - gsm_system_prepare_shutdown (manager->system, - manager->logout_type == GSM_MANAGER_LOGOUT_REBOOT); + if (reboot || shutdown) { + g_signal_connect (priv->system, "shutdown-prepared", + G_CALLBACK (on_shutdown_prepared), manager); + gsm_system_prepare_shutdown (priv->system, reboot); + return FALSE; /* don't leave query end session yet */ + } - return FALSE; /* don't leave query end session yet */ + return TRUE; /* go to end session phase */ } diff --git a/gnome-session/gsm-manager.h b/gnome-session/gsm-manager.h index b358d2b5..3cfd8608 100644 --- a/gnome-session/gsm-manager.h +++ b/gnome-session/gsm-manager.h @@ -28,25 +28,41 @@ G_BEGIN_DECLS -/* UUIDs for log messages */ -#define GSM_MANAGER_STARTUP_SUCCEEDED_MSGID "0ce153587afa4095832d233c17a88001" -#define GSM_MANAGER_UNRECOVERABLE_FAILURE_MSGID "10dd2dc188b54a5e98970f56499d1f73" - #define GSM_TYPE_MANAGER (gsm_manager_get_type ()) -G_DECLARE_FINAL_TYPE (GsmManager, gsm_manager, GSM, MANAGER, GObject) +G_DECLARE_DERIVABLE_TYPE (GsmManager, gsm_manager, GSM, MANAGER, GObject) + +struct _GsmManagerClass +{ + GObjectClass parent_class; + + void (* phase_changed) (GsmManager *manager, + const char *phase); +}; typedef enum { - /* We're waiting for systemd to start session services, the compositor, etc */ - GSM_MANAGER_PHASE_INITIALIZATION = 0, - /* The session is running, but we're still launching the user's autostart apps */ + /* gsm's own startup/initialization phase */ + GSM_MANAGER_PHASE_STARTUP = 0, + /* gnome-initial-setup */ + GSM_MANAGER_PHASE_EARLY_INITIALIZATION, + /* gnome-keyring-daemon */ + GSM_MANAGER_PHASE_PRE_DISPLAY_SERVER, + /* wayland compositor and XWayland */ + GSM_MANAGER_PHASE_DISPLAY_SERVER, + /* xrandr setup, gnome-settings-daemon, etc */ + GSM_MANAGER_PHASE_INITIALIZATION, + /* window/compositing managers */ + GSM_MANAGER_PHASE_WINDOW_MANAGER, + /* apps that will create _NET_WM_WINDOW_TYPE_PANEL windows */ + GSM_MANAGER_PHASE_PANEL, + /* apps that will create _NET_WM_WINDOW_TYPE_DESKTOP windows */ + GSM_MANAGER_PHASE_DESKTOP, + /* everything else */ GSM_MANAGER_PHASE_APPLICATION, - /* The session is running */ + /* done launching */ GSM_MANAGER_PHASE_RUNNING, - /* Someone requested for the session to end, */ + /* shutting down */ GSM_MANAGER_PHASE_QUERY_END_SESSION, - /* The session is shutting down */ GSM_MANAGER_PHASE_END_SESSION, - /* The session has shut down, and gnome-session is about to exit */ GSM_MANAGER_PHASE_EXIT } GsmManagerPhase; @@ -65,13 +81,20 @@ typedef enum #define GSM_MANAGER_ERROR gsm_manager_error_quark () GQuark gsm_manager_error_quark (void); -GsmManager * gsm_manager_new (void); +GsmManager * gsm_manager_new (GsmStore *client_store, + gboolean failsafe, + gboolean systemd_managed); GsmManager * gsm_manager_get (void); + +gboolean gsm_manager_get_failsafe (GsmManager *manager); gboolean gsm_manager_get_dbus_disconnected (GsmManager *manager); +gboolean gsm_manager_get_systemd_managed (GsmManager *manager); gboolean gsm_manager_add_autostart_app (GsmManager *manager, const char *path); +gboolean gsm_manager_add_required_app (GsmManager *manager, + const char *path); gboolean gsm_manager_add_autostart_apps_from_dir (GsmManager *manager, const char *path); gboolean gsm_manager_add_legacy_session_apps (GsmManager *manager, @@ -79,8 +102,11 @@ gboolean gsm_manager_add_legacy_session_apps (GsmManager * void gsm_manager_start (GsmManager *manager); +char * _gsm_manager_get_default_session (GsmManager *manager); + void _gsm_manager_set_active_session (GsmManager *manager, - const char *session_name); + const char *session_name, + gboolean is_fallback); gboolean gsm_manager_logout (GsmManager *manager, guint logout_mode, @@ -89,8 +115,6 @@ gboolean gsm_manager_logout (GsmManager * gboolean gsm_manager_set_phase (GsmManager *manager, GsmManagerPhase phase); -void gsm_quit (void); - G_END_DECLS #endif /* __GSM_MANAGER_H */ diff --git a/gnome-session/gsm-process-helper.c b/gnome-session/gsm-process-helper.c new file mode 100644 index 00000000..32f26c8d --- /dev/null +++ b/gnome-session/gsm-process-helper.c @@ -0,0 +1,113 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2010 Novell, 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 + * 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 "gsm-process-helper.h" + +typedef struct { + gboolean done; + GSubprocess *process; + gboolean caught_error; + GError **error; + GMainContext *maincontext; + GSource *timeout_source; +} GsmProcessHelper; + +static void +on_child_exited (GObject *source, + GAsyncResult *result, + gpointer data) +{ + GsmProcessHelper *helper = data; + + helper->done = TRUE; + + if (!g_subprocess_wait_check_finish ((GSubprocess*)source, result, + helper->caught_error ? NULL : helper->error)) + helper->caught_error = TRUE; + + g_clear_pointer (&helper->timeout_source, g_source_destroy); + + g_main_context_wakeup (helper->maincontext); +} + +static gboolean +on_child_timeout (gpointer data) +{ + GsmProcessHelper *helper = data; + + g_assert (!helper->done); + + g_subprocess_force_exit (helper->process); + + g_set_error_literal (helper->error, + G_IO_CHANNEL_ERROR, + G_IO_CHANNEL_ERROR_FAILED, + "Timed out"); + + helper->timeout_source = NULL; + + return FALSE; +} + +gboolean +gsm_process_helper (const char *command_line, + unsigned int timeout, + GError **error) +{ + gboolean ret = FALSE; + GsmProcessHelper helper = { 0, }; + gchar **argv = NULL; + GMainContext *subcontext = NULL; + + if (!g_shell_parse_argv (command_line, NULL, &argv, error)) + goto out; + + helper.error = error; + + subcontext = g_main_context_new (); + g_main_context_push_thread_default (subcontext); + + helper.process = g_subprocess_newv ((const char*const*)argv, 0, error); + if (!helper.process) + goto out; + + g_subprocess_wait_async (helper.process, NULL, on_child_exited, &helper); + + helper.timeout_source = g_timeout_source_new (timeout); + + g_source_set_callback (helper.timeout_source, on_child_timeout, &helper, NULL); + g_source_attach (helper.timeout_source, subcontext); + + while (!helper.done) + g_main_context_iteration (subcontext, TRUE); + + ret = helper.caught_error; + out: + g_strfreev (argv); + if (subcontext) { + g_main_context_pop_thread_default (subcontext); + g_main_context_unref (subcontext); + } + g_clear_object (&helper.process); + return ret; +} diff --git a/gnome-session/gsm-process-helper.h b/gnome-session/gsm-process-helper.h new file mode 100644 index 00000000..4ca55a95 --- /dev/null +++ b/gnome-session/gsm-process-helper.h @@ -0,0 +1,32 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2010 Novell, 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 + * 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 . + */ + +#ifndef __GSM_PROCESS_HELPER_H +#define __GSM_PROCESS_HELPER_H + +#include + +G_BEGIN_DECLS + +int gsm_process_helper (const char *command_line, + unsigned int timeout, + GError **error); + +G_END_DECLS + +#endif /* __GSM_PROCESS_HELPER_H */ diff --git a/gnome-session/gsm-session-fill.c b/gnome-session/gsm-session-fill.c index b94dec25..d6aeaa50 100644 --- a/gnome-session/gsm-session-fill.c +++ b/gnome-session/gsm-session-fill.c @@ -23,36 +23,117 @@ #include "gsm-system.h" #include "gsm-manager.h" +#include "gsm-process-helper.h" +#include "gsm-util.h" #define GSM_KEYFILE_SESSION_GROUP "GNOME Session" +#define GSM_KEYFILE_RUNNABLE_KEY "IsRunnableHelper" +#define GSM_KEYFILE_FALLBACK_KEY "FallbackSession" +#define GSM_KEYFILE_REQUIRED_COMPONENTS_KEY "RequiredComponents" + +/* See https://bugzilla.gnome.org/show_bug.cgi?id=641992 for discussion */ +#define GSM_RUNNABLE_HELPER_TIMEOUT 3000 /* ms */ + +typedef void (*GsmFillHandleComponent) (const char *component, + const char *app_path, + gpointer user_data); static void -load_standard_apps (GsmManager *manager, - GKeyFile *keyfile) +handle_required_components (GKeyFile *keyfile, + GsmFillHandleComponent callback, + gpointer user_data) { - char *dir; - const char * const *system_dirs; + char **required_components; int i; - if (g_key_file_get_boolean (keyfile, GSM_KEYFILE_SESSION_GROUP, "Kiosk", NULL)) + g_assert (keyfile != NULL); + g_assert (callback != NULL); + + required_components = g_key_file_get_string_list (keyfile, + GSM_KEYFILE_SESSION_GROUP, + GSM_KEYFILE_REQUIRED_COMPONENTS_KEY, + NULL, NULL); + + if (!required_components) return; - dir = g_build_filename (g_get_user_config_dir (), "autostart", NULL); - gsm_manager_add_autostart_apps_from_dir (manager, dir); - g_free (dir); + for (i = 0; required_components[i] != NULL; i++) { + char *app_path; - system_dirs = g_get_system_data_dirs (); - for (i = 0; system_dirs[i]; i++) { - dir = g_build_filename (system_dirs[i], "gnome", "autostart", NULL); - gsm_manager_add_autostart_apps_from_dir (manager, dir); - g_free (dir); + app_path = gsm_util_find_desktop_file_for_app_name (required_components[i], + TRUE); + callback (required_components[i], app_path, user_data); + g_free (app_path); } - system_dirs = g_get_system_config_dirs (); - for (i = 0; system_dirs[i]; i++) { - dir = g_build_filename (system_dirs[i], "autostart", NULL); - gsm_manager_add_autostart_apps_from_dir (manager, dir); - g_free (dir); + g_strfreev (required_components); +} + +static void +check_required_components_helper (const char *component, + const char *app_path, + gpointer user_data) +{ + gboolean *error = user_data; + + if (app_path == NULL) { + g_warning ("Unable to find required component '%s'", component); + *error = TRUE; + } +} + +static gboolean +check_required (GKeyFile *keyfile) +{ + gboolean error = FALSE; + + g_debug ("fill: *** Checking required components"); + + handle_required_components (keyfile, + check_required_components_helper, + &error); + + g_debug ("fill: *** Done checking required components"); + + return !error; +} + +static void +append_required_components_helper (const char *component, + const char *app_path, + gpointer user_data) +{ + GsmManager *manager = user_data; + + if (app_path == NULL) + g_warning ("Unable to find required component '%s'", component); + else + gsm_manager_add_required_app (manager, app_path); +} + + +static void +load_standard_apps (GsmManager *manager, + GKeyFile *keyfile) +{ + g_debug ("fill: *** Adding required components"); + handle_required_components (keyfile, + append_required_components_helper, + manager); + g_debug ("fill: *** Done adding required components"); + + if (!gsm_manager_get_failsafe (manager)) { + char **autostart_dirs; + int i; + + autostart_dirs = gsm_util_get_autostart_dirs (); + + for (i = 0; autostart_dirs[i]; i++) { + gsm_manager_add_autostart_apps_from_dir (manager, + autostart_dirs[i]); + } + + g_strfreev (autostart_dirs); } } @@ -60,6 +141,8 @@ static GKeyFile * get_session_keyfile_if_valid (const char *path) { GKeyFile *keyfile; + gsize len; + char **list; g_debug ("fill: *** Looking if %s is a valid session file", path); @@ -75,6 +158,15 @@ get_session_keyfile_if_valid (const char *path) goto error; } + list = g_key_file_get_string_list (keyfile, + GSM_KEYFILE_SESSION_GROUP, + GSM_KEYFILE_REQUIRED_COMPONENTS_KEY, + &len, NULL); + if (list) + g_strfreev (list); + if (len == 0) + g_warning ("Session '%s': no component in the session.", path); + return keyfile; error: @@ -131,11 +223,68 @@ find_valid_session_keyfile (const char *session) } static GKeyFile * -get_session_keyfile (const char *session) +get_session_keyfile (const char *session, + char **actual_session, + gboolean *is_fallback) { GKeyFile *keyfile; + gboolean session_runnable; + char *value; + GError *error = NULL; + + *actual_session = NULL; + g_debug ("fill: *** Getting session '%s'", session); + keyfile = find_valid_session_keyfile (session); + + if (!keyfile) + return NULL; + + session_runnable = TRUE; + + value = g_key_file_get_string (keyfile, + GSM_KEYFILE_SESSION_GROUP, GSM_KEYFILE_RUNNABLE_KEY, + NULL); + if (!IS_STRING_EMPTY (value)) { + g_debug ("fill: *** Launching helper '%s' to know if session is runnable", value); + session_runnable = gsm_process_helper (value, GSM_RUNNABLE_HELPER_TIMEOUT, &error); + if (!session_runnable) { + g_warning ("Session '%s' runnable check failed: %s", session, + error->message); + g_clear_error (&error); + } + } + g_free (value); + + if (session_runnable) { + session_runnable = check_required (keyfile); + } + + if (session_runnable) { + *actual_session = g_strdup (session); + if (is_fallback) + *is_fallback = FALSE; + return keyfile; + } + + g_debug ("fill: *** Session is not runnable"); + + /* We can't run this session, so try to use the fallback */ + value = g_key_file_get_string (keyfile, + GSM_KEYFILE_SESSION_GROUP, GSM_KEYFILE_FALLBACK_KEY, + NULL); + + g_key_file_free (keyfile); + keyfile = NULL; + + if (!IS_STRING_EMPTY (value)) { + if (is_fallback) + *is_fallback = TRUE; + keyfile = get_session_keyfile (value, actual_session, NULL); + } + g_free (value); + return keyfile; } @@ -144,13 +293,17 @@ gsm_session_fill (GsmManager *manager, const char *session) { GKeyFile *keyfile; + gboolean is_fallback; + char *actual_session; - keyfile = get_session_keyfile (session); + keyfile = get_session_keyfile (session, &actual_session, &is_fallback); if (!keyfile) return FALSE; - _gsm_manager_set_active_session (manager, session); + _gsm_manager_set_active_session (manager, actual_session, is_fallback); + + g_free (actual_session); load_standard_apps (manager, keyfile); diff --git a/gnome-session/gsm-shell-extensions.c b/gnome-session/gsm-shell-extensions.c new file mode 100644 index 00000000..064babe4 --- /dev/null +++ b/gnome-session/gsm-shell-extensions.c @@ -0,0 +1,197 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2011 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, 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 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 . + * + * Authors: + * Jasper St. Pierre + */ + +#include + +#include +#include +#include + +#include "gsm-shell-extensions.h" + +#define SHELL_SCHEMA "org.gnome.shell" +#define DISABLE_EXTENSIONS_KEY "disable-user-extensions" + +struct _GsmShellExtensionsPrivate +{ + GSettings *settings; + guint num_extensions; +}; + +G_DEFINE_TYPE_WITH_CODE (GsmShellExtensions, gsm_shell_extensions, G_TYPE_OBJECT, + G_ADD_PRIVATE (GsmShellExtensions)); + +/** + * gsm_shell_extensions_finalize: + * @object: (in): A #GsmShellExtensions. + * + * Finalizer for a #GsmShellExtensions instance. Frees any resources held by + * the instance. + */ +static void +gsm_shell_extensions_finalize (GObject *object) +{ + GsmShellExtensions *extensions = GSM_SHELL_EXTENSIONS (object); + GsmShellExtensionsPrivate *priv = extensions->priv; + + g_clear_object (&priv->settings); + + G_OBJECT_CLASS (gsm_shell_extensions_parent_class)->finalize (object); +} + +/** + * gsm_shell_extensions_class_init: + * @klass: (in): A #GsmShellExtensionsClass. + * + * Initializes the #GsmShellExtensionsClass and prepares the vtable. + */ +static void +gsm_shell_extensions_class_init (GsmShellExtensionsClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->finalize = gsm_shell_extensions_finalize; +} + +static void +gsm_shell_extensions_scan_dir (GsmShellExtensions *self, + GFile *dir) +{ + GFileEnumerator *enumerator; + GFileInfo *info; + JsonParser *metadata_parser; + + metadata_parser = json_parser_new (); + + enumerator = g_file_enumerate_children (dir, + "standard::*", + G_FILE_QUERY_INFO_NONE, + NULL, + NULL); + + if (enumerator == NULL) + return; + + while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL)) != NULL) + { + gchar *metadata_filename; + const gchar *metadata_uuid; + gchar *dir_uuid; + JsonObject *metadata_root; + + dir_uuid = (char *) g_file_info_get_name (info); + + metadata_filename = g_build_filename (g_file_get_path (dir), + dir_uuid, + "metadata.json", + NULL); + + if (!json_parser_load_from_file (metadata_parser, metadata_filename, NULL)) + continue; + + g_free (metadata_filename); + + metadata_root = json_node_get_object (json_parser_get_root (metadata_parser)); + + metadata_uuid = json_object_get_string_member (metadata_root, "uuid"); + if (!g_str_equal (metadata_uuid, dir_uuid)) + { + g_warning ("Extension with dirname '%s' does not match metadata's UUID of '%s'. Skipping.", + dir_uuid, metadata_uuid); + continue; + } + + self->priv->num_extensions++; + } +} + +static void +gsm_shell_extensions_scan (GsmShellExtensions *self) +{ + gchar *dirname; + GFile *dir; + const gchar * const * system_data_dirs; + int i; + + /* User data dir first. */ + dirname = g_build_filename (g_get_user_data_dir (), "gnome-shell", "extensions", NULL); + dir = g_file_new_for_path (dirname); + g_free (dirname); + + gsm_shell_extensions_scan_dir (self, dir); + g_object_unref (dir); + + system_data_dirs = g_get_system_data_dirs (); + for (i = 0; system_data_dirs[i]; i++) + { + dirname = g_build_filename (system_data_dirs[i], "gnome-shell", "extensions", NULL); + dir = g_file_new_for_path (dirname); + g_free (dirname); + + gsm_shell_extensions_scan_dir (self, dir); + g_object_unref (dir); + } +} + +/** + * gsm_shell_extensions_init: + * @self: (in): A #GsmShellExtensions. + * + * Initializes the newly created #GsmShellExtensions instance. + */ +static void +gsm_shell_extensions_init (GsmShellExtensions *self) +{ + GSettingsSchemaSource *source; + GSettingsSchema *schema; + + self->priv = gsm_shell_extensions_get_instance_private (self); + + source = g_settings_schema_source_get_default (); + schema = g_settings_schema_source_lookup (source, SHELL_SCHEMA, TRUE); + + if (schema != NULL) + { + self->priv->settings = g_settings_new_full (schema, NULL, NULL); + g_settings_schema_unref (schema); + } + + if (self->priv->settings != NULL) + gsm_shell_extensions_scan (self); +} + +gboolean +gsm_shell_extensions_disable_all (GsmShellExtensions *self) +{ + return g_settings_set_boolean (self->priv->settings, + DISABLE_EXTENSIONS_KEY, + TRUE); +} + +guint +gsm_shell_extensions_n_extensions (GsmShellExtensions *self) +{ + if (self->priv->settings == NULL) + return 0; + + return self->priv->num_extensions; +} diff --git a/gnome-session/gsm-shell-extensions.h b/gnome-session/gsm-shell-extensions.h new file mode 100644 index 00000000..a3b3d063 --- /dev/null +++ b/gnome-session/gsm-shell-extensions.h @@ -0,0 +1,61 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2011 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, 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 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 . + * + * Authors: + * Jasper St. Pierre + */ + +#ifndef __GSM_SHELL_EXTENSIONS_H +#define __GSM_SHELL_EXTENSIONS_H + +#include + +G_BEGIN_DECLS + +#define GSM_TYPE_SHELL_EXTENSIONS (gsm_shell_extensions_get_type ()) +#define GSM_SHELL_EXTENSIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSM_TYPE_SHELL_EXTENSIONS, GsmShellExtensions)) +#define GSM_SHELL_EXTENSIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSM_TYPE_SHELL_EXTENSIONS, GsmShellExtensionsClass)) +#define GSM_IS_SHELL_EXTENSIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSM_TYPE_SHELL_EXTENSIONS)) +#define GSM_IS_SHELL_EXTENSIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSM_TYPE_SHELL_EXTENSIONS)) +#define GSM_SHELL_EXTENSIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSM_TYPE_SHELL_EXTENSIONS, GsmShellExtensionsClass)) + +typedef struct _GsmShellExtensions GsmShellExtensions; +typedef struct _GsmShellExtensionsClass GsmShellExtensionsClass; +typedef struct _GsmShellExtensionsPrivate GsmShellExtensionsPrivate; + +struct _GsmShellExtensions +{ + GObject parent; + + /*< private >*/ + GsmShellExtensionsPrivate *priv; +}; + +struct _GsmShellExtensionsClass +{ + GObjectClass parent_class; +}; + +GType gsm_shell_extensions_get_type (void) G_GNUC_CONST; + +gboolean gsm_shell_extensions_disable_all (GsmShellExtensions *self); + +guint gsm_shell_extensions_n_extensions (GsmShellExtensions *self); + +G_END_DECLS + +#endif /* __GSM_SHELL_EXTENSIONS_H */ diff --git a/gnome-session/gsm-system.c b/gnome-session/gsm-system.c index dc49a1af..be224244 100644 --- a/gnome-session/gsm-system.c +++ b/gnome-session/gsm-system.c @@ -73,6 +73,7 @@ typedef GObject GsmSystemNull; typedef GObjectClass GsmSystemNullClass; static void do_nothing (void) { } +static gboolean return_true (void) { return TRUE; } static gboolean return_false (void) { return FALSE; } static void @@ -90,9 +91,11 @@ gsm_system_null_init_iface (GsmSystemInterface *iface) iface->suspend = (void *) do_nothing; iface->hibernate = (void *) do_nothing; iface->set_session_idle = (void *) do_nothing; + iface->is_login_session = (void *) return_true; iface->set_inhibitors = (void *) do_nothing; iface->prepare_shutdown = (void *) do_nothing; iface->complete_shutdown = (void *) do_nothing; + iface->is_last_session_for_user = (void *) return_false; } static void @@ -213,6 +216,18 @@ gsm_system_set_inhibitors (GsmSystem *system, GSM_SYSTEM_GET_IFACE (system)->set_inhibitors (system, flags); } +gboolean +gsm_system_is_login_session (GsmSystem *system) +{ + return GSM_SYSTEM_GET_IFACE (system)->is_login_session (system); +} + +gboolean +gsm_system_is_last_session_for_user (GsmSystem *system) +{ + return GSM_SYSTEM_GET_IFACE (system)->is_last_session_for_user (system); +} + /** * gsm_system_is_active: * diff --git a/gnome-session/gsm-system.h b/gnome-session/gsm-system.h index 2f137422..438434cc 100644 --- a/gnome-session/gsm-system.h +++ b/gnome-session/gsm-system.h @@ -64,11 +64,13 @@ struct _GsmSystemInterface void (* hibernate) (GsmSystem *system); void (* set_session_idle) (GsmSystem *system, gboolean is_idle); + gboolean (* is_login_session) (GsmSystem *system); void (* set_inhibitors) (GsmSystem *system, GsmInhibitorFlag flags); void (* prepare_shutdown) (GsmSystem *system, gboolean restart); void (* complete_shutdown)(GsmSystem *system); + gboolean (* is_last_session_for_user) (GsmSystem *system); }; enum _GsmSystemError { @@ -108,6 +110,10 @@ void gsm_system_hibernate (GsmSystem *system); void gsm_system_set_session_idle (GsmSystem *system, gboolean is_idle); +gboolean gsm_system_is_login_session (GsmSystem *system); + +gboolean gsm_system_is_last_session_for_user (GsmSystem *system); + gboolean gsm_system_is_active (GsmSystem *system); void gsm_system_set_inhibitors (GsmSystem *system, diff --git a/gnome-session/gsm-systemd.c b/gnome-session/gsm-systemd.c index 6f5a03d8..9271e7fe 100644 --- a/gnome-session/gsm-systemd.c +++ b/gnome-session/gsm-systemd.c @@ -707,6 +707,31 @@ gsm_systemd_can_stop (GsmSystem *system) return can_stop; } +static gboolean +gsm_systemd_is_login_session (GsmSystem *system) +{ + GsmSystemd *manager = GSM_SYSTEMD (system); + int res; + gboolean ret; + gchar *session_class = NULL; + + ret = FALSE; + + if (manager->priv->session_id == NULL) { + return ret; + } + + res = sd_session_get_class (manager->priv->session_id, &session_class); + if (res < 0) { + g_warning ("Could not get session class: %s", strerror (-res)); + return FALSE; + } + ret = (g_strcmp0 (session_class, "greeter") == 0); + free (session_class); + + return ret; +} + static gboolean gsm_systemd_can_suspend (GsmSystem *system) { @@ -1032,6 +1057,66 @@ gsm_systemd_complete_shutdown (GsmSystem *system) drop_delay_inhibitor (systemd); } +static gboolean +gsm_systemd_is_last_session_for_user (GsmSystem *system) +{ + char **sessions = NULL; + char *session = NULL; + gboolean is_last_session; + int ret, i; + + if (!gsm_systemd_find_session (&session)) { + return FALSE; + } + + ret = sd_uid_get_sessions (getuid (), FALSE, &sessions); + + if (ret <= 0) { + free (session); + return FALSE; + } + + is_last_session = TRUE; + for (i = 0; sessions[i]; i++) { + char *state = NULL; + char *type = NULL; + + if (g_strcmp0 (sessions[i], session) == 0) + continue; + + ret = sd_session_get_state (sessions[i], &state); + + if (ret != 0) + continue; + + if (g_strcmp0 (state, "closing") == 0) { + free (state); + continue; + } + free (state); + + ret = sd_session_get_type (sessions[i], &type); + + if (ret != 0) + continue; + + if (g_strcmp0 (type, "x11") != 0 && + g_strcmp0 (type, "wayland") != 0) { + free (type); + continue; + } + + is_last_session = FALSE; + } + + for (i = 0; sessions[i]; i++) + free (sessions[i]); + free (sessions); + free (session); + + return is_last_session; +} + static void gsm_systemd_system_init (GsmSystemInterface *iface) { @@ -1047,9 +1132,11 @@ gsm_systemd_system_init (GsmSystemInterface *iface) iface->suspend = gsm_systemd_suspend; iface->hibernate = gsm_systemd_hibernate; iface->set_session_idle = gsm_systemd_set_session_idle; + iface->is_login_session = gsm_systemd_is_login_session; iface->set_inhibitors = gsm_systemd_set_inhibitors; iface->prepare_shutdown = gsm_systemd_prepare_shutdown; iface->complete_shutdown = gsm_systemd_complete_shutdown; + iface->is_last_session_for_user = gsm_systemd_is_last_session_for_user; } GsmSystemd * diff --git a/gnome-session/gsm-util.c b/gnome-session/gsm-util.c index 9dd3e8e1..40a08a35 100644 --- a/gnome-session/gsm-util.c +++ b/gnome-session/gsm-util.c @@ -83,6 +83,116 @@ static const char * const variable_unsetlist[] = { NULL }; +char * +gsm_util_find_desktop_file_for_app_name (const char *name, + gboolean autostart_first) +{ + char *app_path; + char **app_dirs; + GKeyFile *key_file; + char *desktop_file; + int i; + + app_path = NULL; + + app_dirs = gsm_util_get_desktop_dirs (autostart_first); + + key_file = g_key_file_new (); + + desktop_file = g_strdup_printf ("%s.desktop", name); + + g_debug ("GsmUtil: Looking for file '%s'", desktop_file); + + for (i = 0; app_dirs[i] != NULL; i++) { + g_debug ("GsmUtil: Looking in '%s'", app_dirs[i]); + } + + g_key_file_load_from_dirs (key_file, + desktop_file, + (const char **) app_dirs, + &app_path, + G_KEY_FILE_NONE, + NULL); + + if (app_path != NULL) { + g_debug ("GsmUtil: found in XDG dirs: '%s'", app_path); + } + + /* look for gnome vendor prefix */ + if (app_path == NULL) { + g_free (desktop_file); + desktop_file = g_strdup_printf ("gnome-%s.desktop", name); + + g_key_file_load_from_dirs (key_file, + desktop_file, + (const char **) app_dirs, + &app_path, + G_KEY_FILE_NONE, + NULL); + if (app_path != NULL) { + g_debug ("GsmUtil: found in XDG dirs: '%s'", app_path); + } + } + + g_free (desktop_file); + g_key_file_free (key_file); + + g_strfreev (app_dirs); + + return app_path; +} + +static char ** autostart_dirs; + +void +gsm_util_set_autostart_dirs (char ** dirs) +{ + autostart_dirs = g_strdupv (dirs); +} + +static char ** +gsm_util_get_standard_autostart_dirs (void) +{ + GPtrArray *dirs; + const char * const *system_config_dirs; + const char * const *system_data_dirs; + int i; + + dirs = g_ptr_array_new (); + + g_ptr_array_add (dirs, + g_build_filename (g_get_user_config_dir (), + "autostart", NULL)); + + system_data_dirs = g_get_system_data_dirs (); + for (i = 0; system_data_dirs[i]; i++) { + g_ptr_array_add (dirs, + g_build_filename (system_data_dirs[i], + "gnome", "autostart", NULL)); + } + + system_config_dirs = g_get_system_config_dirs (); + for (i = 0; system_config_dirs[i]; i++) { + g_ptr_array_add (dirs, + g_build_filename (system_config_dirs[i], + "autostart", NULL)); + } + + g_ptr_array_add (dirs, NULL); + + return (char **) g_ptr_array_free (dirs, FALSE); +} + +char ** +gsm_util_get_autostart_dirs () +{ + if (autostart_dirs) { + return g_strdupv ((char **)autostart_dirs); + } + + return gsm_util_get_standard_autostart_dirs (); +} + char ** gsm_util_get_app_dirs () { @@ -110,6 +220,71 @@ gsm_util_get_app_dirs () return (char **) g_ptr_array_free (dirs, FALSE); } +char ** +gsm_util_get_desktop_dirs (gboolean autostart_first) +{ + char **apps; + char **autostart; + char **standard_autostart; + char **result; + int size; + int i; + + apps = gsm_util_get_app_dirs (); + autostart = gsm_util_get_autostart_dirs (); + + /* Still, check the standard autostart dirs for things like fulfilling session reqs, + * if using a non-standard autostart dir for autostarting */ + if (autostart_dirs != NULL) + standard_autostart = gsm_util_get_standard_autostart_dirs (); + else + standard_autostart = NULL; + + size = 0; + for (i = 0; apps[i] != NULL; i++) { size++; } + for (i = 0; autostart[i] != NULL; i++) { size++; } + if (standard_autostart != NULL) + for (i = 0; standard_autostart[i] != NULL; i++) { size++; } + + result = g_new (char *, size + 1); /* including last NULL */ + + size = 0; + + if (autostart_first) { + for (i = 0; autostart[i] != NULL; i++, size++) { + result[size] = autostart[i]; + } + if (standard_autostart != NULL) { + for (i = 0; standard_autostart[i] != NULL; i++, size++) { + result[size] = standard_autostart[i]; + } + } + for (i = 0; apps[i] != NULL; i++, size++) { + result[size] = apps[i]; + } + } else { + for (i = 0; apps[i] != NULL; i++, size++) { + result[size] = apps[i]; + } + if (standard_autostart != NULL) { + for (i = 0; standard_autostart[i] != NULL; i++, size++) { + result[size] = standard_autostart[i]; + } + } + for (i = 0; autostart[i] != NULL; i++, size++) { + result[size] = autostart[i]; + } + } + + g_free (apps); + g_free (autostart); + g_free (standard_autostart); + + result[size] = NULL; + + return result; +} + gboolean gsm_util_text_is_blank (const char *str) { @@ -176,6 +351,63 @@ gsm_util_init_error (gboolean fatal, } } +/** + * gsm_util_generate_startup_id: + * + * Generates a new SM client ID. + * + * Return value: an SM client ID. + **/ +char * +gsm_util_generate_startup_id (void) +{ + static int sequence = -1; + static guint rand1 = 0; + static guint rand2 = 0; + static pid_t pid = 0; + struct timeval tv; + + /* The XSMP spec defines the ID as: + * + * Version: "1" + * Address type and address: + * "1" + an IPv4 address as 8 hex digits + * "2" + a DECNET address as 12 hex digits + * "6" + an IPv6 address as 32 hex digits + * Time stamp: milliseconds since UNIX epoch as 13 decimal digits + * Process-ID type and process-ID: + * "1" + POSIX PID as 10 decimal digits + * Sequence number as 4 decimal digits + * + * XSMP client IDs are supposed to be globally unique: if + * SmsGenerateClientID() is unable to determine a network + * address for the machine, it gives up and returns %NULL. + * GNOME and KDE have traditionally used a fourth address + * format in this case: + * "0" + 16 random hex digits + * + * We don't even bother trying SmsGenerateClientID(), since the + * user's IP address is probably "192.168.1.*" anyway, so a random + * number is actually more likely to be globally unique. + */ + + if (!rand1) { + rand1 = g_random_int (); + rand2 = g_random_int (); + pid = getpid (); + } + + sequence = (sequence + 1) % 10000; + gettimeofday (&tv, NULL); + return g_strdup_printf ("10%.04x%.04x%.10lu%.3u%.10lu%.4d", + rand1, + rand2, + (unsigned long) tv.tv_sec, + (unsigned) tv.tv_usec, + (unsigned long) pid, + sequence); +} + static gboolean gsm_util_update_activation_environment (const char *variable, const char *value, @@ -439,6 +671,139 @@ gsm_util_update_user_environment (const char *variable, return environment_updated; } +gboolean +gsm_util_systemd_unit_is_active (const char *unit, + GError **error) +{ + g_autoptr(GDBusProxy) proxy = NULL; + g_autoptr(GVariant) result = NULL; + g_autofree gchar *object_path = NULL; + g_autofree gchar *active_state = NULL; + g_autoptr(GDBusProxy) unit_proxy = NULL; + + proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + NULL, + error); + + if (proxy == NULL) { + return FALSE; + } + + result = g_dbus_proxy_call_sync (proxy, + "GetUnit", + g_variant_new ("(s)", unit), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + error); + + if (result == NULL) { + if (error && *error) { + g_autofree char *remote_error = g_dbus_error_get_remote_error (*error); + + if (g_strcmp0 (remote_error, "org.freedesktop.systemd1.NoSuchUnit") == 0) { + g_clear_error (error); + } + } + return FALSE; + } + + g_variant_get (result, "(o)", &object_path); + g_clear_pointer (&result, g_variant_unref); + + unit_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.freedesktop.systemd1", + object_path, + "org.freedesktop.systemd1.Unit", + NULL, + error); + + if (unit_proxy == NULL) { + return FALSE; + } + + result = g_dbus_proxy_get_cached_property (unit_proxy, "ActiveState"); + + if (result == NULL) { + g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_PROPERTY, "Error getting ActiveState property"); + return FALSE; + } + + g_variant_get (result, "s", &active_state); + + return g_str_equal (active_state, "active"); +} + +gboolean +gsm_util_start_systemd_unit (const char *unit, + const char *mode, + GError **error) +{ + g_autoptr(GDBusConnection) connection = NULL; + g_autoptr(GVariant) reply = NULL; + GError *bus_error = NULL; + + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error); + + if (connection == NULL) + return FALSE; + + reply = g_dbus_connection_call_sync (connection, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartUnit", + g_variant_new ("(ss)", + unit, mode), + NULL, + G_DBUS_CALL_FLAGS_NO_AUTO_START, + -1, NULL, &bus_error); + + if (bus_error != NULL) { + g_propagate_error (error, bus_error); + return FALSE; + } + + return TRUE; +} + +gboolean +gsm_util_systemd_reset_failed (GError **error) +{ + g_autoptr(GDBusConnection) connection = NULL; + g_autoptr(GVariant) reply = NULL; + GError *bus_error = NULL; + + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error); + + if (connection == NULL) + return FALSE; + + reply = g_dbus_connection_call_sync (connection, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "ResetFailed", + NULL, + NULL, + G_DBUS_CALL_FLAGS_NO_AUTO_START, + -1, NULL, &bus_error); + + if (bus_error != NULL) { + g_propagate_error (error, bus_error); + return FALSE; + } + + return TRUE; +} + void gsm_util_setenv (const char *variable, const char *value) @@ -472,8 +837,6 @@ gsm_util_setenv (const char *variable, const char * const * gsm_util_listenv (void) { - if (child_environment == NULL) - child_environment = g_listenv (); return (const char * const *) child_environment; } diff --git a/gnome-session/gsm-util.h b/gnome-session/gsm-util.h index 2cc129f9..ec1f5ce7 100644 --- a/gnome-session/gsm-util.h +++ b/gnome-session/gsm-util.h @@ -24,13 +24,23 @@ G_BEGIN_DECLS #define IS_STRING_EMPTY(x) ((x)==NULL||(x)[0]=='\0') +char * gsm_util_find_desktop_file_for_app_name (const char *app_name, + gboolean autostart_first); + gchar** gsm_util_get_app_dirs (void); +gchar** gsm_util_get_autostart_dirs (void); +void gsm_util_set_autostart_dirs (char **dirs); + +gchar ** gsm_util_get_desktop_dirs (gboolean autostart_first); + gboolean gsm_util_text_is_blank (const char *str); void gsm_util_init_error (gboolean fatal, const char *format, ...) G_GNUC_PRINTF (2, 3); +char * gsm_util_generate_startup_id (void); + void gsm_util_setenv (const char *variable, const char *value); const char * const * gsm_util_listenv (void); @@ -38,6 +48,15 @@ const char * const * gsm_util_get_variable_blacklist(void); gboolean gsm_util_export_activation_environment (GError **error); gboolean gsm_util_export_user_environment (GError **error); +gboolean gsm_util_systemd_unit_is_active (const char *unit, + GError **error); +gboolean gsm_util_start_systemd_unit (const char *unit, + const char *mode, + GError **error); +gboolean gsm_util_systemd_reset_failed (GError **error); + + +void gsm_quit (void); G_END_DECLS diff --git a/gnome-session/main.c b/gnome-session/main.c new file mode 100644 index 00000000..47abd43e --- /dev/null +++ b/gnome-session/main.c @@ -0,0 +1,635 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2006 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 + * 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 +#include + +#include +#include +#include +#include +#include + +#include "gdm-log.h" + +#include "gsm-util.h" +#include "gsm-manager.h" +#include "gsm-session-fill.h" +#include "gsm-store.h" +#include "gsm-system.h" +#include "gsm-fail-whale.h" + +#include + +#define GSM_DBUS_NAME "org.gnome.SessionManager" + +static gboolean systemd_service = FALSE; +static gboolean failsafe = FALSE; +static gboolean show_version = FALSE; +static gboolean debug = FALSE; +static gboolean please_fail = FALSE; +static gboolean disable_acceleration_check = FALSE; +static const char *session_name = NULL; +static GsmManager *manager = NULL; +static char *gl_renderer = NULL; + +static GMainLoop *loop; + +void +gsm_quit (void) +{ + g_main_loop_quit (loop); +} + +static void +gsm_main (void) +{ + if (loop == NULL) + loop = g_main_loop_new (NULL, TRUE); + + g_main_loop_run (loop); +} + +static void +on_name_lost (GDBusConnection *connection, + const char *name, + gpointer data) +{ + if (connection == NULL) { + if (gsm_manager_get_dbus_disconnected (manager)) + gsm_quit (); + else { + g_warning ("Lost name on bus: %s", name); + gsm_fail_whale_dialog_we_failed (TRUE, TRUE, NULL); + } + } else { + g_debug ("Calling name lost callback function"); + + /* + * When the signal handler gets a shutdown signal, it calls + * this function to inform GsmManager to not restart + * applications in the off chance a handler is already queued + * to dispatch following the below call to gtk_main_quit. + */ + gsm_manager_set_phase (manager, GSM_MANAGER_PHASE_EXIT); + + gsm_quit (); + } +} + +static gboolean +term_or_int_signal_cb (gpointer data) +{ + g_autoptr(GError) error = NULL; + GsmManager *manager = (GsmManager *)data; + + /* let the fatal signals interrupt us */ + g_debug ("Caught SIGINT/SIGTERM, shutting down normally."); + + if (!gsm_manager_logout (manager, GSM_MANAGER_LOGOUT_MODE_FORCE, &error)) { + if (g_error_matches (error, GSM_MANAGER_ERROR, GSM_MANAGER_ERROR_NOT_IN_RUNNING)) { + gsm_quit (); + return FALSE; + } + + g_critical ("Failed to log out: %s", error->message); + } + + return FALSE; +} + +static gboolean +sigusr2_cb (gpointer data) +{ + g_debug ("-------- MARK --------"); + return TRUE; +} + +static gboolean +sigusr1_cb (gpointer data) +{ + gdm_log_toggle_debug (); + return TRUE; +} + +static void +on_name_acquired (GDBusConnection *connection, + const char *name, + gpointer data) +{ + gsm_manager_start (manager); +} + +static void +create_manager (void) +{ + GsmStore *client_store; + + client_store = gsm_store_new (); + manager = gsm_manager_new (client_store, failsafe, systemd_service); + g_object_unref (client_store); + + g_unix_signal_add (SIGTERM, term_or_int_signal_cb, manager); + g_unix_signal_add (SIGINT, term_or_int_signal_cb, manager); + g_unix_signal_add (SIGUSR1, sigusr1_cb, manager); + g_unix_signal_add (SIGUSR2, sigusr2_cb, manager); + + if (IS_STRING_EMPTY (session_name)) { + session_name = _gsm_manager_get_default_session (manager); + } + + if (!gsm_session_fill (manager, session_name)) { + gsm_fail_whale_dialog_we_failed (FALSE, TRUE, NULL); + } +} + +static void +on_bus_acquired (GDBusConnection *connection, + const char *name, + gpointer data) +{ + create_manager (); +} + +static guint +acquire_name (void) +{ + return g_bus_own_name (G_BUS_TYPE_SESSION, + GSM_DBUS_NAME, + G_BUS_NAME_OWNER_FLAGS_NONE, + on_bus_acquired, + on_name_acquired, + on_name_lost, + NULL, NULL); +} + +static void +initialize_gio (void) +{ + char *disable_fuse = NULL; + char *use_vfs = NULL; + + disable_fuse = g_strdup (g_getenv ("GVFS_DISABLE_FUSE")); + use_vfs = g_strdup (g_getenv ("GIO_USE_VFS")); + + g_setenv ("GVFS_DISABLE_FUSE", "1", TRUE); + g_setenv ("GIO_USE_VFS", "local", TRUE); + g_vfs_get_default (); + + if (use_vfs) { + g_setenv ("GIO_USE_VFS", use_vfs, TRUE); + g_free (use_vfs); + } else { + g_unsetenv ("GIO_USE_VFS"); + } + + if (disable_fuse) { + g_setenv ("GVFS_DISABLE_FUSE", disable_fuse, TRUE); + g_free (disable_fuse); + } else { + g_unsetenv ("GVFS_DISABLE_FUSE"); + } +} + +static gboolean +leader_term_or_int_signal_cb (gpointer data) +{ + gint fifo_fd = GPOINTER_TO_INT (data); + + g_debug ("Session termination requested"); + + /* Start a shutdown explicitly. */ + gsm_util_start_systemd_unit ("gnome-session-shutdown.target", "replace-irreversibly", NULL); + + if (fifo_fd >= 0) { + int res; + + /* If we have a fifo, try to signal the other side. */ + res = write (fifo_fd, "S", 1); + if (res < 0) { + g_warning ("Error signaling shutdown to monitoring process: %m"); + gsm_quit (); + } + } else { + /* Otherwise quit immediately as we cannot wait on systemd */ + gsm_quit (); + } + + return G_SOURCE_REMOVE; +} + +static void +graphical_session_pre_state_changed_cb (GDBusProxy *proxy, + GVariant *changed_properties) +{ + const char *state; + g_autoptr (GVariant) value = NULL; + + value = g_variant_lookup_value (changed_properties, "ActiveState", NULL); + + if (value == NULL) + return; + + g_variant_get (value, "&s", &state); + if (g_strcmp0 (state, "inactive") == 0) { + g_debug ("Session services now inactive, quitting"); + gsm_quit (); + return; + } +} + +static gboolean +monitor_hangup_cb (int fd, + GIOCondition condition, + gpointer user_data) +{ + g_autoptr (GDBusConnection) connection = NULL; + g_autoptr (GVariant) unit = NULL; + g_autoptr (GVariant) value = NULL; + g_autoptr (GError) error = NULL; + GDBusProxy *proxy = NULL; + const char *unit_path = NULL; + + g_debug ("Services have begun stopping, waiting for them to finish stopping"); + + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + + if (!connection) { + g_debug ("Could not get bus connection: %s", error->message); + gsm_quit (); + return G_SOURCE_REMOVE; + } + + unit = g_dbus_connection_call_sync (connection, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GetUnit", + g_variant_new ("(s)", "graphical-session-pre.target"), + G_VARIANT_TYPE ("(o)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + if (!unit) { + g_debug ("Could not get unit for graphical-session-pre.target: %s", error->message); + gsm_quit (); + return G_SOURCE_REMOVE; + } + + g_variant_get (unit, "(&o)", &unit_path); + + proxy = g_dbus_proxy_new_sync (connection, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.freedesktop.systemd1", + unit_path, + "org.freedesktop.systemd1.Unit", + NULL, + &error); + if (!proxy) { + g_debug ("Could not get proxy for graphical-session-pre.target unit: %s", error->message); + gsm_quit (); + return G_SOURCE_REMOVE; + } + + value = g_dbus_proxy_get_cached_property (proxy, "ActiveState"); + + if (value) { + const char *state; + + g_variant_get (value, "&s", &state); + + if (g_strcmp0 (state, "inactive") == 0) { + g_debug ("State of graphical-session-pre.target unit already inactive quitting"); + gsm_quit (); + return G_SOURCE_REMOVE; + } + g_debug ("State of graphical-session-pre.target unit is '%s', waiting for it to go inactive", state); + } else { + g_debug ("State of graphical-session-pre.target unit is unknown, waiting for it to go inactive"); + } + g_signal_connect (proxy, + "g-properties-changed", + G_CALLBACK (graphical_session_pre_state_changed_cb), + NULL); + + return G_SOURCE_REMOVE; +} + +/** + * systemd_leader_run: + * + * This is the session leader when running under systemd, i.e. it is the only + * process that is *not* managed by the systemd user instance, this process + * is the one executed by the GDM helpers and is part of the session scope in + * the system systemd instance. + * + * This process works together with a service running in the user systemd + * instance (currently gnome-session-ctl@monitor.service): + * + * - It needs to signal shutdown to the user session when receiving SIGTERM + * + * - It needs to quit just after the user session is done + * + * - The monitor instance needs to know if this process was killed + * + * All this is achieved by opening a named fifo in a well known location. + * If this process receives SIGTERM or SIGINT then it will write a single byte + * causing the monitor service to signal STOPPING=1 to systemd, triggering a + * clean shutdown, solving the first item. The other two items are solved by + * waiting for EOF/HUP on both sides and quitting immediately when receiving + * that signal. + * + * As an example, a shutdown might look as follows: + * + * - session-X.scope for user is stopped + * - Leader process receive SIGTERM + * - Leader sends single byte + * - Monitor process receives byte and signals STOPPING=1 + * - Systemd user instance starts session teardown + * - Session is torn down, last job run is stopping monitor process (SIGTERM) + * - Monitor process quits, closing FD in the process + * - Leader process receives HUP and quits + * - GDM shuts down its processes in the users scope + * + * The result is that the session is stopped cleanly. + */ +static void +systemd_leader_run(void) +{ + g_autofree char *fifo_name = NULL; + int res; + int fifo_fd; + + fifo_name = g_strdup_printf ("%s/gnome-session-leader-fifo", + g_get_user_runtime_dir ()); + res = mkfifo (fifo_name, 0666); + if (res < 0 && errno != EEXIST) + g_warning ("Error creating FIFO: %m"); + + fifo_fd = g_open (fifo_name, O_WRONLY | O_CLOEXEC, 0666); + if (fifo_fd >= 0) { + struct stat buf; + + res = fstat (fifo_fd, &buf); + if (res < 0) { + g_warning ("Unable to watch systemd session: fstat failed with %m"); + close (fifo_fd); + fifo_fd = -1; + } else if (!(buf.st_mode & S_IFIFO)) { + g_warning ("Unable to watch systemd session: FD is not a FIFO"); + close (fifo_fd); + fifo_fd = -1; + } else { + g_unix_fd_add (fifo_fd, G_IO_HUP, (GUnixFDSourceFunc) monitor_hangup_cb, NULL); + } + } else { + g_warning ("Unable to watch systemd session: Opening FIFO failed with %m"); + } + + g_unix_signal_add (SIGHUP, leader_term_or_int_signal_cb, GINT_TO_POINTER (fifo_fd)); + g_unix_signal_add (SIGTERM, leader_term_or_int_signal_cb, GINT_TO_POINTER (fifo_fd)); + g_unix_signal_add (SIGINT, leader_term_or_int_signal_cb, GINT_TO_POINTER (fifo_fd)); + + /* Sleep until we receive HUP or are killed. */ + gsm_main (); + exit(0); +} + +int +main (int argc, char **argv) +{ + GError *error = NULL; + static char **override_autostart_dirs = NULL; + static char *opt_session_name = NULL; + const char *debug_string = NULL; + const char *env_override_autostart_dirs = NULL; + g_auto(GStrv) env_override_autostart_dirs_v = NULL; + guint name_owner_id; + GOptionContext *options; + static GOptionEntry entries[] = { + { "systemd-service", 0, 0, G_OPTION_ARG_NONE, &systemd_service, N_("Running as systemd service"), NULL }, + { "autostart", 'a', 0, G_OPTION_ARG_STRING_ARRAY, &override_autostart_dirs, N_("Override standard autostart directories"), N_("AUTOSTART_DIR") }, + { "session", 0, 0, G_OPTION_ARG_STRING, &opt_session_name, N_("Session to use"), N_("SESSION_NAME") }, + { "debug", 0, 0, G_OPTION_ARG_NONE, &debug, N_("Enable debugging code"), NULL }, + { "failsafe", 'f', 0, G_OPTION_ARG_NONE, &failsafe, N_("Do not load user-specified applications"), NULL }, + { "version", 0, 0, G_OPTION_ARG_NONE, &show_version, N_("Version of this application"), NULL }, + /* Translators: the 'fail whale' is the black dialog we show when something goes seriously wrong */ + { "whale", 0, 0, G_OPTION_ARG_NONE, &please_fail, N_("Show the fail whale dialog for testing"), NULL }, + { "disable-acceleration-check", 0, 0, G_OPTION_ARG_NONE, &disable_acceleration_check, N_("This option is ignored"), NULL }, + { NULL, 0, 0, 0, NULL, NULL, NULL } + }; + + /* Make sure that we have a session bus */ + if (!g_getenv ("DBUS_SESSION_BUS_ADDRESS")) + g_error ("No session bus running! Cannot continue"); + + /* From 3.14 GDM sets XDG_CURRENT_DESKTOP. For compatibility with + * older versions of GDM, other display managers, and startx, + * set a fallback value if we don't find it set. + */ + if (g_getenv ("XDG_CURRENT_DESKTOP") == NULL) { + g_setenv("XDG_CURRENT_DESKTOP", "GNOME", TRUE); + gsm_util_setenv ("XDG_CURRENT_DESKTOP", "GNOME"); + } + + /* Make sure we initialize gio in a way that does not autostart any daemon */ + initialize_gio (); + + setlocale (LC_ALL, ""); + bindtextdomain (GETTEXT_PACKAGE, LOCALE_DIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + debug_string = g_getenv ("GNOME_SESSION_DEBUG"); + if (debug_string != NULL) { + debug = atoi (debug_string) == 1; + } + + error = NULL; + options = g_option_context_new (_(" — the GNOME session manager")); + g_option_context_add_main_entries (options, entries, GETTEXT_PACKAGE); + g_option_context_parse (options, &argc, &argv, &error); + if (error != NULL) { + g_warning ("%s", error->message); + exit (1); + } + + g_option_context_free (options); + + if (show_version) { + g_print ("%s %s\n", argv [0], VERSION); + exit (0); + } + + /* Rebind stdout/stderr to the journal explicitly, so that + * journald picks ups the nicer "gnome-session" as the program + * name instead of whatever shell script GDM happened to use. + */ + if (!debug) { + int journalfd; + + journalfd = sd_journal_stream_fd (PACKAGE, LOG_INFO, 0); + if (journalfd >= 0) { + dup2(journalfd, 1); + dup2(journalfd, 2); + } + } + + gdm_log_init (); + gdm_log_set_debug (debug); + + if (please_fail) { + gsm_fail_whale_dialog_we_failed (TRUE, TRUE, NULL); + gsm_main (); + exit (1); + } + + env_override_autostart_dirs = g_getenv ("GNOME_SESSION_AUTOSTART_DIR"); + + if (env_override_autostart_dirs != NULL && env_override_autostart_dirs[0] != '\0') { + env_override_autostart_dirs_v = g_strsplit (env_override_autostart_dirs, ":", 0); + gsm_util_set_autostart_dirs (env_override_autostart_dirs_v); + } else { + gsm_util_set_autostart_dirs (override_autostart_dirs); + + /* Export the override autostart dirs parameter to the environment + * in case we are running on systemd. */ + if (override_autostart_dirs) { + g_autofree char *autostart_dirs = NULL; + autostart_dirs = g_strjoinv (":", override_autostart_dirs); + g_setenv ("GNOME_SESSION_AUTOSTART_DIR", autostart_dirs, TRUE); + } + } + + gsm_util_export_activation_environment (&error); + if (error) { + g_warning ("Failed to upload environment to DBus: %s", error->message); + g_clear_error (&error); + } + + session_name = opt_session_name; + + gsm_util_export_user_environment (&error); + if (error && !g_getenv ("RUNNING_UNDER_GDM")) + g_warning ("Failed to upload environment to systemd: %s", error->message); + g_clear_error (&error); + + if (!systemd_service) { + const gchar *session_type = NULL; + g_autofree gchar *gnome_session_target = NULL; + + session_type = g_getenv ("XDG_SESSION_TYPE"); + + /* We really need to resolve the session name at this point, + * which requires talking to GSettings internally. */ + if (IS_STRING_EMPTY (session_name)) { + session_name = _gsm_manager_get_default_session (NULL); + } + + /* We don't escape the name (i.e. we leave any '-' intact). */ + gnome_session_target = g_strdup_printf ("gnome-session-%s@%s.target", session_type, session_name); + + if (!gsm_util_systemd_unit_is_active (gnome_session_target, &error)) { + if (error != NULL) { + g_warning ("Could not check if unit %s is active: %s", + gnome_session_target, error->message); + g_clear_error (&error); + } + /* Reset all failed units; we are going to start a lof ot things and + * really do not want to run into errors because units have failed + * in a previous session + */ + gsm_util_systemd_reset_failed (&error); + if (error && !g_getenv ("RUNNING_UNDER_GDM")) + g_warning ("Failed to reset failed state of units: %s", error->message); + g_clear_error (&error); + + if (gsm_util_start_systemd_unit (gnome_session_target, "fail", &error)) { + /* We started the unit, open fifo and sleep forever. */ + systemd_leader_run (); + exit (0); + } + + /* We could not start the unit, fall back. */ + if (g_getenv ("RUNNING_UNDER_GDM")) + g_message ("Falling back to non-systemd startup procedure. This is expected to happen for GDM sessions."); + else + g_warning ("Falling back to non-systemd startup procedure due to error: %s", error->message); + g_clear_error (&error); + } else { + g_warning ("Session manager already running!"); + exit (1); + } + } + + { + gchar *ibus_path; + + ibus_path = g_find_program_in_path("ibus-daemon"); + + if (ibus_path) { + const gchar *p; + p = g_getenv ("QT_IM_MODULE"); + if (!p || !*p) + p = "ibus"; + gsm_util_setenv ("QT_IM_MODULE", p); + p = g_getenv ("XMODIFIERS"); + if (!p || !*p) + p = "@im=ibus"; + gsm_util_setenv ("XMODIFIERS", p); + } + + g_free (ibus_path); + } + + /* We want to use the GNOME menus which has the designed categories. + */ + gsm_util_setenv ("XDG_MENU_PREFIX", "gnome-"); + + /* Talk to logind before acquiring a name, since it does synchronous + * calls at initialization time that invoke a main loop and if we + * already owned a name, then we would service too early during + * that main loop. + */ + g_object_unref (gsm_get_system ()); + + name_owner_id = acquire_name (); + + gsm_main (); + + gsm_util_export_user_environment (NULL); + + g_clear_object (&manager); + g_free (gl_renderer); + + g_bus_unown_name (name_owner_id); + gdm_log_shutdown (); + + return 0; +} diff --git a/gnome-session/meson.build b/gnome-session/meson.build index 5861fa47..75a69c07 100644 --- a/gnome-session/meson.build +++ b/gnome-session/meson.build @@ -1,63 +1,60 @@ -sources = files( - 'gsm-util.c', - 'leader-main.c' -) +script_conf = configuration_data() +script_conf.set('libexecdir', session_libexecdir) -cflags = [ - '-DLOCALE_DIR="@0@"'.format(session_localedir), - '-DDATA_DIR="@0@"'.format(session_pkgdatadir), - '-DLIBEXECDIR="@0@"'.format(session_libexecdir) -] +script = 'gnome-session' -executable( - meson.project_name(), - sources, - include_directories: top_inc, - dependencies: session_bin_deps, - c_args: cflags, +configure_file( + input: script + '.in', + output: script, install: true, - install_dir: session_bindir -) - -sources = files( - 'gsm-util.c', - 'leader-systemd.c', + install_dir: session_bindir, + configuration: script_conf ) -executable( - meson.project_name() + '-init-worker', - sources, +libgsmutil = static_library( + 'gsmutil', + sources: 'gsm-util.c', include_directories: top_inc, - dependencies: session_bin_deps, - c_args: cflags, - install: true, - install_dir: session_libexecdir + dependencies: session_deps ) sources = files( 'gsm-app.c', + 'gsm-autostart-app.c', 'gsm-client.c', + 'gsm-dbus-client.c', + 'gsm-fail-whale.c', 'gsm-inhibitor.c', + 'gdm-log.c', 'gsm-manager.c', 'gsm-presence.c', + 'gsm-process-helper.c', 'gsm-session-fill.c', + 'gsm-shell-extensions.c', 'gsm-shell.c', 'gsm-store.c', 'gsm-system.c', 'gsm-systemd.c', - 'gsm-util.c', - 'service-main.c' + 'main.c' ) dbus_ifaces = [ - 'org.gnome.SessionManager', + 'org.gnome.SessionManager', + 'org.gnome.SessionManager.Client', 'org.gnome.SessionManager.ClientPrivate', + 'org.gnome.SessionManager.App', 'org.gnome.SessionManager.Inhibitor', 'org.gnome.SessionManager.Presence', ] xml_dbus_docs = [] +cflags = [ + '-DLOCALE_DIR="@0@"'.format(session_localedir), + '-DDATA_DIR="@0@"'.format(session_pkgdatadir), + '-DLIBEXECDIR="@0@"'.format(session_libexecdir) +] + foreach iface: dbus_ifaces out = gnome.gdbus_codegen( iface, @@ -71,17 +68,32 @@ foreach iface: dbus_ifaces endforeach executable( - meson.project_name() + '-service', - sources, + meson.project_name() + '-binary', sources, include_directories: top_inc, dependencies: session_bin_deps, c_args: cflags, + link_with: libgsmutil, + install: true, + install_dir: session_libexecdir +) + +sources = files('gsm-fail-whale-dialog.c') + +cflags = '-DLOCALE_DIR="@0@"'.format(session_localedir) + +executable( + meson.project_name() + '-failed', + sources, + include_directories: top_inc, + dependencies: gtk_dep, + c_args: cflags, install: true, install_dir: session_libexecdir ) units = [ ['test-client-dbus', [], [gio_dep]], + ['test-process-helper', files('gsm-process-helper.c'), [gio_dep]] ] foreach unit: units diff --git a/gnome-session/org.gnome.SessionManager.App.xml b/gnome-session/org.gnome.SessionManager.App.xml new file mode 100644 index 00000000..25949c8a --- /dev/null +++ b/gnome-session/org.gnome.SessionManager.App.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gnome-session/org.gnome.SessionManager.Client.xml b/gnome-session/org.gnome.SessionManager.Client.xml new file mode 100644 index 00000000..71f1323b --- /dev/null +++ b/gnome-session/org.gnome.SessionManager.Client.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gnome-session/org.gnome.SessionManager.Inhibitor.xml b/gnome-session/org.gnome.SessionManager.Inhibitor.xml index 7546b167..c663cd9f 100644 --- a/gnome-session/org.gnome.SessionManager.Inhibitor.xml +++ b/gnome-session/org.gnome.SessionManager.Inhibitor.xml @@ -56,5 +56,16 @@ + + + + + diff --git a/gnome-session/org.gnome.SessionManager.xml b/gnome-session/org.gnome.SessionManager.xml index 77a37ba5..365fdd3e 100644 --- a/gnome-session/org.gnome.SessionManager.xml +++ b/gnome-session/org.gnome.SessionManager.xml @@ -63,14 +63,14 @@ - + @@ -87,7 +87,7 @@ - + @@ -166,6 +168,19 @@ + + + + + + + + + + @@ -308,7 +336,7 @@ diff --git a/gnome-session/service-main.c b/gnome-session/service-main.c deleted file mode 100644 index eae1dee0..00000000 --- a/gnome-session/service-main.c +++ /dev/null @@ -1,187 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- - * - * Copyright (C) 2006 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 - * 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 "gsm-util.h" -#include "gsm-manager.h" -#include "gsm-session-fill.h" -#include "gsm-store.h" -#include "gsm-system.h" - -static char *session_name = NULL; - -static GsmManager *manager = NULL; -static GMainLoop *loop = NULL; - -void -gsm_quit (void) -{ - g_main_loop_quit (loop); -} - -static void -on_name_lost (GDBusConnection *connection, - const char *name, - gpointer data) -{ - if (connection == NULL) { - if (!gsm_manager_get_dbus_disconnected (manager)) - g_log_structured (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, - "MESSAGE_ID", GSM_MANAGER_UNRECOVERABLE_FAILURE_MSGID, - "MESSAGE", "Lost bus", name); - } else { - g_debug ("Calling name lost callback function"); - - /* - * When the signal handler gets a shutdown signal, it calls - * this function to inform GsmManager to not restart - * applications in the off chance a handler is already queued - * to dispatch following the below call to gtk_main_quit. - */ - gsm_manager_set_phase (manager, GSM_MANAGER_PHASE_EXIT); - } - - gsm_quit (); -} - -static gboolean -term_or_int_signal_cb (gpointer data) -{ - g_autoptr(GError) error = NULL; - GsmManager *manager = (GsmManager *)data; - - /* let the fatal signals interrupt us */ - g_debug ("Caught SIGINT/SIGTERM, shutting down normally."); - - if (!gsm_manager_logout (manager, GSM_MANAGER_LOGOUT_MODE_FORCE, &error)) { - if (g_error_matches (error, GSM_MANAGER_ERROR, GSM_MANAGER_ERROR_NOT_IN_RUNNING)) { - gsm_quit (); - return FALSE; - } - - g_critical ("Failed to log out: %s", error->message); - } - - return FALSE; -} - -static gboolean -sigusr2_cb (gpointer data) -{ - g_debug ("-------- MARK --------"); - return TRUE; -} - -static gboolean -sigusr1_cb (gpointer data) -{ - g_log_set_debug_enabled (!g_log_get_debug_enabled ()); - return TRUE; -} - -static void -on_name_acquired (GDBusConnection *connection, - const char *name, - gpointer data) -{ - gsm_manager_start (manager); -} - -static void -create_manager (void) -{ - manager = gsm_manager_new (); - - g_unix_signal_add (SIGTERM, term_or_int_signal_cb, manager); - g_unix_signal_add (SIGINT, term_or_int_signal_cb, manager); - g_unix_signal_add (SIGUSR1, sigusr1_cb, manager); - g_unix_signal_add (SIGUSR2, sigusr2_cb, manager); - - if (!gsm_session_fill (manager, session_name)) { - g_log_structured (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, - "MESSAGE_ID", GSM_MANAGER_UNRECOVERABLE_FAILURE_MSGID, - "MESSAGE", "Failed to fill session"); - gsm_quit (); - } -} - -static void -on_bus_acquired (GDBusConnection *connection, - const char *name, - gpointer data) -{ - create_manager (); -} - -int -main (int argc, char **argv) -{ - g_autoptr (GError) error = NULL; - g_autoptr (GOptionContext) options = NULL; - const char *debug = NULL; - guint name_owner_id; - static GOptionEntry entries[] = { - { "session", 0, 0, G_OPTION_ARG_STRING, &session_name, N_("Session to use"), N_("SESSION_NAME") }, - { NULL, 0, 0, 0, NULL, NULL, NULL } - }; - - setlocale (LC_ALL, ""); - bindtextdomain (GETTEXT_PACKAGE, LOCALE_DIR); - bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); - textdomain (GETTEXT_PACKAGE); - - options = g_option_context_new (_(" — the GNOME session manager")); - g_option_context_add_main_entries (options, entries, GETTEXT_PACKAGE); - if (!g_option_context_parse (options, &argc, &argv, &error)) - g_error ("%s", error->message); - - debug = g_getenv ("GNOME_SESSION_DEBUG"); - if (debug != NULL) - g_log_set_debug_enabled (atoi (debug) == 1); - - /* Talk to logind before acquiring a name, since it does synchronous - * calls at initialization time that invoke a main loop and if we - * already owned a name, then we would service too early during - * that main loop. */ - g_object_unref (gsm_get_system ()); - - name_owner_id = g_bus_own_name (G_BUS_TYPE_SESSION, - "org.gnome.SessionManager", - G_BUS_NAME_OWNER_FLAGS_NONE, - on_bus_acquired, - on_name_acquired, - on_name_lost, - NULL, NULL); - - loop = g_main_loop_new (NULL, TRUE); - g_main_loop_run (loop); - - g_clear_object (&manager); - g_free (session_name); - g_bus_unown_name (name_owner_id); - return 0; -} diff --git a/gnome-session/test-client-dbus.c b/gnome-session/test-client-dbus.c index 316d3833..7d6f9e4e 100644 --- a/gnome-session/test-client-dbus.c +++ b/gnome-session/test-client-dbus.c @@ -143,16 +143,22 @@ register_client (void) { GError *error; GVariant *object_path_variant; + const char *startup_id; const char *app_id; app_id = "gedit"; + startup_id = g_getenv ("DESKTOP_AUTOSTART_ID"); + if (!startup_id) { + startup_id = ""; + } + error = NULL; object_path_variant = g_dbus_proxy_call_sync (sm_proxy, "RegisterClient", g_variant_new ("(ss)", app_id, - ""), + startup_id), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); diff --git a/gnome-session/test-process-helper.c b/gnome-session/test-process-helper.c new file mode 100644 index 00000000..e48caa8c --- /dev/null +++ b/gnome-session/test-process-helper.c @@ -0,0 +1,56 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2010 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 + * 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 "gsm-process-helper.h" + +int +main (int argc, + char *argv[]) +{ + char *command_line = "xeyes"; + int timeout = 500; + GError *error = NULL; + + if (argc > 3) { + g_printerr ("Too many arguments.\n"); + g_printerr ("Usage: %s [COMMAND] [TIMEOUT]\n", argv[0]); + return 1; + } + + if (argc >= 2) + command_line = argv[1]; + if (argc >= 3) { + int i = atoi (argv[2]); + if (i > 0) + timeout = i; + } + + if (!gsm_process_helper (command_line, timeout, &error)) { + g_warning ("%s", error->message); + g_clear_error (&error); + } else { + g_print ("Command exited successfully.\n"); + } + + return 0; +} diff --git a/meson.build b/meson.build index 216e4880..361b388c 100644 --- a/meson.build +++ b/meson.build @@ -78,7 +78,7 @@ endif add_project_arguments(common_flags + compiler_flags, language: 'c') -glib_req_version = '>= 2.82.0' +glib_req_version = '>= 2.46.0' gio_dep = dependency('gio-2.0', version: glib_req_version) gtk_dep = dependency('gtk4') diff --git a/po/POTFILES.in b/po/POTFILES.in index 1c1ff5a1..bf7c9839 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -1,14 +1,15 @@ # List of source files containing translatable strings. # Please keep this file sorted alphabetically. data/gnome.desktop.in.in -data/gnome.session.desktop +data/gnome.session.desktop.in.in data/gnome-wayland.desktop.in.in data/gnome-xorg.desktop.in.in data/org.gnome.SessionManager.gschema.xml +gnome-session/gsm-fail-whale-dialog.c gnome-session/gsm-manager.c +gnome-session/gsm-process-helper.c gnome-session/gsm-util.c -gnome-session/leader-main.c -gnome-session/service-main.c +gnome-session/main.c tools/gnome-session-ctl.c tools/gnome-session-inhibit.c tools/gnome-session-quit.c diff --git a/po/gl.po b/po/gl.po index a7cf9578..cd1c1ace 100644 --- a/po/gl.po +++ b/po/gl.po @@ -17,8 +17,8 @@ msgid "" msgstr "" "Project-Id-Version: gnome-session.master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-session/issues\n" -"POT-Creation-Date: 2025-07-18 19:39+0000\n" -"PO-Revision-Date: 2025-08-02 22:46+0200\n" +"POT-Creation-Date: 2024-01-13 12:21+0000\n" +"PO-Revision-Date: 2024-01-23 22:17+0100\n" "Last-Translator: Fran Dieguez \n" "Language-Team: Galician \n" "Language: gl\n" @@ -26,7 +26,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 3.6\n" +"X-Generator: Poedit 3.4.2\n" "X-Project-Style: gnome\n" "X-DL-Team: gl\n" "X-DL-Module: gnome-session\n" @@ -34,6 +34,14 @@ msgstr "" "X-DL-Domain: po\n" "X-DL-State: Translating\n" +#: data/gnome-custom-session.desktop.in.in:3 +msgid "Custom" +msgstr "Personalizada" + +#: data/gnome-custom-session.desktop.in.in:4 +msgid "This entry lets you select a saved session" +msgstr "Esta entrada permítelle selecciona unha sesión gardada" + #: data/gnome.desktop.in.in:3 data/gnome.session.desktop.in.in:3 msgid "GNOME" msgstr "GNOME" @@ -43,6 +51,10 @@ msgstr "GNOME" msgid "This session logs you into GNOME" msgstr "Esta sesión iniciará en GNOME" +#: data/gnome-dummy.session.desktop.in.in:3 +msgid "GNOME dummy" +msgstr "GNOME de proba" + #: data/gnome-wayland.desktop.in.in:3 msgid "GNOME on Wayland" msgstr "GNOME en Wayland" @@ -52,20 +64,40 @@ msgid "GNOME on Xorg" msgstr "GNOME en Xorg" #: data/org.gnome.SessionManager.gschema.xml:5 +msgid "Save sessions" +msgstr "Gardar as sesións" + +#: data/org.gnome.SessionManager.gschema.xml:6 +msgid "If enabled, gnome-session will save the session automatically." +msgstr "Se está activo, gnome-session gardará a sesión automaticamente." + +#: data/org.gnome.SessionManager.gschema.xml:10 +msgid "Save this session" +msgstr "Gardar esta sesión" + +#: data/org.gnome.SessionManager.gschema.xml:11 +msgid "" +"When enabled, gnome-session will automatically save the next session at log " +"out even if auto saving is disabled." +msgstr "" +"Cando está activado, gnome-session gardará automaticamente a seguinte sesión " +"cando peche a sesión aínda que o autogardado estea desactivado." + +#: data/org.gnome.SessionManager.gschema.xml:15 msgid "Logout prompt" msgstr "Preguntar ao pechar a sesión" -#: data/org.gnome.SessionManager.gschema.xml:6 +#: data/org.gnome.SessionManager.gschema.xml:16 msgid "If enabled, gnome-session will prompt the user before ending a session." msgstr "" "Se está activado, gnome-session preguntaralle ao usuario antes de finalizar " "a sesión." -#: data/org.gnome.SessionManager.gschema.xml:10 +#: data/org.gnome.SessionManager.gschema.xml:20 msgid "Show the fallback warning" msgstr "Mostrar un aviso ao usar fallback" -#: data/org.gnome.SessionManager.gschema.xml:11 +#: data/org.gnome.SessionManager.gschema.xml:21 msgid "" "If enabled, gnome-session will display a warning dialog after login if the " "session was automatically fallen back." @@ -73,55 +105,153 @@ msgstr "" "Se está activado, gnome-session mostrará un diálogo de aviso despois de " "iniciar a sesión se se usou automaticamente a sesión de respaldo." -#: gnome-session/gsm-manager.c:709 gnome-session/gsm-manager.c:1040 +#: data/session-selector.ui:15 +msgid "Custom Session" +msgstr "Sesión personalizada" + +#: data/session-selector.ui:50 tools/gnome-session-selector.c:102 +msgid "Please select a custom session to run" +msgstr "Seleccione unha sesión personalizada para executar" + +#: data/session-selector.ui:105 +msgid "_New Session" +msgstr "_Nova sesión" + +#: data/session-selector.ui:119 +msgid "_Remove Session" +msgstr "_Quitar sesión" + +#: data/session-selector.ui:133 +msgid "Rena_me Session" +msgstr "Reno_mear sesión" + +#: data/session-selector.ui:168 +msgid "_Continue" +msgstr "_Continuar" + +#: gnome-session/gsm-fail-whale-dialog.c:318 +msgid "Oh no! Something has gone wrong." +msgstr "Recoiro! Algo foi mal." + +#: gnome-session/gsm-fail-whale-dialog.c:325 +msgid "" +"A problem has occurred and the system can’t recover. Please contact a system " +"administrator" +msgstr "" +"Produciuse un problema e non é posíbel recuperar o sistema. Contacte co " +"administrador do sistema" + +#: gnome-session/gsm-fail-whale-dialog.c:327 +msgid "" +"A problem has occurred and the system can’t recover. All extensions have " +"been disabled as a precaution." +msgstr "" +"Produciuse un problema e non é posíbel recuperar o sistema. Desactiváronse " +"todas as extensións como medida de precaución." + +#: gnome-session/gsm-fail-whale-dialog.c:329 +msgid "" +"A problem has occurred and the system can’t recover.\n" +"Please log out and try again." +msgstr "" +"Produciuse un problema e non é posíbel recuperar o sistema.\n" +"Peche a sesión e ténteo de novo." + +#: gnome-session/gsm-fail-whale-dialog.c:344 +msgid "_Log Out" +msgstr "_Saír da sesión" + +#: gnome-session/gsm-fail-whale-dialog.c:412 gnome-session/main.c:403 +msgid "Enable debugging code" +msgstr "Activar o código de depuración" + +#: gnome-session/gsm-fail-whale-dialog.c:413 +msgid "Allow logout" +msgstr "Permitir saír da sesión" + +#: gnome-session/gsm-fail-whale-dialog.c:414 +msgid "Show extension warning" +msgstr "Mostrar aviso de extensión" + +#: gnome-session/gsm-manager.c:1301 gnome-session/gsm-manager.c:2050 msgid "Not responding" msgstr "Non responde" -#: gnome-session/gsm-util.c:283 +#: gnome-session/gsm-util.c:431 msgid "_Log out" msgstr "_Saír da sesión" -#: gnome-session/leader-main.c:534 +#. It'd be really surprising to reach this code: if we're here, +#. * then the XSMP client already has set several XSMP +#. * properties. But it could still be that SmProgram is not set. +#. +#: gnome-session/gsm-xsmp-client.c:557 +msgid "Remembered Application" +msgstr "Aplicación recordado" + +#: gnome-session/gsm-xsmp-client.c:1216 +msgid "This program is blocking logout." +msgstr "Este programa está bloqueando a saída da sesión." + +#: gnome-session/gsm-xsmp-server.c:340 +msgid "" +"Refusing new client connection because the session is currently being shut " +"down\n" +msgstr "" +"Rexeitouse a conexión co novo cliente porque neste momento a sesión está " +"sendo apagada\n" + +#: gnome-session/gsm-xsmp-server.c:607 +#, c-format +msgid "Could not create ICE listening socket: %s" +msgstr "Non foi posíbel crear o socket de escoita ICE: %s" + +#: gnome-session/main.c:400 +msgid "Running as systemd service" +msgstr "Executándose como un servizo de systemd" + +#: gnome-session/main.c:401 msgid "Override standard autostart directories" msgstr "Ignorar os directorios de inicio automático estándares" -#: gnome-session/leader-main.c:534 +#: gnome-session/main.c:401 msgid "AUTOSTART_DIR" msgstr "CARTAFO_DE_AUTOINICIO" -#: gnome-session/leader-main.c:535 gnome-session/service-main.c:147 +#: gnome-session/main.c:402 msgid "Session to use" msgstr "Sesión a usar" -#: gnome-session/leader-main.c:535 gnome-session/service-main.c:147 +#: gnome-session/main.c:402 msgid "SESSION_NAME" msgstr "NOME_DA_SESIÓN" -#: gnome-session/leader-main.c:536 -msgid "Enable debugging code" -msgstr "Activar o código de depuración" +#: gnome-session/main.c:404 +msgid "Do not load user-specified applications" +msgstr "Non cargar as aplicacións especificadas polo usuario" -#: gnome-session/leader-main.c:537 +#: gnome-session/main.c:405 msgid "Version of this application" msgstr "Versión desta aplicación" -#: gnome-session/leader-main.c:538 -msgid "This option is ignored" -msgstr "Esta opción ignórase" +#. Translators: the 'fail whale' is the black dialog we show when something goes seriously wrong +#: gnome-session/main.c:407 +msgid "Show the fail whale dialog for testing" +msgstr "Mostrar o diálogo da balea de fallos" -#: gnome-session/leader-main.c:539 -msgid "Don't re-exec into a login shell" -msgstr "Non re-executar nunha shell de inicio de sesión" +#: gnome-session/main.c:408 +msgid "Disable hardware acceleration check" +msgstr "Desactivar a comprobación de aceleración de hardware" -#: gnome-session/leader-main.c:560 gnome-session/service-main.c:156 +#: gnome-session/main.c:440 msgid " — the GNOME session manager" msgstr " — o xestor de sesións do GNOME" -#: tools/gnome-session-ctl.c:247 +#: tools/gnome-session-ctl.c:245 msgid "Start gnome-session-shutdown.target" msgstr "Iniciar gnome-session-shutdown.target" -#: tools/gnome-session-ctl.c:248 +#: tools/gnome-session-ctl.c:246 msgid "" "Start gnome-session-shutdown.target when receiving EOF or a single byte on " "stdin" @@ -129,92 +259,79 @@ msgstr "" "Iniciar gnome-session-shutdown.target ao recibir EOF ou un byte único pola " "stdin" -#: tools/gnome-session-ctl.c:249 +#: tools/gnome-session-ctl.c:247 msgid "Signal initialization done to gnome-session" msgstr "Inicialización de sinal feita para gnome-session" -#: tools/gnome-session-ctl.c:250 +#: tools/gnome-session-ctl.c:248 msgid "Restart dbus.service if it is running" msgstr "Reiniciar dbus.service se está executándose" -#: tools/gnome-session-ctl.c:251 +#: tools/gnome-session-ctl.c:249 msgid "" -"Run from ExecStopPost to start gnome-session-shutdown.target on service " -"failure" +"Run from ExecStopPost to start gnome-session-failed.target on service failure" msgstr "" -"Execute desde ExecStopPost para iniciar o obxectivo gnome-session-" -"shutdown.target cando falle o servizo" +"Execute desde ExecStopPost para iniciar o obxectivo gnome-session-failed " +"cando falle o servizo" -#: tools/gnome-session-ctl.c:281 +#: tools/gnome-session-ctl.c:279 msgid "Program needs exactly one parameter" msgstr "O programa precisa exactamente un parámetro" -#: tools/gnome-session-inhibit.c:69 +#: tools/gnome-session-inhibit.c:108 #, c-format -msgid "Unknown inhibit argument: %s" -msgstr "Argumento de inhibición descoñecido: %s" - -#: tools/gnome-session-inhibit.c:289 +msgid "" +"%s [OPTION…] COMMAND\n" +"\n" +"Execute COMMAND while inhibiting some session functionality.\n" +"\n" +" -h, --help Show this help\n" +" --version Show program version\n" +" --app-id ID The application id to use\n" +" when inhibiting (optional)\n" +" --reason REASON The reason for inhibiting (optional)\n" +" --inhibit ARG Things to inhibit, colon-separated list of:\n" +" logout, switch-user, suspend, idle, automount\n" +" --inhibit-only Do not launch COMMAND and wait forever instead\n" +" -l, --list List the existing inhibitions, and exit\n" +"\n" +"If no --inhibit option is specified, idle is assumed.\n" +msgstr "" +"%s [OPCIÓN…] ORDE\n" +"\n" +"Execute ORDE ao inhibir algunha funcionalidade da sesión.\n" +"\n" +" -h, --help Mostrar esta axuda\n" +" --version Mostrar versión do programa\n" +" --app-id ID Id de aplicación a usar\n" +" ao inhibir (opcional)\n" +" --reason REASON A razón para inhibir (opcional)\n" +" --inhibit ARG Cousas a inhibir, lista separada por comas de:\n" +" logout, switch-user, suspend, idle, automount\n" +" --inhibit-only Non executar ORDE e agardar no lugar\n" +" -l, --list Lista as inhibicións existentes, e sae\n" +"\n" +"Se non se especifica a opción --inhibit, asúmese idle.\n" + +#: tools/gnome-session-inhibit.c:282 #, c-format -msgid "Failed to execute %s: %m\n" -msgstr "Produciuse un erro ao executar %s: %m\n" - -#: tools/gnome-session-inhibit.c:361 -msgid "Show program version" -msgstr "Mostrar a versión do programa" - -#: tools/gnome-session-inhibit.c:362 -msgid "The application id to use when inhibiting (optional)" -msgstr "O id de aplicación a usar ao inhibir (opcional)" - -#: tools/gnome-session-inhibit.c:362 -msgid "ID" -msgstr "ID" - -#: tools/gnome-session-inhibit.c:363 -msgid "The reason for inhibiting (optional)" -msgstr "A razón para inhibir (opcional)" +msgid "Error while creating pipe: %s\n" +msgstr "Produciuse un erro ao crear a tubería: %s\n" -#: tools/gnome-session-inhibit.c:363 -msgid "REASON" -msgstr "RAZÓN" +#: tools/gnome-session-inhibit.c:299 +msgid "Failure reading pipe\n" +msgstr "Produciuse un erro ao ler a tubería\n" -#: tools/gnome-session-inhibit.c:364 -msgid "Add action to inhibit (repeatable)" -msgstr "Engadir acción ao inhibir (repetíbel)" - -#: tools/gnome-session-inhibit.c:364 -msgid "ACTION" -msgstr "ACCIÓN" - -#: tools/gnome-session-inhibit.c:365 -msgid "Do not launch COMMAND and wait forever instead" -msgstr "Non lanzar ORDE e agardar por sempre no lugar" - -#: tools/gnome-session-inhibit.c:366 -msgid "List the existing inhibitions, and exit" -msgstr "Lista das inhibicións existentes e saír" - -#: tools/gnome-session-inhibit.c:378 -msgid "COMMAND" -msgstr "ORDE" - -#: tools/gnome-session-inhibit.c:384 -msgid "Execute COMMAND while inhibiting some session functionality." -msgstr "Executar ORDE ao inhibir algunha funcionalidade da sesión." - -#: tools/gnome-session-inhibit.c:385 -msgid "" -"Valid ACTION values are: logout, switch-user, suspend, idle, and automount.\n" -"If no --inhibit options are specified, idle is assumed." -msgstr "" -"Os valores de ACCIÓN válidos son: logout, switch-user, suspend, idle e " -"automount.\n" -"Se non se especifican as opcións —inhibit asúmese idle." +#: tools/gnome-session-inhibit.c:303 +#, c-format +msgid "Failed to execute %s\n" +msgstr "Produciuse un erro ao executar %s\n" -#: tools/gnome-session-inhibit.c:409 -msgid "Neither COMMAND nor --inhibit-only were specified." -msgstr "Non se especificou ORDE nin —inhibit-only." +#: tools/gnome-session-inhibit.c:376 tools/gnome-session-inhibit.c:386 +#: tools/gnome-session-inhibit.c:396 +#, c-format +msgid "%s requires an argument\n" +msgstr "%s require un argumento\n" #: tools/gnome-session-quit.c:50 msgid "Log out" @@ -244,174 +361,28 @@ msgstr "Non foi posíbel conectar co xestor de sesión" msgid "Program called with conflicting options" msgstr "Chamouse un programa con opcións en conflito" -#~ msgid "Custom" -#~ msgstr "Personalizada" - -#~ msgid "This entry lets you select a saved session" -#~ msgstr "Esta entrada permítelle selecciona unha sesión gardada" - -#~ msgid "GNOME dummy" -#~ msgstr "GNOME de proba" - -#~ msgid "Save sessions" -#~ msgstr "Gardar as sesións" - -#~ msgid "If enabled, gnome-session will save the session automatically." -#~ msgstr "Se está activo, gnome-session gardará a sesión automaticamente." - -#~ msgid "Save this session" -#~ msgstr "Gardar esta sesión" - -#~ msgid "" -#~ "When enabled, gnome-session will automatically save the next session at " -#~ "log out even if auto saving is disabled." -#~ msgstr "" -#~ "Cando está activado, gnome-session gardará automaticamente a seguinte " -#~ "sesión cando peche a sesión aínda que o autogardado estea desactivado." - -#~ msgid "Custom Session" -#~ msgstr "Sesión personalizada" - -#~ msgid "Please select a custom session to run" -#~ msgstr "Seleccione unha sesión personalizada para executar" - -#~ msgid "_New Session" -#~ msgstr "_Nova sesión" - -#~ msgid "_Remove Session" -#~ msgstr "_Quitar sesión" - -#~ msgid "Rena_me Session" -#~ msgstr "Reno_mear sesión" - -#~ msgid "_Continue" -#~ msgstr "_Continuar" - -#~ msgid "Oh no! Something has gone wrong." -#~ msgstr "Recoiro! Algo foi mal." - -#~ msgid "" -#~ "A problem has occurred and the system can’t recover. Please contact a " -#~ "system administrator" -#~ msgstr "" -#~ "Produciuse un problema e non é posíbel recuperar o sistema. Contacte co " -#~ "administrador do sistema" - -#~ msgid "" -#~ "A problem has occurred and the system can’t recover. All extensions have " -#~ "been disabled as a precaution." -#~ msgstr "" -#~ "Produciuse un problema e non é posíbel recuperar o sistema. " -#~ "Desactiváronse todas as extensións como medida de precaución." - -#~ msgid "" -#~ "A problem has occurred and the system can’t recover.\n" -#~ "Please log out and try again." -#~ msgstr "" -#~ "Produciuse un problema e non é posíbel recuperar o sistema.\n" -#~ "Peche a sesión e ténteo de novo." - -#~ msgid "_Log Out" -#~ msgstr "_Saír da sesión" - -#~ msgid "Allow logout" -#~ msgstr "Permitir saír da sesión" - -#~ msgid "Show extension warning" -#~ msgstr "Mostrar aviso de extensión" - -#~ msgid "Remembered Application" -#~ msgstr "Aplicación recordado" - -#~ msgid "This program is blocking logout." -#~ msgstr "Este programa está bloqueando a saída da sesión." - -#~ msgid "" -#~ "Refusing new client connection because the session is currently being " -#~ "shut down\n" -#~ msgstr "" -#~ "Rexeitouse a conexión co novo cliente porque neste momento a sesión está " -#~ "sendo apagada\n" - +#: tools/gnome-session-selector.c:61 #, c-format -#~ msgid "Could not create ICE listening socket: %s" -#~ msgstr "Non foi posíbel crear o socket de escoita ICE: %s" - -#~ msgid "Running as systemd service" -#~ msgstr "Executándose como un servizo de systemd" +msgid "Session %d" +msgstr "Sesión %d" -#~ msgid "Do not load user-specified applications" -#~ msgstr "Non cargar as aplicacións especificadas polo usuario" - -#~ msgid "Show the fail whale dialog for testing" -#~ msgstr "Mostrar o diálogo da balea de fallos" - -#~ msgid "Disable hardware acceleration check" -#~ msgstr "Desactivar a comprobación de aceleración de hardware" - -#, c-format -#~ msgid "" -#~ "%s [OPTION…] COMMAND\n" -#~ "\n" -#~ "Execute COMMAND while inhibiting some session functionality.\n" -#~ "\n" -#~ " -h, --help Show this help\n" -#~ " --version Show program version\n" -#~ " --app-id ID The application id to use\n" -#~ " when inhibiting (optional)\n" -#~ " --reason REASON The reason for inhibiting (optional)\n" -#~ " --inhibit ARG Things to inhibit, colon-separated list of:\n" -#~ " logout, switch-user, suspend, idle, automount\n" -#~ " --inhibit-only Do not launch COMMAND and wait forever instead\n" -#~ " -l, --list List the existing inhibitions, and exit\n" -#~ "\n" -#~ "If no --inhibit option is specified, idle is assumed.\n" -#~ msgstr "" -#~ "%s [OPCIÓN…] ORDE\n" -#~ "\n" -#~ "Execute ORDE ao inhibir algunha funcionalidade da sesión.\n" -#~ "\n" -#~ " -h, --help Mostrar esta axuda\n" -#~ " --version Mostrar versión do programa\n" -#~ " --app-id ID Id de aplicación a usar\n" -#~ " ao inhibir (opcional)\n" -#~ " --reason REASON A razón para inhibir (opcional)\n" -#~ " --inhibit ARG Cousas a inhibir, lista separada por comas de:\n" -#~ " logout, switch-user, suspend, idle, automount\n" -#~ " --inhibit-only Non executar ORDE e agardar no lugar\n" -#~ " -l, --list Lista as inhibicións existentes, e sae\n" -#~ "\n" -#~ "Se non se especifica a opción --inhibit, asúmese idle.\n" - -#, c-format -#~ msgid "Error while creating pipe: %s\n" -#~ msgstr "Produciuse un erro ao crear a tubería: %s\n" - -#~ msgid "Failure reading pipe\n" -#~ msgstr "Produciuse un erro ao ler a tubería\n" - -#, c-format -#~ msgid "%s requires an argument\n" -#~ msgstr "%s require un argumento\n" - -#, c-format -#~ msgid "Session %d" -#~ msgstr "Sesión %d" - -#~ msgid "" -#~ "Session names are not allowed to start with “.” or contain “/” characters" -#~ msgstr "" -#~ "Os nomes de sesións non poden comezar con «.» ou conter caracteres «/»" +#: tools/gnome-session-selector.c:107 +msgid "" +"Session names are not allowed to start with “.” or contain “/” characters" +msgstr "Os nomes de sesións non poden comezar con «.» ou conter caracteres «/»" -#~ msgid "Session names are not allowed to start with “.”" -#~ msgstr "Os nomes das sesións non poden comezar por «.»" +#: tools/gnome-session-selector.c:111 +msgid "Session names are not allowed to start with “.”" +msgstr "Os nomes das sesións non poden comezar por «.»" -#~ msgid "Session names are not allowed to contain “/” characters" -#~ msgstr "Os nomes de sesións non poden conter caracteres «/»" +#: tools/gnome-session-selector.c:115 +msgid "Session names are not allowed to contain “/” characters" +msgstr "Os nomes de sesións non poden conter caracteres «/»" +#: tools/gnome-session-selector.c:123 #, c-format -#~ msgid "A session named “%s” already exists" -#~ msgstr "Xa existe unha sesión co nome «%s»" +msgid "A session named “%s” already exists" +msgstr "Xa existe unha sesión co nome «%s»" #~ msgid "Use systemd session management" #~ msgstr "Usar a xestión de sesións de systemd" diff --git a/po/ro.po b/po/ro.po index 2055afbd..ccbc0674 100644 --- a/po/ro.po +++ b/po/ro.po @@ -4,203 +4,328 @@ # Lucian Adrian Grijincu , 2009, 2011. # Daniel Șerbănescu , 2010, 2011. # Daniel Șerbănescu , 2015, 2017. -# Antonio Marin , 2025. -# msgid "" msgstr "" "Project-Id-Version: gnome-session-2.0\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-session/issues\n" -"POT-Creation-Date: 2025-09-12 18:51+0000\n" -"PO-Revision-Date: 2025-09-14 19:04+0200\n" -"Last-Translator: Antonio Marin \n" -"Language-Team: Romanian \n" +"POT-Creation-Date: 2024-01-08 21:02+0000\n" +"PO-Revision-Date: 2024-01-10 10:53+0200\n" +"Last-Translator: Florentina Mușat \n" +"Language-Team: Gnome Romanian Translation Team\n" "Language: ro\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < " -"20)) ? 1 : 2);\n" -"X-Generator: Gtranslator 48.0\n" +"20)) ? 1 : 2);;\n" +"X-Generator: Poedit 3.4.1\n" "X-Project-Style: gnome\n" -#: data/gnome.desktop.in.in:2 data/gnome.session.desktop:2 +#: data/gnome-custom-session.desktop.in.in:3 +msgid "Custom" +msgstr "Personalizat" + +#: data/gnome-custom-session.desktop.in.in:4 +msgid "This entry lets you select a saved session" +msgstr "Această opțiune vă permite să selectați o sesiune salvată" + +#: data/gnome.desktop.in.in:3 data/gnome.session.desktop.in.in:3 msgid "GNOME" msgstr "GNOME" -#: data/gnome.desktop.in.in:3 data/gnome-wayland.desktop.in.in:3 -#: data/gnome-xorg.desktop.in.in:3 +#: data/gnome.desktop.in.in:4 data/gnome-wayland.desktop.in.in:4 +#: data/gnome-xorg.desktop.in.in:4 msgid "This session logs you into GNOME" -msgstr "Această sesiune efectuează autentificarea în GNOME" +msgstr "Această sesiune vă va autentifica în GNOME" -#: data/gnome-wayland.desktop.in.in:2 +#: data/gnome-dummy.session.desktop.in.in:3 +msgid "GNOME dummy" +msgstr "Machetă GNOME" + +#: data/gnome-wayland.desktop.in.in:3 msgid "GNOME on Wayland" msgstr "GNOME pe Wayland" -#: data/gnome-xorg.desktop.in.in:2 +#: data/gnome-xorg.desktop.in.in:3 msgid "GNOME on Xorg" msgstr "GNOME pe Xorg" #: data/org.gnome.SessionManager.gschema.xml:5 -msgid "Logout prompt" -msgstr "Aviz la terminarea sesiunii" +msgid "Save sessions" +msgstr "Salvează sesiuni" #: data/org.gnome.SessionManager.gschema.xml:6 +msgid "If enabled, gnome-session will save the session automatically." +msgstr "Dacă este activat, gnome-session va salva sesiunea automat." + +#: data/org.gnome.SessionManager.gschema.xml:10 +msgid "Save this session" +msgstr "Salvează această sesiune" + +#: data/org.gnome.SessionManager.gschema.xml:11 +msgid "" +"When enabled, gnome-session will automatically save the next session at log " +"out even if auto saving is disabled." +msgstr "" +"Când este activată, gnome-session va salva sesiunea automat sesiunea " +"următoare la deautentificare chiar și dacă salvarea automată este " +"dezactivată." + +#: data/org.gnome.SessionManager.gschema.xml:15 +msgid "Logout prompt" +msgstr "Anunță ieșirea din sesiune" + +#: data/org.gnome.SessionManager.gschema.xml:16 msgid "If enabled, gnome-session will prompt the user before ending a session." msgstr "" -"Dacă este activată, gnome-session va cere confirmarea utilizatorului înainte " -"de terminarea sesiunii." +"Dacă este activat, gnome-session va anunța utilizatorul înainte de " +"terminarea sesiunii." -#: data/org.gnome.SessionManager.gschema.xml:10 +#: data/org.gnome.SessionManager.gschema.xml:20 msgid "Show the fallback warning" -msgstr "Arată avertismentul sesiunii de rezervă" +msgstr "Arată avertizarea sesiunii de rezervă" -#: data/org.gnome.SessionManager.gschema.xml:11 +#: data/org.gnome.SessionManager.gschema.xml:21 msgid "" "If enabled, gnome-session will display a warning dialog after login if the " "session was automatically fallen back." msgstr "" -"Dacă este activată, gnome-session va afișa un dialog de avertizare după " -"autentificare dacă s-a trecut automat la sesiunea de rezervă.." +"Dacă este activat, gnome-session va afișa un dialog de avertizare după " +"autentificare dacă sesiunea de rezervă a fost inițiată." + +#: data/session-selector.ui:15 +msgid "Custom Session" +msgstr "Sesiune personalizată" + +#: data/session-selector.ui:50 tools/gnome-session-selector.c:102 +msgid "Please select a custom session to run" +msgstr "Selectați o sesiune personalizată pentru rulare" + +#: data/session-selector.ui:105 +msgid "_New Session" +msgstr "Sesiune _nouă" + +#: data/session-selector.ui:119 +msgid "_Remove Session" +msgstr "_Elimină sesiunea" + +#: data/session-selector.ui:133 +msgid "Rena_me Session" +msgstr "Redenu_mește sesiunea" + +#: data/session-selector.ui:168 +msgid "_Continue" +msgstr "_Continuă" + +#: gnome-session/gsm-fail-whale-dialog.c:318 +msgid "Oh no! Something has gone wrong." +msgstr "Oh, nu! Ceva nu a funcționat corect." + +#: gnome-session/gsm-fail-whale-dialog.c:325 +msgid "" +"A problem has occurred and the system can’t recover. Please contact a system " +"administrator" +msgstr "" +"A intervenit o problemă și sistemul nu se poate recupera. Contactați un " +"administrator de sistem" + +#: gnome-session/gsm-fail-whale-dialog.c:327 +msgid "" +"A problem has occurred and the system can’t recover. All extensions have " +"been disabled as a precaution." +msgstr "" +"A intervenit o problemă și sistemul nu se poate recupera. Toate extensiile " +"au fost dezactivate preventiv." + +#: gnome-session/gsm-fail-whale-dialog.c:329 +msgid "" +"A problem has occurred and the system can’t recover.\n" +"Please log out and try again." +msgstr "" +"A intervenit o problemă și sistemul nu se poate recupera.\n" +"Ieșiți din sesiune și încercați din nou." + +#: gnome-session/gsm-fail-whale-dialog.c:344 +msgid "_Log Out" +msgstr "_Ieșire din sesiune" + +#: gnome-session/gsm-fail-whale-dialog.c:412 gnome-session/main.c:403 +msgid "Enable debugging code" +msgstr "Activează codul pentru depanare" + +#: gnome-session/gsm-fail-whale-dialog.c:413 +msgid "Allow logout" +msgstr "Permite ieșierea din sesiune" + +#: gnome-session/gsm-fail-whale-dialog.c:414 +msgid "Show extension warning" +msgstr "Afișează avertizările pentru extensii" -#: gnome-session/gsm-manager.c:709 gnome-session/gsm-manager.c:1040 +#: gnome-session/gsm-manager.c:1301 gnome-session/gsm-manager.c:2050 msgid "Not responding" msgstr "Nu răspunde" -#: gnome-session/gsm-util.c:167 +#: gnome-session/gsm-util.c:431 msgid "_Log out" -msgstr "_Termină sesiunea" +msgstr "În_chide sesiunea" + +#. It'd be really surprising to reach this code: if we're here, +#. * then the XSMP client already has set several XSMP +#. * properties. But it could still be that SmProgram is not set. +#. +#: gnome-session/gsm-xsmp-client.c:557 +msgid "Remembered Application" +msgstr "Aplicație memorată" + +#: gnome-session/gsm-xsmp-client.c:1216 +msgid "This program is blocking logout." +msgstr "Acest program blochează ieșirea din sesiune." + +#: gnome-session/gsm-xsmp-server.c:340 +msgid "" +"Refusing new client connection because the session is currently being shut " +"down\n" +msgstr "" +"Se refuză conexiunea noului client deoarece sesiunea este în curs de oprire\n" + +#: gnome-session/gsm-xsmp-server.c:607 +#, c-format +msgid "Could not create ICE listening socket: %s" +msgstr "Nu s-a putut creea socketul de ascultare ICE: %s" + +#: gnome-session/main.c:400 +msgid "Running as systemd service" +msgstr "Rulează ca serviciu systemd" + +#: gnome-session/main.c:401 +msgid "Override standard autostart directories" +msgstr "Înlocuiește dosarele standard de pornire automată" -#: gnome-session/leader-main.c:181 gnome-session/service-main.c:148 +#: gnome-session/main.c:401 +msgid "AUTOSTART_DIR" +msgstr "DIRECTOR_DE_PORNIRE" + +#: gnome-session/main.c:402 msgid "Session to use" msgstr "Sesiunea de utilizat" -#: gnome-session/leader-main.c:181 gnome-session/service-main.c:148 +#: gnome-session/main.c:402 msgid "SESSION_NAME" msgstr "NUMELE_SESIUNII" -#: gnome-session/leader-main.c:182 -msgid "Enable debugging code" -msgstr "Activează codul pentru depanare" +#: gnome-session/main.c:404 +msgid "Do not load user-specified applications" +msgstr "Nu încărca aplicațiile specificate de utilizator" -#: gnome-session/leader-main.c:183 +#: gnome-session/main.c:405 msgid "Version of this application" msgstr "Versiunea acestei aplicații" -#: gnome-session/leader-main.c:184 -msgid "This option is ignored" -msgstr "Această opțiune este ignorată" +#. Translators: the 'fail whale' is the black dialog we show when something goes seriously wrong +#: gnome-session/main.c:407 +msgid "Show the fail whale dialog for testing" +msgstr "Afișează dialogul de eroare pentru testare" -#: gnome-session/leader-main.c:185 -msgid "Don't re-exec into a login shell" -msgstr "Nu reporni la autentificare" +#: gnome-session/main.c:408 +msgid "Disable hardware acceleration check" +msgstr "Dezactivează verificarea accelerării hardware" -#: gnome-session/leader-main.c:203 gnome-session/service-main.c:157 +#: gnome-session/main.c:440 msgid " — the GNOME session manager" -msgstr " — Administratorul de sesiuni GNOME" +msgstr " — Administratorul de sesiune GNOME" -#: tools/gnome-session-ctl.c:247 +#: tools/gnome-session-ctl.c:245 msgid "Start gnome-session-shutdown.target" -msgstr "Pornește gnome-session-shutdown.target" +msgstr "Începe gnome-session-shutdown.target" -#: tools/gnome-session-ctl.c:248 +#: tools/gnome-session-ctl.c:246 msgid "" "Start gnome-session-shutdown.target when receiving EOF or a single byte on " "stdin" msgstr "" -"Pornește gnome-session-shutdown.target când se primește EOF sau un singur " +"Începe gnome-session-shutdown.target când se primește EOF sau un singur " "octet la stdin" -#: tools/gnome-session-ctl.c:249 +#: tools/gnome-session-ctl.c:247 msgid "Signal initialization done to gnome-session" msgstr "Inițializarea semnalului a fost făcută la gnome-session" -#: tools/gnome-session-ctl.c:250 +#: tools/gnome-session-ctl.c:248 msgid "Restart dbus.service if it is running" -msgstr "Reporneșe dbus.service dacă este în funcțiune" +msgstr "Reporneșe dbus.service dacă rulează" -#: tools/gnome-session-ctl.c:251 +#: tools/gnome-session-ctl.c:249 msgid "" -"Run from ExecStopPost to start gnome-session-shutdown.target on service " -"failure" +"Run from ExecStopPost to start gnome-session-failed.target on service failure" msgstr "" -"Execută din ExecStopPost pentru a porni gnome-session-shutdown.target la " +"Rulează de la ExecStopPost pentru a începe gnome-session-failed.target la " "eșecul serviciului" -#: tools/gnome-session-ctl.c:281 +#: tools/gnome-session-ctl.c:279 msgid "Program needs exactly one parameter" msgstr "Programul necesită exact un parametru" -#: tools/gnome-session-inhibit.c:68 +#: tools/gnome-session-inhibit.c:108 #, c-format -msgid "Unknown inhibit argument: %s" -msgstr "Argument necunoscut de blocare: %s" - -#: tools/gnome-session-inhibit.c:288 +msgid "" +"%s [OPTION…] COMMAND\n" +"\n" +"Execute COMMAND while inhibiting some session functionality.\n" +"\n" +" -h, --help Show this help\n" +" --version Show program version\n" +" --app-id ID The application id to use\n" +" when inhibiting (optional)\n" +" --reason REASON The reason for inhibiting (optional)\n" +" --inhibit ARG Things to inhibit, colon-separated list of:\n" +" logout, switch-user, suspend, idle, automount\n" +" --inhibit-only Do not launch COMMAND and wait forever instead\n" +" -l, --list List the existing inhibitions, and exit\n" +"\n" +"If no --inhibit option is specified, idle is assumed.\n" +msgstr "" +"%s [OPȚIUNE…] COMANDĂ\n" +"\n" +"Execută COMANDA în timp ce se inhibă unele funcționalități ale sesiunii.\n" +"\n" +" -h, --help Arată acest ajutor\n" +" --version Arată versiunea programului\n" +" --app-id ID ID-ul aplicației de utilizat\n" +" când se inhibă (opțional)\n" +" --reason MOTIV Motivul pentru inhibare (opțional)\n" +" --inhibit ARG Lucruri de inhibat, listă separată prin două puncte de:\n" +" deautentificare, comutare utilizator, suspendă, în " +"așteptare, automontare\n" +" --inhibit-only Nu lansa COMANDA și așteaptă pentru totdeauna în schimb\n" +" -l, --list Listează inhibițiile existente, și ieși\n" +"\n" +"Dacă nicio opțiune --inhibit option nu este specificată, în așteptare este " +"presupusă.\n" + +#: tools/gnome-session-inhibit.c:282 #, c-format -msgid "Failed to execute %s: %m\n" -msgstr "Eroare la executarea %s: %m\n" - -#: tools/gnome-session-inhibit.c:360 -msgid "Show program version" -msgstr "Arată versiunea programului" - -#: tools/gnome-session-inhibit.c:361 -msgid "The application id to use when inhibiting (optional)" -msgstr "ID-ul programului care va fi utilizat pentru blocare (facultativ)" - -#: tools/gnome-session-inhibit.c:361 -msgid "ID" -msgstr "ID" +msgid "Error while creating pipe: %s\n" +msgstr "Eroare în timpul creării conductei: %s\n" -#: tools/gnome-session-inhibit.c:362 -msgid "The reason for inhibiting (optional)" -msgstr "Motivul blocării (facultativ)" +#: tools/gnome-session-inhibit.c:299 +msgid "Failure reading pipe\n" +msgstr "Eșec la citirea conductei\n" -#: tools/gnome-session-inhibit.c:362 -msgid "REASON" -msgstr "MOTIVUL" - -#: tools/gnome-session-inhibit.c:363 -msgid "Add action to inhibit (repeatable)" -msgstr "Adaugă acțiunea de blocat (repetabilă)" - -#: tools/gnome-session-inhibit.c:363 -msgid "ACTION" -msgstr "ACȚIUNE" - -#: tools/gnome-session-inhibit.c:364 -msgid "Do not launch COMMAND and wait forever instead" -msgstr "Nu lansa COMANDA ci așteaptă pentru totdeauna" - -#: tools/gnome-session-inhibit.c:365 -msgid "List the existing inhibitions, and exit" -msgstr "Afișează blocările existente, și închide" - -#: tools/gnome-session-inhibit.c:377 -msgid "COMMAND" -msgstr "COMANDA" - -#: tools/gnome-session-inhibit.c:383 -msgid "Execute COMMAND while inhibiting some session functionality." -msgstr "" -"Execută COMANDA în timpul blocării anumitor caracteristici ale sesiunii." - -#: tools/gnome-session-inhibit.c:384 -msgid "" -"Valid ACTION values are: logout, switch-user, suspend, idle, and automount.\n" -"If no --inhibit options are specified, idle is assumed." -msgstr "" -"Valorile ACȚIUNE valabile sunt: logout, switch-user, suspend, idle și " -"automount.\n" -"Dacă nu este specificată nicio opțiune --inhibit, va fi folosit idle." +#: tools/gnome-session-inhibit.c:303 +#, c-format +msgid "Failed to execute %s\n" +msgstr "Eroare la executarea %s\n" -#: tools/gnome-session-inhibit.c:408 -msgid "Neither COMMAND nor --inhibit-only were specified." -msgstr "Nu au fost specificate nici COMANDA, nici --inhibit-only." +#: tools/gnome-session-inhibit.c:376 tools/gnome-session-inhibit.c:386 +#: tools/gnome-session-inhibit.c:396 +#, c-format +msgid "%s requires an argument\n" +msgstr "%s necesită un argument\n" #: tools/gnome-session-quit.c:50 msgid "Log out" -msgstr "Termină sesiunea" +msgstr "Închide sesiunea" #: tools/gnome-session-quit.c:51 msgid "Power off" @@ -224,4 +349,397 @@ msgstr "Nu s-a putut realiza conectarea la administratorul de sesiuni" #: tools/gnome-session-quit.c:198 msgid "Program called with conflicting options" -msgstr "Program pornit cu opțiuni care intră în conflict" +msgstr "Program apelat cu opțiuni care intră în conflict" + +#: tools/gnome-session-selector.c:61 +#, c-format +msgid "Session %d" +msgstr "Sesiunea %d" + +#: tools/gnome-session-selector.c:107 +msgid "" +"Session names are not allowed to start with “.” or contain “/” characters" +msgstr "" +"Nu este permis ca numele sesiunilor să înceapă cu „.” sau să conțină " +"caracterul „/”" + +#: tools/gnome-session-selector.c:111 +msgid "Session names are not allowed to start with “.”" +msgstr "Nu este permis ca numele sesiunilor să înceapă cu „.”" + +#: tools/gnome-session-selector.c:115 +msgid "Session names are not allowed to contain “/” characters" +msgstr "Nu este permis ca numele sesiunilor să conțină caracterul „/”" + +#: tools/gnome-session-selector.c:123 +#, c-format +msgid "A session named “%s” already exists" +msgstr "O sesiune numită „%s” există deja" + +#~ msgid "Use systemd session management" +#~ msgstr "Utilizează administrarea de sesiune systemd" + +#~ msgid "Use builtin session management (rather than the systemd based one)" +#~ msgstr "" +#~ "Utilizează administrarea de sesiune încorporată (față de cea bazată pe " +#~ "systemd)" + +#~| msgid "This session logs you into GNOME" +#~ msgid "This session logs you into GNOME, using Wayland" +#~ msgstr "Această sesiune vă va autentifica în GNOME, folosind Wayland" + +#~ msgid "Additional startup _programs:" +#~ msgstr "_Programe la pornire adiționale:" + +#~ msgid "Startup Programs" +#~ msgstr "Programe la pornire" + +#~ msgid "_Automatically remember running applications when logging out" +#~ msgstr "Memorează _automat aplicațiile ce rulează la ieșire" + +#~| msgid "_Remember Currently Running Application" +#~ msgid "_Remember Currently Running Applications" +#~ msgstr "Memo_rează aplicațiile ce rulează în acest moment" + +#~ msgid "Options" +#~ msgstr "Opțiuni" + +#~ msgid "Browse…" +#~ msgstr "Navighează…" + +#~ msgid "Comm_ent:" +#~ msgstr "Com_entariu:" + +#~ msgid "Co_mmand:" +#~ msgstr "Co_mandă:" + +#~ msgid "_Name:" +#~ msgstr "_Nume:" + +#~ msgid "Select Command" +#~ msgstr "Alegere comandă" + +#~ msgid "Add Startup Program" +#~ msgstr "Adaugă un program de pornit" + +# LG: aici e vorba de titlul unei ferestre. +#~ msgid "Edit Startup Program" +#~ msgstr "Editare program de pornit" + +#~ msgid "The startup command cannot be empty" +#~ msgstr "Comanda de pornire nu poate fi nulă" + +#~ msgid "The startup command is not valid" +#~ msgstr "Comanda de start nu este validă" + +#~ msgid "Enabled" +#~ msgstr "Activat" + +#~ msgid "Icon" +#~ msgstr "Iconiță" + +#~ msgid "Program" +#~ msgstr "Program" + +#~ msgid "Startup Applications Preferences" +#~ msgstr "Preferințe aplicații la pornire" + +#~ msgid "No name" +#~ msgstr "Fără nume" + +#~ msgid "No description" +#~ msgstr "Fără descriere" + +#~ msgid "Could not display help document" +#~ msgstr "Nu se poate afișa documentul de ajutor" + +#~ msgid "Some programs are still running:" +#~ msgstr "O parte din programe sunt încă în funcțiune:" + +#~ msgid "" +#~ "Waiting for the program to finish. Interrupting the program may cause " +#~ "you to lose work." +#~ msgstr "" +#~ "Se așteaptă terminarea programului. Întreruperea lui poate duce la " +#~ "pierderea modificărilor efectuate." + +#~ msgid "Choose what applications to start when you log in" +#~ msgstr "Alegeți aplicațiile ce vor porni după ce vă autentificați" + +#~ msgid "Startup Applications" +#~ msgstr "Aplicații pornite după autentificare" + +#~ msgid "File is not a valid .desktop file" +#~ msgstr "Fișierul nu este un fișier .desktop valid" + +#~ msgid "Unrecognized desktop file Version '%s'" +#~ msgstr "Versiunea „%s” a fișierului desktop este nerecunoscută" + +#~ msgid "Starting %s" +#~ msgstr "Se pornește %s" + +#~ msgid "Application does not accept documents on command line" +#~ msgstr "Aplicația nu acceptă documente în linia de comandă" + +#~ msgid "Unrecognized launch option: %d" +#~ msgstr "Opțiune de lansare nerecunoscută: %d" + +#~ msgid "Can't pass document URIs to a 'Type=Link' desktop entry" +#~ msgstr "" +#~ "Nu s-a putut trimite URI-ul documentului unei intrări desktop „Type=Link”" + +#~ msgid "Not a launchable item" +#~ msgstr "Nu este un element lansabil" + +#~ msgid "Disable connection to session manager" +#~ msgstr "Dezactivează conexiunea la administratorul sesiunii" + +#~ msgid "Specify file containing saved configuration" +#~ msgstr "Specifică fișierul ce conține configurația salvată" + +#~ msgid "FILE" +#~ msgstr "FIȘIER" + +#~ msgid "ID" +#~ msgstr "ID" + +#~ msgid "Session management options:" +#~ msgstr "Opțiuni de administrare a sesiunii:" + +#~ msgid "Show session management options" +#~ msgstr "Afișează opțiunile de administrare a sesiunii" + +#~ msgid "" +#~ "There is a problem with the configuration server.\n" +#~ "(%s exited with status %d)" +#~ msgstr "" +#~ "Există o problemă cu serverul de configurare.\n" +#~ "(%s a ieșit cu starea %d)" + +#~ msgid "Icon '%s' not found" +#~ msgstr "Iconița „%s” nu a fost găsită" + +#~ msgid "Unknown" +#~ msgstr "Necunoscut" + +#~ msgid "A program is still running:" +#~ msgstr "Un program este încă în funcțiune:" + +#~ msgid "Some programs are still running:" +#~ msgstr "Unele programe sunt încă în funcțiune:" + +#~ msgid "" +#~ "Waiting for programs to finish. Interrupting these programs may cause " +#~ "you to lose work." +#~ msgstr "" +#~ "Se așteaptă terminarea programelor. Întreruperea acestora poate duce la " +#~ "pierderea modificărilor efectuate." + +#~ msgid "Switch User Anyway" +#~ msgstr "Schimbă oricum utilizatorul" + +#~ msgid "Log Out Anyway" +#~ msgstr "Închide sesiunea oricum" + +#~ msgid "Suspend Anyway" +#~ msgstr "Suspendă oricum" + +#~ msgid "Hibernate Anyway" +#~ msgstr "Hibernează oricum" + +#~ msgid "Shut Down Anyway" +#~ msgstr "Oprește oricum" + +#~ msgid "Lock Screen" +#~ msgstr "Blochează ecranul" + +#~ msgid "Cancel" +#~ msgstr "Renunță" + +#~ msgid "You will be automatically logged out in %d second." +#~ msgid_plural "You will be automatically logged out in %d seconds." +#~ msgstr[0] "Sesiunea se va închide automat într-o secundă." +#~ msgstr[1] "Sesiunea se va închide automat în %d secunde." +#~ msgstr[2] "Sesiunea se va închide automat în %d de secunde." + +#~ msgid "This system will be automatically shut down in %d second." +#~ msgid_plural "This system will be automatically shut down in %d seconds." +#~ msgstr[0] "Sistemul va fi oprit automat într-o secundă." +#~ msgstr[1] "Sistemul va fi oprit automat în %d secunde." +#~ msgstr[2] "Sistemul va fi oprit automat în %d de secunde." + +#~ msgid "You are currently logged in as \"%s\"." +#~ msgstr "Sunteți autentificat ca „%s”." + +#~ msgid "Log out of this system now?" +#~ msgstr "Ieșiți de pe acest sistem acum?" + +#~ msgid "_Switch User" +#~ msgstr "_Schimbă utilizatorul" + +#~ msgid "Shut down this system now?" +#~ msgstr "Opriți sistemul acum?" + +#~ msgid "S_uspend" +#~ msgstr "S_uspendă" + +#~ msgid "_Hibernate" +#~ msgstr "_Hibernează" + +#~ msgid "_Restart" +#~ msgstr "_Repornește" + +#~ msgid "_Shut Down" +#~ msgstr "_Oprește" + +#~ msgid "Exited with code %d" +#~ msgstr "A ieșit cu codul %d" + +#~ msgid "Killed by signal %d" +#~ msgstr "Omorât de semnalul %d" + +#~ msgid "Stopped by signal %d" +#~ msgstr "Oprit de semnalul %d" + +#~ msgid "GNOME 3 Failed to Load" +#~ msgstr "GNOME 3 nu a reușit să pornească" + +#~ msgid "" +#~ "Unfortunately GNOME 3 failed to start properly and started in the " +#~ "fallback mode.\n" +#~ "\n" +#~ "This most likely means your system (graphics hardware or driver) is not " +#~ "capable of delivering the full GNOME 3 experience." +#~ msgstr "" +#~ "Din păcate, GNOME 3 nu a reușit să pornească corect și a pornit versiunea " +#~ "de rezervă.\n" +#~ "\n" +#~ "Cel mai probabil aceasta înseamnă că sistemul dumneavoastră (placa " +#~ "grafică sau driverul acesteia) nu poate oferi o experiență GNOME 3 " +#~ "completă." + +#~ msgid "Learn more about GNOME 3" +#~ msgstr "Aflați mai multe despre GNOME 3" + +#~ msgid "" +#~ "Unable to start login session (and unable to connect to the X server)" +#~ msgstr "" +#~ "Nu s-a putut porni sesiunea de start (iar conexiunea la serverul X nu a " +#~ "putut fi realizată)" + +#~ msgid "File Manager" +#~ msgstr "Administrator de fișiere" + +#~ msgid "List of applications that are part of the default session." +#~ msgstr "Lista aplicațiilor care fac parte din sesiunea implicită." + +#~ msgid "" +#~ "List of components that are required as part of the session. (Each " +#~ "element names a key under \"/desktop/gnome/session/" +#~ "required_components\"). The Startup Applications preferences tool will " +#~ "not normally allow users to remove a required component from the session, " +#~ "and the session manager will automatically add the required components " +#~ "back to the session at login time if they do get removed." +#~ msgstr "" +#~ "Lista componentelor care sunt necesare ca parte a sesiunii. (Fiecare " +#~ "element numește o cheie din „/desktop/gnome/session/" +#~ "required_components\".) Preferințele aplicațiilor de la pornire nu vor " +#~ "permite în mod normal ca utilizatorii să șteargă o componentă necesară " +#~ "din sesiune, iar administratorul sesiunii va adăuga automat componentele " +#~ "necesare în cazul în care vor fi șterse." + +#~ msgid "Panel" +#~ msgstr "Panou" + +#~ msgid "Required session components" +#~ msgstr "Componentele necesare sesiunii" + +#~ msgid "" +#~ "The file manager provides the desktop icons and allows you to interact " +#~ "with your saved files." +#~ msgstr "" +#~ "Administratorul de fișiere furnizează iconițele de pe desktop și permite " +#~ "interacțiunea cu fișierele salvate." + +#~ msgid "" +#~ "The number of minutes of inactivity before the session is considered idle." +#~ msgstr "" +#~ "Numărul de minute de inactivitate înainte ca sesiunea să fie considerată " +#~ "inactivă." + +#~ msgid "" +#~ "The panel provides the bar at the top or bottom of the screen containing " +#~ "menus, the window list, status icons, the clock, etc." +#~ msgstr "" +#~ "Panoul furnizează bara de deasupra ori dedesubtul ecranului ce conține " +#~ "meniuri, lista cu ferestre, iconițele de stare, ceasul etc." + +#~ msgid "" +#~ "The window manager is the program that draws the title bar and borders " +#~ "around windows, and allows you to move and resize windows." +#~ msgstr "" +#~ "Administratorul de ferestre este programul care afișează bara de titlu și " +#~ "marginile de jur împrejurul ferestrelor și permite mutarea sau " +#~ "redimensionarea acestora." + +#~ msgid "Time before session is considered idle" +#~ msgstr "Durata până ce sesiunea este considerată inactivă" + +#~ msgid "Window Manager" +#~ msgstr "Administrator de ferestre" + +#~ msgid "GConf key used to look up default session" +#~ msgstr "Cheia GConf folosită la căutarea sesiunii implicite" + +#~ msgid "Show shutdown dialog" +#~ msgstr "Afișează dialogul de oprire" + +#~ msgid "Use dialog boxes for errors" +#~ msgstr "Folosește casete de dialog pentru erori" + +#~ msgid "Set the current session name" +#~ msgstr "Definește numele sesiunii curente" + +#~ msgid "NAME" +#~ msgstr "NUME" + +#~ msgid "GNOME Settings Daemon Helper" +#~ msgstr "Ajutor serviciu configurări GNOME" + +#~ msgid "" +#~ "If enabled, gnome-session will save the session automatically. Otherwise, " +#~ "the logout dialog will have an option to save the session." +#~ msgstr "" +#~ "Dacă este activat, gnome-session va salva sesiunea automat. Altfel, " +#~ "dialogul de ieșire va avea o opțiune pentru salvarea sesiunii." + +#~ msgid "Preferred Image to use for login splash screen" +#~ msgstr "Imaginea preferată afișată după autentificare" + +#~ msgid "Show the splash screen" +#~ msgstr "Arată imaginea de pornire" + +#~ msgid "Show the splash screen when the session starts up" +#~ msgstr "Arată imaginea de pornire la intrarea în sesiune" + +#~ msgid "" +#~ "This is a relative path value based off the $datadir/pixmaps/ directory. " +#~ "Sub-directories and image names are valid values. Changing this value " +#~ "will effect the next session login." +#~ msgstr "" +#~ "Aceasta este o cale relativă bazată pe directorul $datadir/pixmaps/. " +#~ "Numele de subdirectoare și de imagini sunt valori valide. Efectul " +#~ "modificării acestei valori va fi vizibil la pornirea următoarei sesiuni." + +#~ msgid "" +#~ "Waiting for program to finish. Interrupting program may cause you to " +#~ "lose work." +#~ msgstr "" +#~ "Se așteaptă ca programul să termine. Întreruperea acestuia poate duce la " +#~ "pierderea activității de până acum." + +#~ msgid "- GNOME Splash Screen" +#~ msgstr "- Imagine de pornire GNOME" + +#~ msgid "GNOME Splash Screen" +#~ msgstr "Imagine pornire GNOME" diff --git a/po/sl.po b/po/sl.po index e15e6eda..a1f345b8 100644 --- a/po/sl.po +++ b/po/sl.po @@ -9,18 +9,18 @@ msgid "" msgstr "" "Project-Id-Version: gnome-session master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-session/issues\n" -"POT-Creation-Date: 2025-07-18 19:39+0000\n" -"PO-Revision-Date: 2025-07-19 11:32+0200\n" +"POT-Creation-Date: 2025-04-14 21:21+0000\n" +"PO-Revision-Date: 2025-04-15 18:46+0200\n" "Last-Translator: Martin Srebotnjak \n" "Language-Team: Slovenian GNOME Translation Team \n" "Language: sl_SI\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=4; plural=(n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n" -"%100==4 ? 3 : 0);\n" +"Plural-Forms: nplurals=4; plural=(n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || " +"n%100==4 ? 3 : 0);\n" "X-Poedit-SourceCharset: utf-8\n" -"X-Generator: Poedit 2.2.1\n" +"X-Generator: Poedit 3.4.2\n" #: data/gnome.desktop.in.in:3 data/gnome.session.desktop.in.in:3 msgid "GNOME" @@ -40,18 +40,38 @@ msgid "GNOME on Xorg" msgstr "GNOME na sistemu Xorg" #: data/org.gnome.SessionManager.gschema.xml:5 +msgid "Save sessions" +msgstr "Shrani seje" + +#: data/org.gnome.SessionManager.gschema.xml:6 +msgid "If enabled, gnome-session will save the session automatically." +msgstr "Izbrana možnost omogoči samodejno shranjevanje seje." + +#: data/org.gnome.SessionManager.gschema.xml:10 +msgid "Save this session" +msgstr "Shrani sejo" + +#: data/org.gnome.SessionManager.gschema.xml:11 +msgid "" +"When enabled, gnome-session will automatically save the next session at log " +"out even if auto saving is disabled." +msgstr "" +"Izbrana možnost omogoči samodejno shranjevanje seje ob odjavi, neglede na " +"nastavitev samodejnega shranjevanja." + +#: data/org.gnome.SessionManager.gschema.xml:15 msgid "Logout prompt" msgstr "Vnosno polje odjave" -#: data/org.gnome.SessionManager.gschema.xml:6 +#: data/org.gnome.SessionManager.gschema.xml:16 msgid "If enabled, gnome-session will prompt the user before ending a session." msgstr "Izbrana možnost omogoči prikaz vnosnega polja pred koncem seje." -#: data/org.gnome.SessionManager.gschema.xml:10 +#: data/org.gnome.SessionManager.gschema.xml:20 msgid "Show the fallback warning" msgstr "Pokaži opozorila programske povrnitve" -#: data/org.gnome.SessionManager.gschema.xml:11 +#: data/org.gnome.SessionManager.gschema.xml:21 msgid "" "If enabled, gnome-session will display a warning dialog after login if the " "session was automatically fallen back." @@ -59,47 +79,119 @@ msgstr "" "Izbrana možnost omogoči prikaz opozorila po prijavi, če je seja samodejno " "povrnjena." -#: gnome-session/gsm-manager.c:709 gnome-session/gsm-manager.c:1040 +#: gnome-session/gsm-fail-whale-dialog.c:90 +msgid "Oh no! Something has gone wrong." +msgstr "Prišlo je do nepričakovane napake." + +#: gnome-session/gsm-fail-whale-dialog.c:97 +msgid "" +"A problem has occurred and the system can’t recover. Please contact a system " +"administrator" +msgstr "" +"Prišlo je do napake, ki je ni mogoče samodejno odpraviti. Stopite v stik s " +"skrbnikom." + +#: gnome-session/gsm-fail-whale-dialog.c:99 +msgid "" +"A problem has occurred and the system can’t recover. All extensions have " +"been disabled as a precaution." +msgstr "" +"Prišlo je do napake, ki je ni mogoče samodejno odpraviti. Priporočljivo je " +"onemogočiti vse razširitve, dokler se napaka ne razreši." + +#: gnome-session/gsm-fail-whale-dialog.c:101 +msgid "" +"A problem has occurred and the system can’t recover.\n" +"Please log out and try again." +msgstr "" +"Prišlo je do napake, ki je ni mogoče samodejno odpraviti.\n" +"Odjavite se in poskusite znova." + +#: gnome-session/gsm-fail-whale-dialog.c:109 +msgid "_Log Out" +msgstr "_Odjava" + +#: gnome-session/gsm-fail-whale-dialog.c:153 gnome-session/main.c:479 +msgid "Enable debugging code" +msgstr "Omogoči razhroščevanje kode" + +#: gnome-session/gsm-fail-whale-dialog.c:154 +msgid "Allow logout" +msgstr "Dovoli odjavo" + +#: gnome-session/gsm-fail-whale-dialog.c:155 +msgid "Show extension warning" +msgstr "Pokaži opozorila razširitev" + +#: gnome-session/gsm-manager.c:1305 gnome-session/gsm-manager.c:2054 msgid "Not responding" msgstr "Ni odziva" -#: gnome-session/gsm-util.c:283 +#: gnome-session/gsm-util.c:431 msgid "_Log out" msgstr "_Odjavi" -#: gnome-session/leader-main.c:534 +#. It'd be really surprising to reach this code: if we're here, +#. * then the XSMP client already has set several XSMP +#. * properties. But it could still be that SmProgram is not set. +#. +#: gnome-session/gsm-xsmp-client.c:557 +msgid "Remembered Application" +msgstr "Zapomnjeni programi" + +#: gnome-session/gsm-xsmp-client.c:1216 +msgid "This program is blocking logout." +msgstr "Program zavira postopek odjave." + +#: gnome-session/gsm-xsmp-server.c:340 +msgid "" +"Refusing new client connection because the session is currently being shut " +"down\n" +msgstr "Povezave z odjemalci bodo zavrnjene, ker se trenutna seja izklaplja.\n" + +#: gnome-session/gsm-xsmp-server.c:613 +#, c-format +msgid "Could not create ICE listening socket: %s" +msgstr "Ni mogoče ustvariti vtiča za prisluh ICE: %s" + +#: gnome-session/main.c:476 +msgid "Running as systemd service" +msgstr "Zagnano kot storitev systemd" + +#: gnome-session/main.c:477 msgid "Override standard autostart directories" msgstr "Preskoči običajne mape samodejnega zagona" -#: gnome-session/leader-main.c:534 +#: gnome-session/main.c:477 msgid "AUTOSTART_DIR" msgstr "ZAČETNA_MAPA" -#: gnome-session/leader-main.c:535 gnome-session/service-main.c:147 +#: gnome-session/main.c:478 msgid "Session to use" msgstr "Seja za uporabo" -#: gnome-session/leader-main.c:535 gnome-session/service-main.c:147 +#: gnome-session/main.c:478 msgid "SESSION_NAME" msgstr "IME_SEJE" -#: gnome-session/leader-main.c:536 -msgid "Enable debugging code" -msgstr "Omogoči razhroščevanje kode" +#: gnome-session/main.c:480 +msgid "Do not load user-specified applications" +msgstr "Ne naloži uporabniško določenih programov" -#: gnome-session/leader-main.c:537 +#: gnome-session/main.c:481 msgid "Version of this application" msgstr "Različica programa" -#: gnome-session/leader-main.c:538 +#. Translators: the 'fail whale' is the black dialog we show when something goes seriously wrong +#: gnome-session/main.c:483 +msgid "Show the fail whale dialog for testing" +msgstr "Pokaži pogovorno okno napak med preizkušanjem" + +#: gnome-session/main.c:484 msgid "This option is ignored" msgstr "Ta možnost je prezrta" -#: gnome-session/leader-main.c:539 -msgid "Don't re-exec into a login shell" -msgstr "Ne izvedi ponovno v lupini za prijavo" - -#: gnome-session/leader-main.c:560 gnome-session/service-main.c:156 +#: gnome-session/main.c:516 msgid " — the GNOME session manager" msgstr " – upravljalnik seje GNOME" @@ -125,11 +217,10 @@ msgstr "Ponovno zaženi storitev dbus.service, če je že zagnana" #: tools/gnome-session-ctl.c:251 msgid "" -"Run from ExecStopPost to start gnome-session-shutdown.target on service " -"failure" +"Run from ExecStopPost to start gnome-session-failed.target on service failure" msgstr "" "Zaženi iz ExecStopPost za zagon gnome-session-failed.target ob spodleteli " -"storitvi" +"seji" #: tools/gnome-session-ctl.c:281 msgid "Program needs exactly one parameter" diff --git a/po/tr.po b/po/tr.po index 30729c40..2943295c 100644 --- a/po/tr.po +++ b/po/tr.po @@ -16,14 +16,14 @@ # Muhammet Kara , 2011-2015. # Çağatay Yiğit Şahin , 2017, 2018. # Sabri Ünal , 2019, 2022-2024. -# Emin Tufan Çetin , 2017-2025. +# Emin Tufan Çetin , 2017-2024. # msgid "" msgstr "" "Project-Id-Version: gnome-session\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-session/issues\n" -"POT-Creation-Date: 2025-08-27 22:11+0000\n" -"PO-Revision-Date: 2025-08-28 14:07+0300\n" +"POT-Creation-Date: 2024-07-29 23:21+0000\n" +"PO-Revision-Date: 2024-05-07 12:06+0300\n" "Last-Translator: Sabri Ünal \n" "Language-Team: Turkish \n" "Language: tr\n" @@ -31,39 +31,71 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Poedit 3.6\n" +"X-Generator: Poedit 3.4.2\n" -#: data/gnome.desktop.in.in:2 data/gnome.session.desktop:2 +#: data/gnome-custom-session.desktop.in.in:3 +msgid "Custom" +msgstr "Özel" + +#: data/gnome-custom-session.desktop.in.in:4 +msgid "This entry lets you select a saved session" +msgstr "Bu girdi, kaydedilmiş oturum seçmenizi sağlar" + +#: data/gnome.desktop.in.in:3 data/gnome.session.desktop.in.in:3 msgid "GNOME" msgstr "GNOME" -#: data/gnome.desktop.in.in:3 data/gnome-wayland.desktop.in.in:3 -#: data/gnome-xorg.desktop.in.in:3 +#: data/gnome.desktop.in.in:4 data/gnome-wayland.desktop.in.in:4 +#: data/gnome-xorg.desktop.in.in:4 msgid "This session logs you into GNOME" msgstr "Bu oturum GNOME’a girmenizi sağlar" -#: data/gnome-wayland.desktop.in.in:2 +#: data/gnome-dummy.session.desktop.in.in:3 +msgid "GNOME dummy" +msgstr "GNOME dummy" + +#: data/gnome-wayland.desktop.in.in:3 msgid "GNOME on Wayland" msgstr "Wayland üzerinde GNOME" -#: data/gnome-xorg.desktop.in.in:2 +#: data/gnome-xorg.desktop.in.in:3 msgid "GNOME on Xorg" msgstr "Xorg üzerinde GNOME" #: data/org.gnome.SessionManager.gschema.xml:5 +msgid "Save sessions" +msgstr "Oturumları kaydet" + +#: data/org.gnome.SessionManager.gschema.xml:6 +msgid "If enabled, gnome-session will save the session automatically." +msgstr "Etkinse, gnome-session, oturumu kendiliğinden kaydedecektir." + +#: data/org.gnome.SessionManager.gschema.xml:10 +msgid "Save this session" +msgstr "Bu oturumu kaydet" + +#: data/org.gnome.SessionManager.gschema.xml:11 +msgid "" +"When enabled, gnome-session will automatically save the next session at log " +"out even if auto saving is disabled." +msgstr "" +"Etkinleştirildiğinde; gnome-session, kendiliğinden kaydetme devre dışı olsa " +"bile oturum kapatıldığında sonraki oturumu kendiliğinden kaydedecektir." + +#: data/org.gnome.SessionManager.gschema.xml:15 msgid "Logout prompt" msgstr "Oturum kapatma istemi" -#: data/org.gnome.SessionManager.gschema.xml:6 +#: data/org.gnome.SessionManager.gschema.xml:16 msgid "If enabled, gnome-session will prompt the user before ending a session." msgstr "" "Etkinse, gnome-session, oturumu bitirmeden önce kullanıcıya soracaktır." -#: data/org.gnome.SessionManager.gschema.xml:10 +#: data/org.gnome.SessionManager.gschema.xml:20 msgid "Show the fallback warning" msgstr "Yedeğe dönüş uyarısını göster" -#: data/org.gnome.SessionManager.gschema.xml:11 +#: data/org.gnome.SessionManager.gschema.xml:21 msgid "" "If enabled, gnome-session will display a warning dialog after login if the " "session was automatically fallen back." @@ -71,39 +103,142 @@ msgstr "" "Etkinse, gnome-session, oturum kendiliğinden yedeğe düşmüşse girişin " "ardından uyarı penceresi gösterecektir." -#: gnome-session/gsm-manager.c:709 gnome-session/gsm-manager.c:1040 +#: data/session-selector.ui:15 +msgid "Custom Session" +msgstr "Özel Oturum" + +#: data/session-selector.ui:50 tools/gnome-session-selector.c:102 +msgid "Please select a custom session to run" +msgstr "Lütfen çalıştırılacak özel oturum seçin" + +#: data/session-selector.ui:105 +msgid "_New Session" +msgstr "_Yeni Oturum" + +#: data/session-selector.ui:119 +msgid "_Remove Session" +msgstr "Oturumu _Kaldır" + +#: data/session-selector.ui:133 +msgid "Rena_me Session" +msgstr "Oturumu Yeniden _Adlandır" + +#: data/session-selector.ui:168 +msgid "_Continue" +msgstr "_Sürdür" + +#: gnome-session/gsm-fail-whale-dialog.c:318 +msgid "Oh no! Something has gone wrong." +msgstr "Hayda! Bir şeyler yanlış gitti." + +#: gnome-session/gsm-fail-whale-dialog.c:325 +msgid "" +"A problem has occurred and the system can’t recover. Please contact a system " +"administrator" +msgstr "" +"Sorun oluştu ve sistem kurtarılamıyor. Lütfen sistem yöneticisine başvurun" + +#: gnome-session/gsm-fail-whale-dialog.c:327 +msgid "" +"A problem has occurred and the system can’t recover. All extensions have " +"been disabled as a precaution." +msgstr "" +"Sorun oluştu ve sistem kurtarılamıyor. Önlem olarak tüm uzantılar devre dışı " +"bırakıldı." + +#: gnome-session/gsm-fail-whale-dialog.c:329 +msgid "" +"A problem has occurred and the system can’t recover.\n" +"Please log out and try again." +msgstr "" +"Sorun oluştu ve sistem kurtarılamıyor.\n" +"Lütfen oturumu kapatıp yeniden deneyin." + +#: gnome-session/gsm-fail-whale-dialog.c:344 +msgid "_Log Out" +msgstr "_Oturumu Kapat" + +#: gnome-session/gsm-fail-whale-dialog.c:412 gnome-session/main.c:505 +msgid "Enable debugging code" +msgstr "Kod hatası ayıklamayı etkinleştir" + +#: gnome-session/gsm-fail-whale-dialog.c:413 +msgid "Allow logout" +msgstr "Oturumu kapatmaya izin ver" + +#: gnome-session/gsm-fail-whale-dialog.c:414 +msgid "Show extension warning" +msgstr "Uzantı uyarısını göster" + +#: gnome-session/gsm-manager.c:1301 gnome-session/gsm-manager.c:2050 msgid "Not responding" msgstr "Yanıtlamıyor" -#: gnome-session/gsm-util.c:167 +#: gnome-session/gsm-util.c:431 msgid "_Log out" msgstr "_Oturumu kapat" -#: gnome-session/leader-main.c:181 gnome-session/service-main.c:148 +#. It'd be really surprising to reach this code: if we're here, +#. * then the XSMP client already has set several XSMP +#. * properties. But it could still be that SmProgram is not set. +#. +#: gnome-session/gsm-xsmp-client.c:557 +msgid "Remembered Application" +msgstr "Anımsanan Uygulama" + +#: gnome-session/gsm-xsmp-client.c:1216 +msgid "This program is blocking logout." +msgstr "Bu program, oturumu kapatmayı engelliyor." + +#: gnome-session/gsm-xsmp-server.c:340 +msgid "" +"Refusing new client connection because the session is currently being shut " +"down\n" +msgstr "Oturum şu anda kapatıldığından yeni istemci bağlantısı reddediliyor\n" + +#: gnome-session/gsm-xsmp-server.c:607 +#, c-format +msgid "Could not create ICE listening socket: %s" +msgstr "ICE dinleme soketi oluşturulamadı: %s" + +#: gnome-session/main.c:502 +msgid "Running as systemd service" +msgstr "systemd hizmeti olarak çalışıyor" + +#: gnome-session/main.c:503 +msgid "Override standard autostart directories" +msgstr "Standart kendiliğinden başlat dizinlerinin yerine geç" + +#: gnome-session/main.c:503 +msgid "AUTOSTART_DIR" +msgstr "KENDILIGINDANBASLAT_DIZINI" + +#: gnome-session/main.c:504 msgid "Session to use" msgstr "Kullanılacak oturum" -#: gnome-session/leader-main.c:181 gnome-session/service-main.c:148 +#: gnome-session/main.c:504 msgid "SESSION_NAME" msgstr "OTURUM_ADI" -#: gnome-session/leader-main.c:182 -msgid "Enable debugging code" -msgstr "Kod hatası ayıklamayı etkinleştir" +#: gnome-session/main.c:506 +msgid "Do not load user-specified applications" +msgstr "Kullanıcıya özel programları yükleme" -#: gnome-session/leader-main.c:183 +#: gnome-session/main.c:507 msgid "Version of this application" msgstr "Bu uygulamanın sürümü" -#: gnome-session/leader-main.c:184 -msgid "This option is ignored" -msgstr "Bu seçenek yok sayıldı" +#. Translators: the 'fail whale' is the black dialog we show when something goes seriously wrong +#: gnome-session/main.c:509 +msgid "Show the fail whale dialog for testing" +msgstr "Sınama için büyük hata iletişim penceresini göster" -#: gnome-session/leader-main.c:185 -msgid "Don't re-exec into a login shell" -msgstr "Oturum açma kabuğuna yeniden girme" +#: gnome-session/main.c:510 +msgid "Disable hardware acceleration check" +msgstr "Donanım hızlandırma denetimini devre dışı bırak" -#: gnome-session/leader-main.c:203 gnome-session/service-main.c:157 +#: gnome-session/main.c:542 msgid " — the GNOME session manager" msgstr " — GNOME oturum yöneticisi" @@ -128,82 +263,67 @@ msgstr "Çalışıyorsa dbus.service’i yeniden başlat" #: tools/gnome-session-ctl.c:251 msgid "" -"Run from ExecStopPost to start gnome-session-shutdown.target on service " -"failure" -msgstr "" -"Hizmet hatasında gnome-session-shutdown.target’ı ExecStopPost’tan başlat" +"Run from ExecStopPost to start gnome-session-failed.target on service failure" +msgstr "Hizmet hatasında gnome-session-failed.target’ı ExecStopPost’tan başlat" #: tools/gnome-session-ctl.c:281 msgid "Program needs exactly one parameter" msgstr "Program tam olarak bir parametreye gereksiniyor" -#: tools/gnome-session-inhibit.c:69 +#: tools/gnome-session-inhibit.c:108 #, c-format -msgid "Unknown inhibit argument: %s" -msgstr "Bilinmeyen önleme argümanı: %s" - -#: tools/gnome-session-inhibit.c:289 +msgid "" +"%s [OPTION…] COMMAND\n" +"\n" +"Execute COMMAND while inhibiting some session functionality.\n" +"\n" +" -h, --help Show this help\n" +" --version Show program version\n" +" --app-id ID The application id to use\n" +" when inhibiting (optional)\n" +" --reason REASON The reason for inhibiting (optional)\n" +" --inhibit ARG Things to inhibit, colon-separated list of:\n" +" logout, switch-user, suspend, idle, automount\n" +" --inhibit-only Do not launch COMMAND and wait forever instead\n" +" -l, --list List the existing inhibitions, and exit\n" +"\n" +"If no --inhibit option is specified, idle is assumed.\n" +msgstr "" +"%s [SEÇENEK…] KOMUT\n" +"\n" +"Bazı oturum işlevlerini kısıtlayarak KOMUT çalıştır.\n" +"\n" +" -h, --help Bu yardımı göster\n" +" --version Program sürümünü göster\n" +" --app-id ID Kısıtlama yapılırken kullanılacak\n" +" uygulama ID’si (isteğe bağlı)\n" +" --reason REASON Kısıtlama nedeni (isteğe bağlı)\n" +" --inhibit ARG Kısıtlamaların virgülle ayrılmış listesi:\n" +" logout, switch-user, suspend, idle, automount\n" +" --inhibit-only KOMUT’u çalıştırmak yerine sürekli bekle\n" +" -l, --list Var olan kısıtlamaları göster ve çık\n" +"\n" +"Eğer hiç --inhibit seçeneği verilmediyse, idle kısıtı kabul edilir.\n" + +#: tools/gnome-session-inhibit.c:282 #, c-format -msgid "Failed to execute %s: %m\n" -msgstr "%s çalıştırılamadı: %m\n" - -#: tools/gnome-session-inhibit.c:361 -msgid "Show program version" -msgstr "Program sürümünü göster" - -#: tools/gnome-session-inhibit.c:362 -msgid "The application id to use when inhibiting (optional)" -msgstr "Önlenirken kullanılacak uygulama kimliği (isteğe bağlı)" - -#: tools/gnome-session-inhibit.c:362 -msgid "ID" -msgstr "KİMLİK" - -#: tools/gnome-session-inhibit.c:363 -msgid "The reason for inhibiting (optional)" -msgstr "Önleme nedeni (isteğe bağlı)" - -#: tools/gnome-session-inhibit.c:363 -msgid "REASON" -msgstr "NEDEN" +msgid "Error while creating pipe: %s\n" +msgstr "Boru oluşturulurken hata: %s\n" -#: tools/gnome-session-inhibit.c:364 -msgid "Add action to inhibit (repeatable)" -msgstr "Önlemeye eylem ekle (yinelenebilir)" +#: tools/gnome-session-inhibit.c:299 +msgid "Failure reading pipe\n" +msgstr "Boru okuma hatası\n" -#: tools/gnome-session-inhibit.c:364 -msgid "ACTION" -msgstr "EYLEM" - -#: tools/gnome-session-inhibit.c:365 -msgid "Do not launch COMMAND and wait forever instead" -msgstr "KOMUTU çalıştırma ve bunun yerine sonsuza dek bekle" - -#: tools/gnome-session-inhibit.c:366 -msgid "List the existing inhibitions, and exit" -msgstr "Var olan önlemeleri listele ve çık" - -#: tools/gnome-session-inhibit.c:378 -msgid "COMMAND" -msgstr "KOMUT" - -#: tools/gnome-session-inhibit.c:384 -msgid "Execute COMMAND while inhibiting some session functionality." -msgstr "Bazı oturum işlevselliğini önlerken KOMUTU çalıştır" - -#: tools/gnome-session-inhibit.c:385 -msgid "" -"Valid ACTION values are: logout, switch-user, suspend, idle, and automount.\n" -"If no --inhibit options are specified, idle is assumed." -msgstr "" -"Geçerli EYLEM değerleri şunlardır: logout (oturumu kapat), switch-user " -"(kullanıcı değiştir), suspend (askıya al), idle (boşta) ve automount " -"(kendiliğinden bağla).\n" -"Eğer herhangi bir --inhibit seçeneği belirtilmezse idle (boşta) kullanılır." +#: tools/gnome-session-inhibit.c:303 +#, c-format +msgid "Failed to execute %s\n" +msgstr "%s çalıştırılamadı\n" -#: tools/gnome-session-inhibit.c:409 -msgid "Neither COMMAND nor --inhibit-only were specified." -msgstr "Ne KOMUT ne de --inhibit-only belirtildi." +#: tools/gnome-session-inhibit.c:376 tools/gnome-session-inhibit.c:386 +#: tools/gnome-session-inhibit.c:396 +#, c-format +msgid "%s requires an argument\n" +msgstr "%s için parametre eksik\n" #: tools/gnome-session-quit.c:50 msgid "Log out" @@ -215,11 +335,11 @@ msgstr "Bilgisayarı kapat" #: tools/gnome-session-quit.c:52 msgid "Reboot" -msgstr "Yeniden başlat" +msgstr "Yeniden Başlat" #: tools/gnome-session-quit.c:53 msgid "Ignoring any existing inhibitors" -msgstr "Var olan tüm önleyiciler yok sayılıyor" +msgstr "Var olan tüm engelleyiciler yok sayılıyor" #: tools/gnome-session-quit.c:54 msgid "Don’t prompt for user confirmation" @@ -233,49 +353,25 @@ msgstr "Oturum yöneticisine bağlanılamadı" msgid "Program called with conflicting options" msgstr "Program çakışan seçeneklerle çağırıldı" -#~ msgid "Oh no! Something has gone wrong." -#~ msgstr "Hayda! Bir şeyler yanlış gitti." - -#~ msgid "" -#~ "A problem has occurred and the system can’t recover. Please contact a " -#~ "system administrator" -#~ msgstr "" -#~ "Sorun oluştu ve sistem kurtarılamıyor. Lütfen sistem yöneticisine başvurun" - -#~ msgid "" -#~ "A problem has occurred and the system can’t recover. All extensions have " -#~ "been disabled as a precaution." -#~ msgstr "" -#~ "Sorun oluştu ve sistem kurtarılamıyor. Önlem olarak tüm uzantılar devre " -#~ "dışı bırakıldı." - -#~ msgid "" -#~ "A problem has occurred and the system can’t recover.\n" -#~ "Please log out and try again." -#~ msgstr "" -#~ "Sorun oluştu ve sistem kurtarılamıyor.\n" -#~ "Lütfen oturumu kapatıp yeniden deneyin." - -#~ msgid "_Log Out" -#~ msgstr "_Oturumu Kapat" - -#~ msgid "Allow logout" -#~ msgstr "Oturumu kapatmaya izin ver" - -#~ msgid "Show extension warning" -#~ msgstr "Uzantı uyarısını göster" - -#~ msgid "Running as systemd service" -#~ msgstr "systemd hizmeti olarak çalışıyor" +#: tools/gnome-session-selector.c:61 +#, c-format +msgid "Session %d" +msgstr "%d Oturumu" -#~ msgid "Override standard autostart directories" -#~ msgstr "Standart kendiliğinden başlat dizinlerinin yerine geç" +#: tools/gnome-session-selector.c:107 +msgid "" +"Session names are not allowed to start with “.” or contain “/” characters" +msgstr "Oturum adları “.” ile başlayamaz ve “/” karakterini içeremez" -#~ msgid "AUTOSTART_DIR" -#~ msgstr "KENDILIGINDANBASLAT_DIZINI" +#: tools/gnome-session-selector.c:111 +msgid "Session names are not allowed to start with “.”" +msgstr "Oturum adları “.” ile başlayamaz" -#~ msgid "Do not load user-specified applications" -#~ msgstr "Kullanıcıya özel programları yükleme" +#: tools/gnome-session-selector.c:115 +msgid "Session names are not allowed to contain “/” characters" +msgstr "Oturum adları “/” karakterini içeremez" -#~ msgid "Show the fail whale dialog for testing" -#~ msgstr "Sınama için büyük hata iletişim penceresini göster" +#: tools/gnome-session-selector.c:123 +#, c-format +msgid "A session named “%s” already exists" +msgstr "“%s” adında oturum var" diff --git a/tools/gnome-session-ctl.c b/tools/gnome-session-ctl.c index e413cd52..edae5062 100644 --- a/tools/gnome-session-ctl.c +++ b/tools/gnome-session-ctl.c @@ -248,7 +248,7 @@ main (int argc, char *argv[]) { "monitor", '\0', 0, G_OPTION_ARG_NONE, &opt_monitor, N_("Start gnome-session-shutdown.target when receiving EOF or a single byte on stdin"), NULL }, { "signal-init", '\0', 0, G_OPTION_ARG_NONE, &opt_signal_init, N_("Signal initialization done to gnome-session"), NULL }, { "restart-dbus", '\0', 0, G_OPTION_ARG_NONE, &opt_restart_dbus, N_("Restart dbus.service if it is running"), NULL }, - { "exec-stop-check", '\0', 0, G_OPTION_ARG_NONE, &opt_exec_stop_check, N_("Run from ExecStopPost to start gnome-session-shutdown.target on service failure"), NULL }, + { "exec-stop-check", '\0', 0, G_OPTION_ARG_NONE, &opt_exec_stop_check, N_("Run from ExecStopPost to start gnome-session-failed.target on service failure"), NULL }, { NULL }, }; @@ -296,7 +296,7 @@ main (int argc, char *argv[]) } else if (opt_exec_stop_check) { /* Start failed target if the restart limit was hit */ if (g_strcmp0 ("start-limit-hit", g_getenv ("SERVICE_RESULT")) == 0) { - do_start_unit ("gnome-session-shutdown.target", "fail"); + do_start_unit ("gnome-session-failed.target", "fail"); } } else { g_assert_not_reached ();