From 79ead5309fe21e77cfe58adc6a9340953c2d52f4 Mon Sep 17 00:00:00 2001 From: Richard Hughes Date: Fri, 21 Sep 2012 11:56:53 +0100 Subject: [PATCH] power and media-keys: Use logind for suspending and rebooting the system Use the new logind features to suspend and resume but making sure we opt out of logind handling the sleep and power keys, and also inhibiting for lid close auto-suspend if there is an external monitor connected. Also use a delay inihibit for logind so that we can do actions on suspend like blanking the screen using the screensaver and also poking the screensaver on resume. https://bugzilla.gnome.org/show_bug.cgi?id=680689 --- plugins/common/Makefile.am | 4 +- plugins/common/gsd-power-helper.c | 203 -------- plugins/common/gsd-power-helper.h | 35 -- plugins/media-keys/gsd-media-keys-manager.c | 156 +++++-- plugins/power/gsd-power-manager.c | 699 +++++++++++++++++++--------- 5 files changed, 595 insertions(+), 502 deletions(-) delete mode 100644 plugins/common/gsd-power-helper.c delete mode 100644 plugins/common/gsd-power-helper.h diff --git a/plugins/common/Makefile.am b/plugins/common/Makefile.am index 7e50db4..b0e907c 100644 --- a/plugins/common/Makefile.am +++ b/plugins/common/Makefile.am @@ -6,9 +6,7 @@ libcommon_la_SOURCES = \ gsd-keygrab.c \ gsd-keygrab.h \ gsd-input-helper.c \ - gsd-input-helper.h \ - gsd-power-helper.c \ - gsd-power-helper.h + gsd-input-helper.h libcommon_la_CPPFLAGS = \ $(AM_CPPFLAGS) diff --git a/plugins/common/gsd-power-helper.c b/plugins/common/gsd-power-helper.c deleted file mode 100644 index 27d0eda..0000000 --- a/plugins/common/gsd-power-helper.c +++ /dev/null @@ -1,203 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- - * - * Copyright (C) 2012 Bastien Nocera - * - * 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, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * - */ - -#include "config.h" - -#include "gsd-power-helper.h" - -#define SYSTEMD_DBUS_NAME "org.freedesktop.login1" -#define SYSTEMD_DBUS_PATH "/org/freedesktop/login1" -#define SYSTEMD_DBUS_INTERFACE "org.freedesktop.login1.Manager" - -#define CONSOLEKIT_DBUS_NAME "org.freedesktop.ConsoleKit" -#define CONSOLEKIT_DBUS_PATH_MANAGER "/org/freedesktop/ConsoleKit/Manager" -#define CONSOLEKIT_DBUS_INTERFACE_MANAGER "org.freedesktop.ConsoleKit.Manager" - -#ifdef HAVE_SYSTEMD -static void -systemd_stop (void) -{ - GDBusConnection *bus; - - bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, NULL); - g_dbus_connection_call (bus, - SYSTEMD_DBUS_NAME, - SYSTEMD_DBUS_PATH, - SYSTEMD_DBUS_INTERFACE, - "PowerOff", - g_variant_new ("(b)", FALSE), - NULL, 0, G_MAXINT, NULL, NULL, NULL); - g_object_unref (bus); -} - -static void -systemd_suspend (void) -{ - GDBusConnection *bus; - - bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, NULL); - g_dbus_connection_call (bus, - SYSTEMD_DBUS_NAME, - SYSTEMD_DBUS_PATH, - SYSTEMD_DBUS_INTERFACE, - "Suspend", - g_variant_new ("(b)", TRUE), - NULL, 0, G_MAXINT, NULL, NULL, NULL); - g_object_unref (bus); -} - -static void -systemd_hibernate (void) -{ - GDBusConnection *bus; - - bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, NULL); - g_dbus_connection_call (bus, - SYSTEMD_DBUS_NAME, - SYSTEMD_DBUS_PATH, - SYSTEMD_DBUS_INTERFACE, - "Hibernate", - g_variant_new ("(b)", TRUE), - NULL, 0, G_MAXINT, NULL, NULL, NULL); - g_object_unref (bus); -} - -#else /* HAVE_SYSTEMD */ - -static void -consolekit_stop_cb (GObject *source_object, - GAsyncResult *res, - gpointer user_data) -{ - GVariant *result; - GError *error = NULL; - - result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), - res, - &error); - if (result == NULL) { - g_warning ("couldn't stop using ConsoleKit: %s", - error->message); - g_error_free (error); - } else { - g_variant_unref (result); - } -} - -static void -consolekit_stop (void) -{ - GError *error = NULL; - GDBusProxy *proxy; - - /* power down the machine in a safe way */ - proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, - G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, - NULL, - CONSOLEKIT_DBUS_NAME, - CONSOLEKIT_DBUS_PATH_MANAGER, - CONSOLEKIT_DBUS_INTERFACE_MANAGER, - NULL, &error); - if (proxy == NULL) { - g_warning ("cannot connect to ConsoleKit: %s", - error->message); - g_error_free (error); - return; - } - g_dbus_proxy_call (proxy, - "Stop", - NULL, - G_DBUS_CALL_FLAGS_NONE, - -1, NULL, - consolekit_stop_cb, NULL); - g_object_unref (proxy); -} -static void -upower_sleep_cb (GObject *source_object, - GAsyncResult *res, - gpointer user_data) -{ - GVariant *result; - GError *error = NULL; - - result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), - res, - &error); - if (result == NULL) { - g_warning ("couldn't sleep using UPower: %s", - error->message); - g_error_free (error); - } else { - g_variant_unref (result); - } -} - -static void -upower_suspend (GDBusProxy *upower_proxy) -{ - g_dbus_proxy_call (upower_proxy, - "Suspend", - NULL, - G_DBUS_CALL_FLAGS_NONE, - -1, NULL, - upower_sleep_cb, NULL); -} - -static void -upower_hibernate (GDBusProxy *upower_proxy) -{ - g_dbus_proxy_call (upower_proxy, - "Hibernate", - NULL, - G_DBUS_CALL_FLAGS_NONE, - -1, NULL, - upower_sleep_cb, NULL); -} -#endif /* HAVE_SYSTEMD */ - -void -gsd_power_suspend (GDBusProxy *upower_proxy) -{ -#ifdef HAVE_SYSTEMD - systemd_suspend (); -#else - upower_suspend (upower_proxy); -#endif -} - -void -gsd_power_poweroff (void) -{ -#ifdef HAVE_SYSTEMD - systemd_stop (); -#else - consolekit_stop (); -#endif -} - -void -gsd_power_hibernate (GDBusProxy *upower_proxy) -{ -#ifdef HAVE_SYSTEMD - systemd_hibernate (); -#else - upower_hibernate (upower_proxy); -#endif -} diff --git a/plugins/common/gsd-power-helper.h b/plugins/common/gsd-power-helper.h deleted file mode 100644 index e3be14f..0000000 --- a/plugins/common/gsd-power-helper.h +++ /dev/null @@ -1,35 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- - * - * Copyright (C) 2012 Bastien Nocera - * - * 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, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -#ifndef __GSD_POWER_HELPER_H -#define __GSD_POWER_HELPER_H - -#include - -G_BEGIN_DECLS - -#include - -void gsd_power_suspend (GDBusProxy *upower_proxy); -void gsd_power_hibernate (GDBusProxy *upower_proxy); -void gsd_power_poweroff (void); - -G_END_DECLS - -#endif /* __GSD_POWER_HELPER_H */ diff --git a/plugins/media-keys/gsd-media-keys-manager.c b/plugins/media-keys/gsd-media-keys-manager.c index 9c84d7f..a2f277e 100644 --- a/plugins/media-keys/gsd-media-keys-manager.c +++ b/plugins/media-keys/gsd-media-keys-manager.c @@ -39,6 +39,7 @@ #include #include #include +#include #ifdef HAVE_GUDEV #include @@ -51,7 +52,6 @@ #include "shortcuts-list.h" #include "gsd-osd-window.h" #include "gsd-input-helper.h" -#include "gsd-power-helper.h" #include "gsd-enums.h" #include @@ -105,6 +105,10 @@ static const gchar introspection_xml[] = #define KEY_CURRENT_INPUT_SOURCE "current" #define KEY_INPUT_SOURCES "sources" +#define SYSTEMD_DBUS_NAME "org.freedesktop.login1" +#define SYSTEMD_DBUS_PATH "/org/freedesktop/login1" +#define SYSTEMD_DBUS_INTERFACE "org.freedesktop.login1.Manager" + #define GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSD_TYPE_MEDIA_KEYS_MANAGER, GsdMediaKeysManagerPrivate)) typedef struct { @@ -148,10 +152,13 @@ struct GsdMediaKeysManagerPrivate /* Power stuff */ GSettings *power_settings; - GDBusProxy *upower_proxy; GDBusProxy *power_screen_proxy; GDBusProxy *power_keyboard_proxy; + /* systemd stuff */ + GDBusProxy *logind_proxy; + gint inhibit_keys_fd; + /* Multihead stuff */ GdkScreen *current_screen; GSList *screens; @@ -1618,6 +1625,38 @@ do_toggle_contrast_action (GsdMediaKeysManager *manager) } static void +power_action_suspend (GsdMediaKeysManager *manager) +{ +#ifndef HAVE_SYSTEMD + g_warning ("no systemd support"); + return; +#endif + g_dbus_proxy_call (manager->priv->logind_proxy, + "Suspend", + g_variant_new ("(b)", TRUE), + G_DBUS_CALL_FLAGS_NONE, + G_MAXINT, + manager->priv->bus_cancellable, + NULL, NULL); +} + +static void +power_action_hibernate (GsdMediaKeysManager *manager) +{ +#ifndef HAVE_SYSTEMD + g_warning ("no systemd support"); + return; +#endif + g_dbus_proxy_call (manager->priv->logind_proxy, + "Hibernate", + g_variant_new ("(b)", TRUE), + G_DBUS_CALL_FLAGS_NONE, + G_MAXINT, + manager->priv->bus_cancellable, + NULL, NULL); +} + +static void do_config_power_action (GsdMediaKeysManager *manager, const gchar *config_key) { @@ -1627,14 +1666,14 @@ do_config_power_action (GsdMediaKeysManager *manager, config_key); switch (action_type) { case GSD_POWER_ACTION_SUSPEND: - gsd_power_suspend (manager->priv->upower_proxy); + power_action_suspend (manager); break; case GSD_POWER_ACTION_INTERACTIVE: case GSD_POWER_ACTION_SHUTDOWN: gnome_session_shutdown (manager); break; case GSD_POWER_ACTION_HIBERNATE: - gsd_power_hibernate (manager->priv->upower_proxy); + power_action_hibernate (manager); break; case GSD_POWER_ACTION_BLANK: case GSD_POWER_ACTION_NOTHING: @@ -2248,6 +2287,7 @@ gsd_media_keys_manager_stop (GsdMediaKeysManager *manager) } #endif /* HAVE_GUDEV */ + g_clear_object (&priv->logind_proxy); if (priv->settings) { g_object_unref (priv->settings); priv->settings = NULL; @@ -2268,11 +2308,6 @@ gsd_media_keys_manager_stop (GsdMediaKeysManager *manager) priv->power_keyboard_proxy = NULL; } - if (priv->upower_proxy) { - g_object_unref (priv->upower_proxy); - priv->upower_proxy = NULL; - } - if (priv->cancellable != NULL) { g_cancellable_cancel (priv->cancellable); g_object_unref (priv->cancellable); @@ -2363,9 +2398,85 @@ gsd_media_keys_manager_class_init (GsdMediaKeysManagerClass *klass) } static void +inhibit_done (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GDBusProxy *proxy = G_DBUS_PROXY (source); + GsdMediaKeysManager *manager = GSD_MEDIA_KEYS_MANAGER (user_data); + GError *error = NULL; + GVariant *res; + GUnixFDList *fd_list = NULL; + gint idx; + + res = g_dbus_proxy_call_with_unix_fd_list_finish (proxy, &fd_list, result, &error); + if (res == NULL) { + g_warning ("Unable to inhibit keypresses: %s", error->message); + g_error_free (error); + } else { + g_variant_get (res, "(h)", &idx); + manager->priv->inhibit_keys_fd = g_unix_fd_list_get (fd_list, idx, &error); + if (manager->priv->inhibit_keys_fd == -1) { + g_warning ("Failed to receive system inhibitor fd: %s", error->message); + g_error_free (error); + } + g_debug ("System inhibitor fd is %d", manager->priv->inhibit_keys_fd); + g_object_unref (fd_list); + g_variant_unref (res); + } +} + +static void gsd_media_keys_manager_init (GsdMediaKeysManager *manager) { + GError *error; + GDBusConnection *bus; + + error = NULL; manager->priv = GSD_MEDIA_KEYS_MANAGER_GET_PRIVATE (manager); + + bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (bus == NULL) { + g_warning ("Failed to connect to system bus: %s", + error->message); + g_error_free (error); + return; + } + + manager->priv->logind_proxy = + g_dbus_proxy_new_sync (bus, + 0, + NULL, + SYSTEMD_DBUS_NAME, + SYSTEMD_DBUS_PATH, + SYSTEMD_DBUS_INTERFACE, + NULL, + &error); + + if (manager->priv->logind_proxy == NULL) { + g_warning ("Failed to connect to systemd: %s", + error->message); + g_error_free (error); + } + + g_object_unref (bus); + + g_debug ("Adding system inhibitors for power keys"); + manager->priv->inhibit_keys_fd = -1; + g_dbus_proxy_call_with_unix_fd_list (manager->priv->logind_proxy, + "Inhibit", + g_variant_new ("(ssss)", + "handle-power-key:handle-suspend-key:handle-hibernate-key", + g_get_user_name (), + "GNOME handling keypresses", + "block"), + 0, + G_MAXINT, + NULL, + NULL, + inhibit_done, + manager); + } static void @@ -2382,6 +2493,8 @@ gsd_media_keys_manager_finalize (GObject *object) if (media_keys_manager->priv->start_idle_id != 0) g_source_remove (media_keys_manager->priv->start_idle_id); + if (media_keys_manager->priv->inhibit_keys_fd != -1) + close (media_keys_manager->priv->inhibit_keys_fd); G_OBJECT_CLASS (gsd_media_keys_manager_parent_class)->finalize (object); } @@ -2401,21 +2514,6 @@ xrandr_ready_cb (GObject *source_object, } static void -upower_ready_cb (GObject *source_object, - GAsyncResult *res, - GsdMediaKeysManager *manager) -{ - GError *error = NULL; - - manager->priv->upower_proxy = g_dbus_proxy_new_finish (res, &error); - if (manager->priv->upower_proxy == NULL) { - g_warning ("Failed to get proxy for upower: %s", - error->message); - g_error_free (error); - } -} - -static void power_screen_ready_cb (GObject *source_object, GAsyncResult *res, GsdMediaKeysManager *manager) @@ -2517,16 +2615,6 @@ register_manager (GsdMediaKeysManager *manager) manager->priv->bus_cancellable, (GAsyncReadyCallback) on_bus_gotten, manager); - - g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, - G_DBUS_PROXY_FLAGS_NONE, - NULL, - "org.freedesktop.UPower", - "/org/freedesktop/UPower", - "org.freedesktop.UPower", - NULL, - (GAsyncReadyCallback) upower_ready_cb, - manager); } GsdMediaKeysManager * diff --git a/plugins/power/gsd-power-manager.c b/plugins/power/gsd-power-manager.c index 070cf32..18fcedf 100644 --- a/plugins/power/gsd-power-manager.c +++ b/plugins/power/gsd-power-manager.c @@ -1,7 +1,7 @@ /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- * * Copyright (C) 2007 William Jon McCann - * Copyright (C) 2011 Richard Hughes + * Copyright (C) 2011-2012 Richard Hughes * Copyright (C) 2011 Ritesh Khadgaray * * This program is free software; you can redistribute it and/or modify @@ -32,6 +32,7 @@ #include #include #include +#include #define GNOME_DESKTOP_USE_UNSTABLE_API #include @@ -43,7 +44,6 @@ #include "gnome-settings-session.h" #include "gsd-enums.h" #include "gsd-power-manager.h" -#include "gsd-power-helper.h" #define GNOME_SESSION_DBUS_NAME "org.gnome.SessionManager" #define GNOME_SESSION_DBUS_PATH "/org/gnome/SessionManager" @@ -78,6 +78,10 @@ #define GSD_POWER_MANAGER_RECALL_DELAY 30 /* seconds */ #define GSD_POWER_MANAGER_LID_CLOSE_SAFETY_TIMEOUT 30 /* seconds */ +#define SYSTEMD_DBUS_NAME "org.freedesktop.login1" +#define SYSTEMD_DBUS_PATH "/org/freedesktop/login1" +#define SYSTEMD_DBUS_INTERFACE "org.freedesktop.login1.Manager" + /* Keep this in sync with gnome-shell */ #define SCREENSAVER_FADE_TIME 10 /* seconds */ @@ -193,14 +197,20 @@ struct GsdPowerManagerPrivate ca_context *canberra_context; ca_proplist *critical_alert_loop_props; guint32 critical_alert_timeout_id; - GDBusProxy *screensaver_proxy; GDBusProxy *session_proxy; GDBusProxy *session_presence_proxy; GpmIdletime *idletime; GsdPowerIdleMode current_idle_mode; - guint lid_close_safety_timer_id; GtkStatusIcon *status_icon; guint xscreensaver_watchdog_timer_id; + + /* systemd stuff */ + GDBusProxy *logind_proxy; + gint inhibit_lid_switch_fd; + gboolean inhibit_lid_switch_taken; + gint inhibit_suspend_fd; + gboolean inhibit_suspend_taken; + guint inhibit_lid_switch_timer_id; }; enum { @@ -217,8 +227,8 @@ static GIcon *engine_get_icon (GsdPowerManager *manager); static gchar *engine_get_summary (GsdPowerManager *manager); static void do_power_action_type (GsdPowerManager *manager, GsdPowerActionType action_type); static void do_lid_closed_action (GsdPowerManager *manager); -static void lock_screensaver (GsdPowerManager *manager); -static void kill_lid_close_safety_timer (GsdPowerManager *manager); +static void uninhibit_lid_switch (GsdPowerManager *manager); +static gboolean external_monitor_is_connected (GnomeRRScreen *screen); G_DEFINE_TYPE (GsdPowerManager, gsd_power_manager, G_TYPE_OBJECT) @@ -2048,6 +2058,57 @@ gnome_session_shutdown (void) } static void +action_poweroff (GsdPowerManager *manager) +{ + if (manager->priv->logind_proxy == NULL) { + g_warning ("no systemd support"); + return; + } + g_dbus_proxy_call (manager->priv->logind_proxy, + "PowerOff", + g_variant_new ("(b)", FALSE), + G_DBUS_CALL_FLAGS_NONE, + G_MAXINT, + NULL, + NULL, + NULL); +} + +static void +action_suspend (GsdPowerManager *manager) +{ + if (manager->priv->logind_proxy == NULL) { + g_warning ("no systemd support"); + return; + } + g_dbus_proxy_call (manager->priv->logind_proxy, + "Suspend", + g_variant_new ("(b)", FALSE), + G_DBUS_CALL_FLAGS_NONE, + G_MAXINT, + NULL, + NULL, + NULL); +} + +static void +action_hibernate (GsdPowerManager *manager) +{ + if (manager->priv->logind_proxy == NULL) { + g_warning ("no systemd support"); + return; + } + g_dbus_proxy_call (manager->priv->logind_proxy, + "Hibernate", + g_variant_new ("(b)", FALSE), + G_DBUS_CALL_FLAGS_NONE, + G_MAXINT, + NULL, + NULL, + NULL); +} + +static void do_power_action_type (GsdPowerManager *manager, GsdPowerActionType action_type) { @@ -2056,19 +2117,19 @@ do_power_action_type (GsdPowerManager *manager, switch (action_type) { case GSD_POWER_ACTION_SUSPEND: - gsd_power_suspend (manager->priv->upower_proxy); + action_suspend (manager); break; case GSD_POWER_ACTION_INTERACTIVE: gnome_session_shutdown (); break; case GSD_POWER_ACTION_HIBERNATE: - gsd_power_hibernate (manager->priv->upower_proxy); + action_hibernate (manager); break; case GSD_POWER_ACTION_SHUTDOWN: /* this is only used on critically low battery where * hibernate is not available and is marginally better * than just powering down the computer mid-write */ - gsd_power_poweroff (); + action_poweroff (manager); break; case GSD_POWER_ACTION_BLANK: ret = gnome_rr_screen_set_dpms_mode (manager->priv->x11_screen, @@ -2140,85 +2201,20 @@ upower_kbd_toggle (GsdPowerManager *manager, return ret; } -static void -do_lid_open_action (GsdPowerManager *manager) -{ - gboolean ret; - GError *error = NULL; - - /* play a sound, using sounds from the naming spec */ - ca_context_play (manager->priv->canberra_context, 0, - CA_PROP_EVENT_ID, "lid-open", - /* TRANSLATORS: this is the sound description */ - CA_PROP_EVENT_DESCRIPTION, _("Lid has been opened"), - NULL); - - /* ensure we turn the panel back on after lid open */ - ret = gnome_rr_screen_set_dpms_mode (manager->priv->x11_screen, - GNOME_RR_DPMS_ON, - &error); - if (!ret) { - g_warning ("failed to turn the panel on after lid open: %s", - error->message); - g_clear_error (&error); - } - - /* only toggle keyboard if present and already toggled off */ - if (manager->priv->upower_kdb_proxy != NULL && - manager->priv->kbd_brightness_old != -1) { - ret = upower_kbd_toggle (manager, &error); - if (!ret) { - g_warning ("failed to turn the kbd backlight on: %s", - error->message); - g_error_free (error); - } - } - - kill_lid_close_safety_timer (manager); -} - static gboolean -is_on (GnomeRROutput *output) +inhibit_lid_switch_timer_cb (GsdPowerManager *manager) { - GnomeRRCrtc *crtc; - - crtc = gnome_rr_output_get_crtc (output); - if (!crtc) - return FALSE; - return gnome_rr_crtc_get_current_mode (crtc) != NULL; -} - -static gboolean -non_laptop_outputs_are_all_off (GnomeRRScreen *screen) -{ - GnomeRROutput **outputs; - int i; - - outputs = gnome_rr_screen_list_outputs (screen); - for (i = 0; outputs[i] != NULL; i++) { - if (gnome_rr_output_is_laptop (outputs[i])) - continue; - - if (is_on (outputs[i])) - return FALSE; + if (!external_monitor_is_connected (manager->priv->x11_screen) || + g_settings_get_boolean (manager->priv->settings, + "lid-close-suspend-with-external-monitor")) { + g_debug ("no external monitors for a while; uninhibiting lid close"); + uninhibit_lid_switch (manager); + manager->priv->inhibit_lid_switch_timer_id = 0; + return G_SOURCE_REMOVE; } - return TRUE; -} - -/* Timeout callback used to check conditions when the laptop's lid is closed but - * the machine is not suspended yet. We try to suspend again, so that the laptop - * won't overheat if placed in a backpack. - */ -static gboolean -lid_close_safety_timer_cb (GsdPowerManager *manager) -{ - manager->priv->lid_close_safety_timer_id = 0; - - g_debug ("lid has been closed for a while; trying to suspend again"); - do_lid_closed_action (manager); - - return FALSE; + g_debug ("external monitor still there; trying again later"); + return G_SOURCE_CONTINUE; } /* Sets up a timer to be triggered some seconds after closing the laptop lid @@ -2226,82 +2222,73 @@ lid_close_safety_timer_cb (GsdPowerManager *manager) * again in the timeout handler to see if we can suspend then. */ static void -setup_lid_close_safety_timer (GsdPowerManager *manager) +setup_inhibit_lid_switch_timer (GsdPowerManager *manager) { - if (manager->priv->lid_close_safety_timer_id != 0) + if (manager->priv->inhibit_lid_switch_timer_id != 0) { + g_debug ("lid close safety timer already set up"); return; + } + + g_debug ("setting up lid close safety timer"); - manager->priv->lid_close_safety_timer_id = g_timeout_add_seconds (GSD_POWER_MANAGER_LID_CLOSE_SAFETY_TIMEOUT, - (GSourceFunc) lid_close_safety_timer_cb, + manager->priv->inhibit_lid_switch_timer_id = g_timeout_add_seconds (GSD_POWER_MANAGER_LID_CLOSE_SAFETY_TIMEOUT, + (GSourceFunc) inhibit_lid_switch_timer_cb, manager); - g_source_set_name_by_id (manager->priv->lid_close_safety_timer_id, "[GsdPowerManager] lid close safety timer"); + g_source_set_name_by_id (manager->priv->inhibit_lid_switch_timer_id, "[GsdPowerManager] lid close safety timer"); } static void -kill_lid_close_safety_timer (GsdPowerManager *manager) +restart_inhibit_lid_switch_timer (GsdPowerManager *manager) { - if (manager->priv->lid_close_safety_timer_id != 0) { - g_source_remove (manager->priv->lid_close_safety_timer_id); - manager->priv->lid_close_safety_timer_id = 0; + if (manager->priv->inhibit_lid_switch_timer_id != 0) { + g_debug ("restarting lid close safety timer"); + g_source_remove (manager->priv->inhibit_lid_switch_timer_id); + manager->priv->inhibit_lid_switch_timer_id = 0; + setup_inhibit_lid_switch_timer (manager); } } static void -suspend_with_lid_closed (GsdPowerManager *manager) +do_lid_open_action (GsdPowerManager *manager) { gboolean ret; GError *error = NULL; - GsdPowerActionType action_type; - /* maybe lock the screen if the lid is closed */ - lock_screensaver (manager); - - /* we have different settings depending on AC state */ - if (up_client_get_on_battery (manager->priv->up_client)) { - action_type = g_settings_get_enum (manager->priv->settings, - "lid-close-battery-action"); - } else { - action_type = g_settings_get_enum (manager->priv->settings, - "lid-close-ac-action"); - } - - /* check we won't melt when the lid is closed */ - if (action_type != GSD_POWER_ACTION_SUSPEND && - action_type != GSD_POWER_ACTION_HIBERNATE) { - if (up_client_get_lid_force_sleep (manager->priv->up_client)) { - g_warning ("to prevent damage, now forcing suspend"); - do_power_action_type (manager, GSD_POWER_ACTION_SUSPEND); - return; - } - } + /* play a sound, using sounds from the naming spec */ + ca_context_play (manager->priv->canberra_context, 0, + CA_PROP_EVENT_ID, "lid-open", + /* TRANSLATORS: this is the sound description */ + CA_PROP_EVENT_DESCRIPTION, _("Lid has been opened"), + NULL); - /* ensure we turn the panel back on after resume */ + /* ensure we turn the panel back on after lid open */ ret = gnome_rr_screen_set_dpms_mode (manager->priv->x11_screen, - GNOME_RR_DPMS_OFF, + GNOME_RR_DPMS_ON, &error); if (!ret) { - g_warning ("failed to turn the panel off after lid close: %s", + g_warning ("failed to turn the panel on after lid open: %s", error->message); - g_error_free (error); + g_clear_error (&error); } - /* only toggle keyboard if present and not already toggled */ - if (manager->priv->upower_kdb_proxy && - manager->priv->kbd_brightness_old == -1) { + /* only toggle keyboard if present and already toggled off */ + if (manager->priv->upower_kdb_proxy != NULL && + manager->priv->kbd_brightness_old != -1) { ret = upower_kbd_toggle (manager, &error); if (!ret) { - g_warning ("failed to turn the kbd backlight off: %s", + g_warning ("failed to turn the kbd backlight on: %s", error->message); g_error_free (error); } } - - do_power_action_type (manager, action_type); } static void do_lid_closed_action (GsdPowerManager *manager) { + gboolean ret; + GError *error = NULL; + /* play a sound, using sounds from the naming spec */ ca_context_play (manager->priv->canberra_context, 0, CA_PROP_EVENT_ID, "lid-close", @@ -2309,21 +2296,22 @@ do_lid_closed_action (GsdPowerManager *manager) CA_PROP_EVENT_DESCRIPTION, _("Lid has been closed"), NULL); + /* turn the panel off if the lid is closed (mainly for Dells...) */ + ret = gnome_rr_screen_set_dpms_mode (manager->priv->x11_screen, + GNOME_RR_DPMS_OFF, + &error); + if (!ret) { + g_warning ("failed to turn the panel off after lid close: %s", + error->message); + g_error_free (error); + } + /* refresh RANDR so we get an accurate view of what monitors are plugged in when the lid is closed */ gnome_rr_screen_refresh (manager->priv->x11_screen, NULL); /* NULL-GError */ - /* perform policy action */ - if (g_settings_get_boolean (manager->priv->settings, "lid-close-suspend-with-external-monitor") - || non_laptop_outputs_are_all_off (manager->priv->x11_screen)) { - g_debug ("lid is closed; suspending or hibernating"); - suspend_with_lid_closed (manager); - } else { - g_debug ("lid is closed; not suspending nor hibernating since some external monitor outputs are still active"); - setup_lid_close_safety_timer (manager); - } + restart_inhibit_lid_switch_timer (manager); } - static void up_client_changed_cb (UpClient *client, GsdPowerManager *manager) { @@ -2343,6 +2331,7 @@ up_client_changed_cb (UpClient *client, GsdPowerManager *manager) if (manager->priv->lid_is_closed == tmp) return; manager->priv->lid_is_closed = tmp; + g_debug ("up changed: lid is now %s", tmp ? "closed" : "open"); /* fake a keypress */ if (tmp) @@ -3294,30 +3283,6 @@ gsd_power_manager_class_init (GsdPowerManagerClass *klass) } static void -sleep_cb_screensaver_proxy_ready_cb (GObject *source_object, - GAsyncResult *res, - gpointer user_data) -{ - GError *error = NULL; - GsdPowerManager *manager = GSD_POWER_MANAGER (user_data); - - manager->priv->screensaver_proxy = g_dbus_proxy_new_for_bus_finish (res, &error); - if (manager->priv->screensaver_proxy == NULL) { - g_warning ("Could not connect to gnome-screensaver: %s", - error->message); - g_error_free (error); - return; - } - - /* Finish the upower_notify_sleep_cb() call by locking the screen */ - g_debug ("gnome-screensaver activated, doing gnome-screensaver lock"); - g_dbus_proxy_call (manager->priv->screensaver_proxy, - "Lock", - NULL, G_DBUS_CALL_FLAGS_NONE, -1, - NULL, NULL, NULL); -} - -static void idle_dbus_signal_cb (GDBusProxy *proxy, const gchar *sender_name, const gchar *signal_name, @@ -3469,75 +3434,38 @@ out: } static void -lock_screensaver (GsdPowerManager *manager) +lock_screensaver (GsdPowerManager *manager, + GSourceFunc done_cb) { gboolean do_lock; do_lock = g_settings_get_boolean (manager->priv->settings_screensaver, "lock-enabled"); - if (!do_lock) + if (!do_lock && done_cb) { + done_cb (manager); return; - - if (manager->priv->screensaver_proxy != NULL) { - g_debug ("doing gnome-screensaver lock"); - g_dbus_proxy_call (manager->priv->screensaver_proxy, - "Lock", - NULL, G_DBUS_CALL_FLAGS_NONE, -1, - NULL, NULL, NULL); - } else { - /* connect to the screensaver first */ - g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION, - G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, - NULL, - GS_DBUS_NAME, - GS_DBUS_PATH, - GS_DBUS_INTERFACE, - NULL, - sleep_cb_screensaver_proxy_ready_cb, - manager); } -} - -static void -upower_notify_sleep_cb (UpClient *client, - UpSleepKind sleep_kind, - GsdPowerManager *manager) -{ - lock_screensaver (manager); -} - -static void -upower_notify_resume_cb (UpClient *client, - UpSleepKind sleep_kind, - GsdPowerManager *manager) -{ - gboolean ret; - GError *error = NULL; - - /* this displays the unlock dialogue so the user doesn't have - * to move the mouse or press any key before the window comes up */ - if (manager->priv->screensaver_proxy != NULL) { - g_dbus_proxy_call (manager->priv->screensaver_proxy, - "SimulateUserActivity", - NULL, - G_DBUS_CALL_FLAGS_NONE, - -1, NULL, NULL, NULL); - } - - /* close existing notifications on resume, the system power - * state is probably different now */ - notify_close_if_showing (manager->priv->notification_low); - notify_close_if_showing (manager->priv->notification_discharging); - /* ensure we turn the panel back on after resume */ - ret = gnome_rr_screen_set_dpms_mode (manager->priv->x11_screen, - GNOME_RR_DPMS_ON, - &error); - if (!ret) { - g_warning ("failed to turn the panel on after resume: %s", - error->message); - g_error_free (error); - } + g_dbus_connection_call (manager->priv->connection, + GS_DBUS_NAME, + GS_DBUS_PATH, + GS_DBUS_INTERFACE, + "Lock", + NULL, NULL, + G_DBUS_CALL_FLAGS_NONE, -1, + NULL, NULL, NULL); + + /* Wait until gnome-shell shield animation is done + * + * FIXME: the shell should mark the lock as active + * when the shield is down, then we could wait for + * that. This would also fix the problem that we wait + * needlessly when the shell has already locked the + * screen because it is initiating the suspend. + * + * https://bugzilla.gnome.org/show_bug.cgi?id=685053 + */ + g_timeout_add (500, done_cb, manager); } static void @@ -3696,6 +3624,287 @@ disable_builtin_screensaver (gpointer unused) return TRUE; } +static void +inhibit_lid_switch_done (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GDBusProxy *proxy = G_DBUS_PROXY (source); + GsdPowerManager *manager = GSD_POWER_MANAGER (user_data); + GError *error = NULL; + GVariant *res; + GUnixFDList *fd_list = NULL; + gint idx; + + res = g_dbus_proxy_call_with_unix_fd_list_finish (proxy, &fd_list, result, &error); + if (res == NULL) { + g_warning ("Unable to inhibit lid switch: %s", error->message); + g_error_free (error); + } else { + g_variant_get (res, "(h)", &idx); + manager->priv->inhibit_lid_switch_fd = g_unix_fd_list_get (fd_list, idx, &error); + if (manager->priv->inhibit_lid_switch_fd == -1) { + g_warning ("Failed to receive system inhibitor fd: %s", error->message); + g_error_free (error); + } + g_debug ("System inhibitor fd is %d", manager->priv->inhibit_lid_switch_fd); + g_object_unref (fd_list); + g_variant_unref (res); + } +} + +static void +inhibit_lid_switch (GsdPowerManager *manager) +{ + GVariant *params; + + if (manager->priv->inhibit_lid_switch_taken) { + g_debug ("already inhibited lid-switch"); + return; + } + g_debug ("Adding lid switch system inhibitor"); + manager->priv->inhibit_lid_switch_taken = TRUE; + + params = g_variant_new ("(ssss)", + "handle-lid-switch", + g_get_user_name (), + "Multiple displays attached", + "block"); + g_dbus_proxy_call_with_unix_fd_list (manager->priv->logind_proxy, + "Inhibit", + params, + 0, + G_MAXINT, + NULL, + NULL, + inhibit_lid_switch_done, + manager); +} + +static void +uninhibit_lid_switch (GsdPowerManager *manager) +{ + if (manager->priv->inhibit_lid_switch_fd == -1) { + g_debug ("no lid-switch inhibitor"); + return; + } + g_debug ("Removing lid switch system inhibitor"); + close (manager->priv->inhibit_lid_switch_fd); + manager->priv->inhibit_lid_switch_fd = -1; + manager->priv->inhibit_lid_switch_taken = FALSE; +} + +static void +inhibit_suspend_done (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GDBusProxy *proxy = G_DBUS_PROXY (source); + GsdPowerManager *manager = GSD_POWER_MANAGER (user_data); + GError *error = NULL; + GVariant *res; + GUnixFDList *fd_list = NULL; + gint idx; + + res = g_dbus_proxy_call_with_unix_fd_list_finish (proxy, &fd_list, result, &error); + if (res == NULL) { + g_warning ("Unable to inhibit suspend: %s", error->message); + g_error_free (error); + } else { + g_variant_get (res, "(h)", &idx); + manager->priv->inhibit_suspend_fd = g_unix_fd_list_get (fd_list, idx, &error); + if (manager->priv->inhibit_suspend_fd == -1) { + g_warning ("Failed to receive system inhibitor fd: %s", error->message); + g_error_free (error); + } + g_debug ("System inhibitor fd is %d", manager->priv->inhibit_suspend_fd); + g_object_unref (fd_list); + g_variant_unref (res); + } +} + +/* We take a delay inhibitor here, which causes logind to send a + * PrepareToSleep signal, which gives us a chance to lock the screen + * and do some other preparations. + */ +static void +inhibit_suspend (GsdPowerManager *manager) +{ + if (manager->priv->inhibit_suspend_taken) { + g_debug ("already inhibited lid-switch"); + return; + } + g_debug ("Adding suspend delay inhibitor"); + manager->priv->inhibit_suspend_taken = TRUE; + g_dbus_proxy_call_with_unix_fd_list (manager->priv->logind_proxy, + "Inhibit", + g_variant_new ("(ssss)", + "sleep", + g_get_user_name (), + "GNOME needs to lock the screen", + "delay"), + 0, + G_MAXINT, + NULL, + NULL, + inhibit_suspend_done, + manager); +} + +static void +uninhibit_suspend (GsdPowerManager *manager) +{ + if (manager->priv->inhibit_suspend_fd == -1) { + g_debug ("no suspend delay inhibitor"); + return; + } + g_debug ("Removing suspend delay inhibitor"); + close (manager->priv->inhibit_suspend_fd); + manager->priv->inhibit_suspend_fd = -1; + manager->priv->inhibit_suspend_taken = TRUE; +} + +static gboolean +randr_output_is_on (GnomeRROutput *output) +{ + GnomeRRCrtc *crtc; + + crtc = gnome_rr_output_get_crtc (output); + if (!crtc) + return FALSE; + return gnome_rr_crtc_get_current_mode (crtc) != NULL; +} + +static gboolean +external_monitor_is_connected (GnomeRRScreen *screen) +{ + GnomeRROutput **outputs; + guint i; + + if (g_file_test ("/tmp/external_connected", G_FILE_TEST_EXISTS)) + return TRUE; + + /* see if we have more than one screen plugged in */ + outputs = gnome_rr_screen_list_outputs (screen); + for (i = 0; outputs[i] != NULL; i++) { + if (randr_output_is_on (outputs[i]) && + !gnome_rr_output_is_laptop (outputs[i])) + return TRUE; + } + + return FALSE; +} + +static void +on_randr_event (GnomeRRScreen *screen, gpointer user_data) +{ + GsdPowerManager *manager = GSD_POWER_MANAGER (user_data); + + /* when a second monitor is plugged in, we take the + * handle-lid-switch inhibitor lock of logind to prevent + * it from suspending. + * + * Uninhibiting is done in the inhibit_lid_switch_timer, + * since we want to give users a few seconds when unplugging + * and replugging an external monitor, not suspend right away. + */ + if (external_monitor_is_connected (screen) && + !g_settings_get_boolean (manager->priv->settings, + "lid-close-suspend-with-external-monitor")) { + inhibit_lid_switch (manager); + setup_inhibit_lid_switch_timer (manager); + } + else { + restart_inhibit_lid_switch_timer (manager); + } +} + +static gboolean +screen_lock_done_cb (gpointer data) +{ + GsdPowerManager *manager = data; + + /* lift the delay inhibit, so logind can proceed */ + uninhibit_suspend (manager); + + return FALSE; +} + +static void +handle_suspend_actions (GsdPowerManager *manager) +{ + gboolean ret; + GError *error = NULL; + + /* ensure we turn the panel back on after resume */ + ret = gnome_rr_screen_set_dpms_mode (manager->priv->x11_screen, + GNOME_RR_DPMS_ON, + &error); + if (!ret) { + g_warning ("failed to turn the panel on after resume: %s", + error->message); + g_error_free (error); + } + + lock_screensaver (manager, screen_lock_done_cb); +} + +static void +handle_resume_actions (GsdPowerManager *manager) +{ + gboolean ret; + GError *error = NULL; + + /* this displays the unlock dialogue so the user doesn't have + * to move the mouse or press any key before the window comes up */ + g_dbus_connection_call (manager->priv->connection, + GS_DBUS_NAME, + GS_DBUS_PATH, + GS_DBUS_INTERFACE, + "SimulateUserActivity", + NULL, NULL, + G_DBUS_CALL_FLAGS_NONE, -1, + NULL, NULL, NULL); + + /* close existing notifications on resume, the system power + * state is probably different now */ + notify_close_if_showing (manager->priv->notification_low); + notify_close_if_showing (manager->priv->notification_discharging); + + /* ensure we turn the panel back on after resume */ + ret = gnome_rr_screen_set_dpms_mode (manager->priv->x11_screen, + GNOME_RR_DPMS_ON, + &error); + if (!ret) { + g_warning ("failed to turn the panel on after resume: %s", + error->message); + g_error_free (error); + } + + /* set up the delay again */ + inhibit_suspend (manager); +} + +static void +logind_proxy_signal_cb (GDBusProxy *proxy, + const gchar *sender_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + GsdPowerManager *manager = GSD_POWER_MANAGER (user_data); + gboolean is_about_to_suspend; + + if (g_strcmp0 (signal_name, "PrepareForSleep") != 0) + return; + g_variant_get (parameters, "(b)", &is_about_to_suspend); + if (is_about_to_suspend) { + handle_suspend_actions (manager); + } else { + handle_resume_actions (manager); + } +} + gboolean gsd_power_manager_start (GsdPowerManager *manager, GError **error) @@ -3705,6 +3914,25 @@ gsd_power_manager_start (GsdPowerManager *manager, g_debug ("Starting power manager"); gnome_settings_profile_start (NULL); + manager->priv->logind_proxy = + g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, + 0, + NULL, + SYSTEMD_DBUS_NAME, + SYSTEMD_DBUS_PATH, + SYSTEMD_DBUS_INTERFACE, + NULL, + error); + if (manager->priv->logind_proxy == NULL) { + g_warning ("no systemd support"); + return FALSE; + } + g_signal_connect (manager->priv->logind_proxy, "g-signal", + G_CALLBACK (logind_proxy_signal_cb), + manager); + /* Set up a delay inhibitor to be informed about suspend attempts */ + inhibit_suspend (manager); + /* track the active session */ manager->priv->session = gnome_settings_session_new (); g_signal_connect (manager->priv->session, "notify::state", @@ -3719,10 +3947,6 @@ gsd_power_manager_start (GsdPowerManager *manager, G_CALLBACK (engine_settings_key_changed_cb), manager); manager->priv->settings_screensaver = g_settings_new ("org.gnome.desktop.screensaver"); manager->priv->up_client = up_client_new (); - g_signal_connect (manager->priv->up_client, "notify-sleep", - G_CALLBACK (upower_notify_sleep_cb), manager); - g_signal_connect (manager->priv->up_client, "notify-resume", - G_CALLBACK (upower_notify_resume_cb), manager); manager->priv->lid_is_closed = up_client_get_lid_is_closed (manager->priv->up_client); g_signal_connect (manager->priv->up_client, "device-added", G_CALLBACK (engine_device_added_cb), manager); @@ -3836,6 +4060,9 @@ gsd_power_manager_start (GsdPowerManager *manager, manager->priv->x11_screen = gnome_rr_screen_new (gdk_screen_get_default (), error); if (manager->priv->x11_screen == NULL) return FALSE; + g_signal_connect (manager->priv->x11_screen, "changed", G_CALLBACK (on_randr_event), manager); + /* set up initial state */ + on_randr_event (manager->priv->x11_screen, manager); /* ensure the default dpms timeouts are cleared */ ret = gnome_rr_screen_set_dpms_mode (manager->priv->x11_screen, @@ -3865,6 +4092,11 @@ gsd_power_manager_stop (GsdPowerManager *manager) { g_debug ("Stopping power manager"); + if (manager->priv->inhibit_lid_switch_timer_id != 0) { + g_source_remove (manager->priv->inhibit_lid_switch_timer_id); + manager->priv->inhibit_lid_switch_timer_id = 0; + } + if (manager->priv->bus_cancellable != NULL) { g_cancellable_cancel (manager->priv->bus_cancellable); g_object_unref (manager->priv->bus_cancellable); @@ -3876,8 +4108,6 @@ gsd_power_manager_stop (GsdPowerManager *manager) manager->priv->introspection_data = NULL; } - kill_lid_close_safety_timer (manager); - g_signal_handlers_disconnect_by_data (manager->priv->up_client, manager); g_clear_object (&manager->priv->connection); @@ -3885,6 +4115,19 @@ gsd_power_manager_stop (GsdPowerManager *manager) g_clear_object (&manager->priv->settings); g_clear_object (&manager->priv->settings_screensaver); g_clear_object (&manager->priv->up_client); + + if (manager->priv->inhibit_lid_switch_fd != -1) { + close (manager->priv->inhibit_lid_switch_fd); + manager->priv->inhibit_lid_switch_fd = -1; + manager->priv->inhibit_lid_switch_taken = FALSE; + } + if (manager->priv->inhibit_suspend_fd != -1) { + close (manager->priv->inhibit_suspend_fd); + manager->priv->inhibit_suspend_fd = -1; + manager->priv->inhibit_suspend_taken = FALSE; + } + + g_clear_object (&manager->priv->logind_proxy); g_clear_object (&manager->priv->x11_screen); g_ptr_array_unref (manager->priv->devices_array); @@ -3918,6 +4161,8 @@ static void gsd_power_manager_init (GsdPowerManager *manager) { manager->priv = GSD_POWER_MANAGER_GET_PRIVATE (manager); + manager->priv->inhibit_lid_switch_fd = -1; + manager->priv->inhibit_suspend_fd = -1; } static void -- 1.7.12.2