/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- * * Copyright (C) 2005-2011 Richard Hughes * * Licensed under the GNU General Public License Version 2 * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "config.h" #include #include #include #include #include #include #include #include #include #define GNOME_DESKTOP_USE_UNSTABLE_API #include #include "gnome-settings-bus.h" #include "gpm-common.h" #include "gsd-power-constants.h" #include "gsd-power-manager.h" #define XSCREENSAVER_WATCHDOG_TIMEOUT 120 /* seconds */ #define UPS_SOUND_LOOP_ID 99 #define GSD_POWER_MANAGER_CRITICAL_ALERT_TIMEOUT 5 /* seconds */ static int gsd_power_backlight_convert_safe (int value, int from_range, int to_range) { /* round (value / from_range) * to_range */ return (value * to_range + from_range / 2) / from_range; } /* take a discrete value with offset and convert to percentage */ int gsd_power_backlight_abs_to_percentage (int min, int max, int value) { g_return_val_if_fail (max > min, -1); g_return_val_if_fail (value >= min, -1); g_return_val_if_fail (value <= max, -1); return gsd_power_backlight_convert_safe (value - min, max - min, 100); } /* take a percentage and convert to a discrete value with offset */ int gsd_power_backlight_percentage_to_abs (int min, int max, int value) { g_return_val_if_fail (max > min, -1); g_return_val_if_fail (value >= 0, -1); g_return_val_if_fail (value <= 100, -1); return min + gsd_power_backlight_convert_safe (value, 100, max - min); } #define GPM_UP_TIME_PRECISION 5*60 #define GPM_UP_TEXT_MIN_TIME 120 /** * Return value: The time string, e.g. "2 hours 3 minutes" **/ gchar * gpm_get_timestring (guint time_secs) { char* timestring = NULL; gint hours; gint minutes; /* Add 0.5 to do rounding */ minutes = (int) ( ( time_secs / 60.0 ) + 0.5 ); if (minutes == 0) { timestring = g_strdup (_("Unknown time")); return timestring; } if (minutes < 60) { timestring = g_strdup_printf (ngettext ("%i minute", "%i minutes", minutes), minutes); return timestring; } hours = minutes / 60; minutes = minutes % 60; if (minutes == 0) timestring = g_strdup_printf (ngettext ( "%i hour", "%i hours", hours), hours); else /* TRANSLATOR: "%i %s %i %s" are "%i hours %i minutes" * Swap order with "%2$s %2$i %1$s %1$i if needed */ timestring = g_strdup_printf (_("%i %s %i %s"), hours, ngettext ("hour", "hours", hours), minutes, ngettext ("minute", "minutes", minutes)); return timestring; } static gboolean parse_vm_kernel_cmdline (gboolean *is_virtual_machine) { gboolean ret = FALSE; GRegex *regex; GMatchInfo *match; char *contents; char *word; const char *arg; if (!g_file_get_contents ("/proc/cmdline", &contents, NULL, NULL)) return ret; regex = g_regex_new ("gnome.is_vm=(\\S+)", 0, G_REGEX_MATCH_NOTEMPTY, NULL); if (!g_regex_match (regex, contents, G_REGEX_MATCH_NOTEMPTY, &match)) goto out; word = g_match_info_fetch (match, 0); g_debug ("Found command-line match '%s'", word); arg = word + strlen ("gnome.is_vm="); if (*arg != '0' && *arg != '1') { g_warning ("Invalid value '%s' for gnome.is_vm passed in kernel command line.\n", arg); } else { *is_virtual_machine = atoi (arg); ret = TRUE; } g_free (word); out: g_match_info_free (match); g_regex_unref (regex); g_free (contents); if (ret) g_debug ("Kernel command-line parsed to %d", *is_virtual_machine); return ret; } gboolean gsd_power_is_hardware_a_vm (void) { const gchar *str; gboolean ret = FALSE; GError *error = NULL; GVariant *inner; GVariant *variant = NULL; GDBusConnection *connection; if (parse_vm_kernel_cmdline (&ret)) return ret; connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); if (connection == NULL) { g_warning ("system bus not available: %s", error->message); g_error_free (error); goto out; } variant = g_dbus_connection_call_sync (connection, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.DBus.Properties", "Get", g_variant_new ("(ss)", "org.freedesktop.systemd1.Manager", "Virtualization"), NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); if (variant == NULL) { g_debug ("Failed to get property '%s': %s", "Virtualization", error->message); g_error_free (error); goto out; } /* on bare-metal hardware this is the empty string, * otherwise an identifier such as "kvm", "vmware", etc. */ g_variant_get (variant, "(v)", &inner); str = g_variant_get_string (inner, NULL); if (str != NULL && str[0] != '\0') ret = TRUE; g_variant_unref (inner); out: if (connection != NULL) g_object_unref (connection); if (variant != NULL) g_variant_unref (variant); return ret; } /* This timer goes off every few minutes, whether the user is idle or not, to try and clean up anything that has gone wrong. It calls disable_builtin_screensaver() so that if xset has been used, or some other program (like xlock) has messed with the XSetScreenSaver() settings, they will be set back to sensible values (if a server extension is in use, messing with xlock can cause the screensaver to never get a wakeup event, and could cause monitor power-saving to occur, and all manner of heinousness.) This code was originally part of gnome-screensaver, see http://git.gnome.org/browse/gnome-screensaver/tree/src/gs-watcher-x11.c?id=fec00b12ec46c86334cfd36b37771cc4632f0d4d#n530 */ static gboolean disable_builtin_screensaver (gpointer unused) { int current_server_timeout, current_server_interval; int current_prefer_blank, current_allow_exp; int desired_server_timeout, desired_server_interval; int desired_prefer_blank, desired_allow_exp; XGetScreenSaver (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), ¤t_server_timeout, ¤t_server_interval, ¤t_prefer_blank, ¤t_allow_exp); desired_server_timeout = current_server_timeout; desired_server_interval = current_server_interval; desired_prefer_blank = current_prefer_blank; desired_allow_exp = current_allow_exp; desired_server_interval = 0; /* I suspect (but am not sure) that DontAllowExposures might have something to do with powering off the monitor as well, at least on some systems that don't support XDPMS? Who know... */ desired_allow_exp = AllowExposures; /* When we're not using an extension, set the server-side timeout to 0, so that the server never gets involved with screen blanking, and we do it all ourselves. (However, when we *are* using an extension, we tell the server when to notify us, and rather than blanking the screen, the server will send us an X event telling us to blank.) */ desired_server_timeout = 0; if (desired_server_timeout != current_server_timeout || desired_server_interval != current_server_interval || desired_prefer_blank != current_prefer_blank || desired_allow_exp != current_allow_exp) { g_debug ("disabling server builtin screensaver:" " (xset s %d %d; xset s %s; xset s %s)", desired_server_timeout, desired_server_interval, (desired_prefer_blank ? "blank" : "noblank"), (desired_allow_exp ? "expose" : "noexpose")); XSetScreenSaver (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), desired_server_timeout, desired_server_interval, desired_prefer_blank, desired_allow_exp); XSync (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), FALSE); } return TRUE; } guint gsd_power_enable_screensaver_watchdog (void) { int dummy; guint id; /* Make sure that Xorg's DPMS extension never gets in our * way. The defaults are now applied in Fedora 20 from * being "0" by default to being "600" by default */ gdk_x11_display_error_trap_push (gdk_display_get_default ()); if (DPMSQueryExtension(GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), &dummy, &dummy)) DPMSSetTimeouts (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), 0, 0, 0); gdk_x11_display_error_trap_pop_ignored (gdk_display_get_default ()); id = g_timeout_add_seconds (XSCREENSAVER_WATCHDOG_TIMEOUT, disable_builtin_screensaver, NULL); g_source_set_name_by_id (id, "[gnome-settings-daemon] disable_builtin_screensaver"); return id; } static gpointer parse_mock_mock_external_monitor (gpointer data) { const char *mocked_file; mocked_file = g_getenv ("GSD_MOCK_EXTERNAL_MONITOR_FILE"); return g_strdup (mocked_file); } static const gchar * get_mock_external_monitor_file (void) { static GOnce mocked_once = G_ONCE_INIT; g_once (&mocked_once, parse_mock_mock_external_monitor, NULL); return mocked_once.retval; } 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 void mock_monitor_changed (GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, gpointer user_data) { GnomeRRScreen *screen = (GnomeRRScreen *) user_data; g_debug ("Mock screen configuration changed"); g_signal_emit_by_name (G_OBJECT (screen), "changed"); } static void screen_destroyed (gpointer user_data, GObject *where_the_object_was) { g_object_unref (G_OBJECT (user_data)); } void watch_external_monitor (GnomeRRScreen *screen) { const gchar *filename; GFile *file; GFileMonitor *monitor; filename = get_mock_external_monitor_file (); if (!filename) return; file = g_file_new_for_commandline_arg (filename); monitor = g_file_monitor (file, G_FILE_MONITOR_NONE, NULL, NULL); g_object_unref (file); g_signal_connect (monitor, "changed", G_CALLBACK (mock_monitor_changed), screen); g_object_weak_ref (G_OBJECT (screen), screen_destroyed, monitor); } static gboolean mock_external_monitor_is_connected (GnomeRRScreen *screen) { char *mock_external_monitor_contents; const gchar *filename; filename = get_mock_external_monitor_file (); g_assert (filename); if (g_file_get_contents (filename, &mock_external_monitor_contents, NULL, NULL)) { if (mock_external_monitor_contents[0] == '1') { g_free (mock_external_monitor_contents); g_debug ("Mock external monitor is on"); return TRUE; } else if (mock_external_monitor_contents[0] == '0') { g_free (mock_external_monitor_contents); g_debug ("Mock external monitor is off"); return FALSE; } g_error ("Unhandled value for GSD_MOCK_EXTERNAL_MONITOR contents: %s", mock_external_monitor_contents); g_free (mock_external_monitor_contents); } return FALSE; } gboolean external_monitor_is_connected (GnomeRRScreen *screen) { GnomeRROutput **outputs; guint i; if (get_mock_external_monitor_file ()) return mock_external_monitor_is_connected (screen); g_assert (screen != NULL); /* 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_builtin_display (outputs[i])) return TRUE; } return FALSE; } static void play_sound (void) { ca_context_play (ca_gtk_context_get (), UPS_SOUND_LOOP_ID, CA_PROP_EVENT_ID, "battery-caution", CA_PROP_EVENT_DESCRIPTION, _("Battery is critically low"), NULL); } static gboolean play_loop_timeout_cb (gpointer user_data) { play_sound (); return TRUE; } void play_loop_start (guint *id) { if (*id != 0) return; *id = g_timeout_add_seconds (GSD_POWER_MANAGER_CRITICAL_ALERT_TIMEOUT, (GSourceFunc) play_loop_timeout_cb, NULL); g_source_set_name_by_id (*id, "[gnome-settings-daemon] play_loop_timeout_cb"); play_sound (); } void play_loop_stop (guint *id) { if (*id == 0) return; ca_context_cancel (ca_gtk_context_get (), UPS_SOUND_LOOP_ID); g_source_remove (*id); *id = 0; }