/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * * Copyright (C) 2008 David Zeuthen * Copyright (C) 2008 Richard Hughes * * 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 St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include "up-config.h" #include "up-device-list.h" #include "up-device.h" #include "up-backend.h" #include "up-daemon.h" #include "up-daemon-glue.h" #include "up-marshal.h" enum { PROP_0, PROP_DAEMON_VERSION, PROP_ON_BATTERY, PROP_LID_IS_CLOSED, PROP_LID_IS_PRESENT, PROP_LAST }; enum { SIGNAL_DEVICE_ADDED, SIGNAL_DEVICE_REMOVED, SIGNAL_LAST, }; static guint signals[SIGNAL_LAST] = { 0 }; struct UpDaemonPrivate { DBusGConnection *connection; DBusGProxy *proxy; UpConfig *config; UpBackend *backend; UpDeviceList *power_devices; guint action_timeout_id; GHashTable *poll_timeouts; /* Properties */ gboolean on_battery; UpDeviceLevel warning_level; gboolean lid_is_closed; gboolean lid_is_present; /* PropertiesChanged to be emitted */ GHashTable *changed_props; guint props_idle_id; /* Display battery properties */ UpDevice *display_device; UpDeviceKind kind; UpDeviceState state; gdouble percentage; gdouble energy; gdouble energy_full; gdouble energy_rate; gint64 time_to_empty; gint64 time_to_full; /* WarningLevel configuration */ gboolean use_percentage_for_policy; guint low_percentage; guint critical_percentage; guint action_percentage; guint low_time; guint critical_time; guint action_time; }; static void up_daemon_finalize (GObject *object); static gboolean up_daemon_get_on_battery_local (UpDaemon *daemon); static gboolean up_daemon_get_warning_level_local(UpDaemon *daemon); static gboolean up_daemon_get_on_ac_local (UpDaemon *daemon); G_DEFINE_TYPE (UpDaemon, up_daemon, G_TYPE_OBJECT) #define UP_DAEMON_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), UP_TYPE_DAEMON, UpDaemonPrivate)) #define UP_DAEMON_ACTION_DELAY 20 /* seconds */ /** * up_daemon_get_on_battery_local: * * As soon as _any_ battery goes discharging, this is true **/ static gboolean up_daemon_get_on_battery_local (UpDaemon *daemon) { guint i; gboolean ret; gboolean result = FALSE; gboolean on_battery; UpDevice *device; GPtrArray *array; /* ask each device */ array = up_device_list_get_array (daemon->priv->power_devices); for (i=0; ilen; i++) { device = (UpDevice *) g_ptr_array_index (array, i); ret = up_device_get_on_battery (device, &on_battery); if (ret && on_battery) { result = TRUE; break; } } g_ptr_array_unref (array); return result; } /** * up_daemon_get_number_devices_of_type: **/ guint up_daemon_get_number_devices_of_type (UpDaemon *daemon, UpDeviceKind type) { guint i; UpDevice *device; GPtrArray *array; UpDeviceKind type_tmp; guint count = 0; /* ask each device */ array = up_device_list_get_array (daemon->priv->power_devices); for (i=0; ilen; i++) { device = (UpDevice *) g_ptr_array_index (array, i); g_object_get (device, "type", &type_tmp, NULL); if (type == type_tmp) count++; } g_ptr_array_unref (array); return count; } /** * up_daemon_update_display_battery: * * Update our internal state. * * Returns: %TRUE if the state changed. **/ static gboolean up_daemon_update_display_battery (UpDaemon *daemon) { guint i; GPtrArray *array; UpDeviceKind kind_total = UP_DEVICE_KIND_UNKNOWN; UpDeviceState state_total = UP_DEVICE_STATE_UNKNOWN; gdouble percentage_total = 0.0; gdouble energy_total = 0.0; gdouble energy_full_total = 0.0; gdouble energy_rate_total = 0.0; gint64 time_to_empty_total = 0; gint64 time_to_full_total = 0; gboolean is_present_total = FALSE; guint num_batteries = 0; /* Gather state from each device */ array = up_device_list_get_array (daemon->priv->power_devices); for (i = 0; i < array->len; i++) { UpDevice *device; UpDeviceState state = UP_DEVICE_STATE_UNKNOWN; UpDeviceKind kind = UP_DEVICE_KIND_UNKNOWN; gdouble percentage = 0.0; gdouble energy = 0.0; gdouble energy_full = 0.0; gdouble energy_rate = 0.0; gint64 time_to_empty = 0; gint64 time_to_full = 0; gboolean power_supply = FALSE; device = g_ptr_array_index (array, i); g_object_get (device, "type", &kind, "state", &state, "percentage", &percentage, "energy", &energy, "energy-full", &energy_full, "energy-rate", &energy_rate, "time-to-empty", &time_to_empty, "time-to-full", &time_to_full, "power-supply", &power_supply, NULL); /* When we have a UPS, it's either a desktop, and * has no batteries, or a laptop, in which case we * ignore the batteries */ if (kind == UP_DEVICE_KIND_UPS) { kind_total = kind; state_total = state; energy_total = energy; energy_full_total = energy_full; energy_rate_total = energy_rate; time_to_empty_total = time_to_empty; time_to_full_total = time_to_full; percentage_total = percentage; is_present_total = TRUE; break; } if (kind != UP_DEVICE_KIND_BATTERY || power_supply == FALSE) continue; /* If one battery is charging, then the composite is charging * If all batteries are discharging, then the composite is discharging * If all batteries are fully charged, then they're all fully charged * Everything else is unknown */ if (state == UP_DEVICE_STATE_CHARGING) state_total = UP_DEVICE_STATE_CHARGING; else if (state == UP_DEVICE_STATE_DISCHARGING && state_total != UP_DEVICE_STATE_CHARGING) state_total = UP_DEVICE_STATE_DISCHARGING; else if (state == UP_DEVICE_STATE_FULLY_CHARGED && state_total == UP_DEVICE_STATE_UNKNOWN) state_total = UP_DEVICE_STATE_FULLY_CHARGED; /* sum up composite */ kind_total = UP_DEVICE_KIND_BATTERY; is_present_total = TRUE; energy_total += energy; energy_full_total += energy_full; energy_rate_total += energy_rate; time_to_empty_total += time_to_empty; time_to_full_total += time_to_full; /* Will be recalculated for multiple batteries, no worries */ percentage_total += percentage; num_batteries++; } /* Handle multiple batteries */ if (num_batteries <= 1) goto out; g_debug ("Calculating percentage and time to full/to empty for %i batteries", num_batteries); /* use percentage weighted for each battery capacity */ if (energy_full_total > 0.0) percentage_total = 100.0 * energy_total / energy_full_total; /* calculate a quick and dirty time remaining value */ if (energy_rate_total > 0) { if (state_total == UP_DEVICE_STATE_DISCHARGING) time_to_empty_total = 3600 * (energy_total / energy_rate_total); else if (state_total == UP_DEVICE_STATE_CHARGING) time_to_full_total = 3600 * ((energy_full_total - energy_total) / energy_rate_total); } out: g_ptr_array_unref (array); /* Did anything change? */ if (daemon->priv->kind == kind_total && daemon->priv->state == state_total && daemon->priv->energy == energy_total && daemon->priv->energy_full == energy_full_total && daemon->priv->energy_rate == energy_rate_total && daemon->priv->time_to_empty == time_to_empty_total && daemon->priv->time_to_full == time_to_full_total && daemon->priv->percentage == percentage_total) return FALSE; daemon->priv->kind = kind_total; daemon->priv->state = state_total; daemon->priv->energy = energy_total; daemon->priv->energy_full = energy_full_total; daemon->priv->energy_rate = energy_rate_total; daemon->priv->time_to_empty = time_to_empty_total; daemon->priv->time_to_full = time_to_full_total; daemon->priv->percentage = percentage_total; g_object_set (daemon->priv->display_device, "type", kind_total, "state", state_total, "energy", energy_total, "energy-full", energy_full_total, "energy-rate", energy_rate_total, "time-to-empty", time_to_empty_total, "time-to-full", time_to_full_total, "percentage", percentage_total, "is-present", is_present_total, "power-supply", TRUE, "update-time", (guint64) g_get_real_time () / G_USEC_PER_SEC, NULL); return TRUE; } /** * up_daemon_get_warning_level_local: * * As soon as _all_ batteries are low, this is true **/ static gboolean up_daemon_get_warning_level_local (UpDaemon *daemon) { up_daemon_update_display_battery (daemon); if (daemon->priv->kind != UP_DEVICE_KIND_UPS && daemon->priv->kind != UP_DEVICE_KIND_BATTERY) return UP_DEVICE_LEVEL_NONE; if (daemon->priv->kind == UP_DEVICE_KIND_UPS && daemon->priv->state != UP_DEVICE_STATE_DISCHARGING) return UP_DEVICE_LEVEL_NONE; /* Check to see if the batteries have not noticed we are on AC */ if (daemon->priv->kind == UP_DEVICE_KIND_BATTERY && up_daemon_get_on_ac_local (daemon)) return UP_DEVICE_LEVEL_NONE; return up_daemon_compute_warning_level (daemon, daemon->priv->state, daemon->priv->kind, TRUE, /* power_supply */ daemon->priv->percentage, daemon->priv->time_to_empty); } /** * up_daemon_get_on_ac_local: * * As soon as _any_ ac supply goes online, this is true **/ static gboolean up_daemon_get_on_ac_local (UpDaemon *daemon) { guint i; gboolean ret; gboolean result = FALSE; gboolean online; UpDevice *device; GPtrArray *array; /* ask each device */ array = up_device_list_get_array (daemon->priv->power_devices); for (i=0; ilen; i++) { device = (UpDevice *) g_ptr_array_index (array, i); ret = up_device_get_online (device, &online); if (ret && online) { result = TRUE; break; } } g_ptr_array_unref (array); return result; } /** * up_daemon_refresh_battery_devices: **/ static gboolean up_daemon_refresh_battery_devices (UpDaemon *daemon) { guint i; GPtrArray *array; UpDevice *device; /* refresh all devices in array */ array = up_device_list_get_array (daemon->priv->power_devices); for (i=0; ilen; i++) { UpDeviceKind type; gboolean power_supply; device = (UpDevice *) g_ptr_array_index (array, i); /* only refresh battery devices */ g_object_get (device, "type", &type, "power-supply", &power_supply, NULL); if (type == UP_DEVICE_KIND_BATTERY && power_supply) up_device_refresh_internal (device); } g_ptr_array_unref (array); return TRUE; } /** * up_daemon_enumerate_devices: **/ gboolean up_daemon_enumerate_devices (UpDaemon *daemon, DBusGMethodInvocation *context) { guint i; GPtrArray *array; GPtrArray *object_paths; UpDevice *device; /* build a pointer array of the object paths */ object_paths = g_ptr_array_new_with_free_func (g_free); array = up_device_list_get_array (daemon->priv->power_devices); for (i=0; ilen; i++) { device = (UpDevice *) g_ptr_array_index (array, i); g_ptr_array_add (object_paths, g_strdup (up_device_get_object_path (device))); } g_ptr_array_unref (array); /* return it on the bus */ dbus_g_method_return (context, object_paths); /* free */ g_ptr_array_unref (object_paths); return TRUE; } /** * up_daemon_get_display_device: **/ gboolean up_daemon_get_display_device (UpDaemon *daemon, DBusGMethodInvocation *context) { dbus_g_method_return (context, up_device_get_object_path (daemon->priv->display_device)); return TRUE; } /** * up_daemon_get_critical_action: **/ gboolean up_daemon_get_critical_action (UpDaemon *daemon, DBusGMethodInvocation *context) { dbus_g_method_return (context, up_backend_get_critical_action (daemon->priv->backend)); return TRUE; } /** * up_daemon_register_power_daemon: **/ static gboolean up_daemon_register_power_daemon (UpDaemon *daemon) { GError *error = NULL; gboolean ret = FALSE; UpDaemonPrivate *priv = daemon->priv; priv->connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error); if (priv->connection == NULL) { if (error != NULL) { g_critical ("error getting system bus: %s", error->message); g_error_free (error); } goto out; } /* Register the display device */ up_device_register_display_device (daemon->priv->display_device, daemon); /* connect to DBUS */ priv->proxy = dbus_g_proxy_new_for_name (priv->connection, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS); /* register GObject */ dbus_g_connection_register_g_object (priv->connection, "/org/freedesktop/UPower", G_OBJECT (daemon)); /* success */ ret = TRUE; out: return ret; } /** * up_daemon_startup: **/ gboolean up_daemon_startup (UpDaemon *daemon) { gboolean ret; gboolean on_battery; UpDeviceLevel warning_level; UpDaemonPrivate *priv = daemon->priv; /* stop signals and callbacks */ g_debug ("daemon now coldplug"); /* coldplug backend backend */ ret = up_backend_coldplug (priv->backend, daemon); if (!ret) { g_warning ("failed to coldplug backend"); goto out; } /* get battery state */ on_battery = (up_daemon_get_on_battery_local (daemon) && !up_daemon_get_on_ac_local (daemon)); warning_level = up_daemon_get_warning_level_local (daemon); up_daemon_set_on_battery (daemon, on_battery); up_daemon_set_warning_level (daemon, warning_level); /* start signals and callbacks */ g_debug ("daemon now not coldplug"); /* register on bus */ ret = up_daemon_register_power_daemon (daemon); if (!ret) { g_warning ("failed to register"); goto out; } out: return ret; } /** * up_daemon_get_device_list: **/ UpDeviceList * up_daemon_get_device_list (UpDaemon *daemon) { return g_object_ref (daemon->priv->power_devices); } static void changed_props_add_to_msg (gpointer key, gpointer _value, gpointer user_data) { GVariant *value = _value; const gchar *property = key; DBusMessageIter *subiter = user_data; DBusMessageIter v_iter; DBusMessageIter dict_iter; dbus_message_iter_open_container (subiter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_iter); dbus_message_iter_append_basic (&dict_iter, DBUS_TYPE_STRING, &property); if (g_variant_is_of_type (value, G_VARIANT_TYPE_UINT32)) { guint32 val = g_variant_get_uint32 (value); dbus_message_iter_open_container (&dict_iter, DBUS_TYPE_VARIANT, "u", &v_iter); dbus_message_iter_append_basic (&v_iter, DBUS_TYPE_UINT32, &val); } else if (g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN)) { gboolean val = g_variant_get_boolean (value); dbus_message_iter_open_container (&dict_iter, DBUS_TYPE_VARIANT, "b", &v_iter); dbus_message_iter_append_basic (&v_iter, DBUS_TYPE_BOOLEAN, &val); } else if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) { const gchar *val = g_variant_get_string (value, NULL); dbus_message_iter_open_container (&dict_iter, DBUS_TYPE_VARIANT, "s", &v_iter); dbus_message_iter_append_basic (&v_iter, DBUS_TYPE_STRING, &val); } else if (g_variant_is_of_type (value, G_VARIANT_TYPE_DOUBLE)) { gdouble val = g_variant_get_double (value); dbus_message_iter_open_container (&dict_iter, DBUS_TYPE_VARIANT, "d", &v_iter); dbus_message_iter_append_basic (&v_iter, DBUS_TYPE_DOUBLE, &val); } else if (g_variant_is_of_type (value, G_VARIANT_TYPE_UINT64)) { guint64 val = g_variant_get_uint64 (value); dbus_message_iter_open_container (&dict_iter, DBUS_TYPE_VARIANT, "t", &v_iter); dbus_message_iter_append_basic (&v_iter, DBUS_TYPE_UINT64, &val); } else if (g_variant_is_of_type (value, G_VARIANT_TYPE_INT64)) { gint64 val = g_variant_get_int64 (value); dbus_message_iter_open_container (&dict_iter, DBUS_TYPE_VARIANT, "x", &v_iter); dbus_message_iter_append_basic (&v_iter, DBUS_TYPE_INT64, &val); } else { g_assert_not_reached (); } dbus_message_iter_close_container (&dict_iter, &v_iter); dbus_message_iter_close_container (subiter, &dict_iter); } void up_daemon_emit_properties_changed (DBusGConnection *gconnection, const gchar *object_path, const gchar *interface, GHashTable *props) { DBusConnection *connection; DBusMessage *message; DBusMessageIter iter; DBusMessageIter subiter; g_return_if_fail (gconnection != NULL); g_return_if_fail (object_path != NULL); g_return_if_fail (interface != NULL); g_return_if_fail (props != NULL); connection = dbus_g_connection_get_connection (gconnection); message = dbus_message_new_signal (object_path, "org.freedesktop.DBus.Properties", "PropertiesChanged"); dbus_message_iter_init_append (message, &iter); dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &interface); /* changed */ dbus_message_iter_open_container (&iter, DBUS_TYPE_ARRAY, "{sv}", &subiter); g_hash_table_foreach (props, changed_props_add_to_msg, &subiter); dbus_message_iter_close_container (&iter, &subiter); /* invalidated */ dbus_message_iter_open_container (&iter, DBUS_TYPE_ARRAY, "s", &subiter); dbus_message_iter_close_container (&iter, &subiter); dbus_connection_send (connection, message, NULL); dbus_message_unref (message); } static gboolean changed_props_idle_cb (gpointer user_data) { UpDaemon *daemon = user_data; /* D-Bus */ up_daemon_emit_properties_changed (daemon->priv->connection, "/org/freedesktop/UPower", "org.freedesktop.UPower", daemon->priv->changed_props); g_clear_pointer (&daemon->priv->changed_props, g_hash_table_unref); daemon->priv->props_idle_id = 0; return G_SOURCE_REMOVE; } /** * up_daemon_queue_changed_property: **/ static void up_daemon_queue_changed_property (UpDaemon *daemon, const gchar *property, GVariant *value) { g_return_if_fail (UP_IS_DAEMON (daemon)); if (daemon->priv->connection == NULL) return; if (!daemon->priv->changed_props) { daemon->priv->changed_props = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) g_variant_unref); } g_hash_table_insert (daemon->priv->changed_props, (gpointer) property, value); if (daemon->priv->props_idle_id == 0) daemon->priv->props_idle_id = g_idle_add (changed_props_idle_cb, daemon); } /** * up_daemon_set_lid_is_closed: **/ void up_daemon_set_lid_is_closed (UpDaemon *daemon, gboolean lid_is_closed) { UpDaemonPrivate *priv = daemon->priv; /* check if we are ignoring the lid */ if (up_config_get_boolean (priv->config, "IgnoreLid")) { g_debug ("ignoring lid state"); return; } g_debug ("lid_is_closed = %s", lid_is_closed ? "yes" : "no"); priv->lid_is_closed = lid_is_closed; g_object_notify (G_OBJECT (daemon), "lid-is-closed"); up_daemon_queue_changed_property (daemon, "LidIsClosed", g_variant_new_boolean (lid_is_closed)); } /** * up_daemon_set_lid_is_present: **/ void up_daemon_set_lid_is_present (UpDaemon *daemon, gboolean lid_is_present) { UpDaemonPrivate *priv = daemon->priv; /* check if we are ignoring the lid */ if (up_config_get_boolean (priv->config, "IgnoreLid")) { g_debug ("ignoring lid state"); return; } g_debug ("lid_is_present = %s", lid_is_present ? "yes" : "no"); priv->lid_is_present = lid_is_present; g_object_notify (G_OBJECT (daemon), "lid-is-present"); up_daemon_queue_changed_property (daemon, "LidIsPresent", g_variant_new_boolean (lid_is_present)); } /** * up_daemon_set_on_battery: **/ void up_daemon_set_on_battery (UpDaemon *daemon, gboolean on_battery) { UpDaemonPrivate *priv = daemon->priv; g_debug ("on_battery = %s", on_battery ? "yes" : "no"); priv->on_battery = on_battery; g_object_notify (G_OBJECT (daemon), "on-battery"); up_daemon_queue_changed_property (daemon, "OnBattery", g_variant_new_boolean (on_battery)); } static gboolean take_action_timeout_cb (UpDaemon *daemon) { up_backend_take_action (daemon->priv->backend); return G_SOURCE_REMOVE; } /** * up_daemon_set_warning_level: **/ void up_daemon_set_warning_level (UpDaemon *daemon, UpDeviceLevel warning_level) { UpDaemonPrivate *priv = daemon->priv; if (priv->warning_level == warning_level) return; g_debug ("warning_level = %s", up_device_level_to_string (warning_level)); priv->warning_level = warning_level; g_object_set (G_OBJECT (daemon->priv->display_device), "warning-level", warning_level, "update-time", (guint64) g_get_real_time () / G_USEC_PER_SEC, NULL); if (daemon->priv->warning_level == UP_DEVICE_LEVEL_ACTION) { if (daemon->priv->action_timeout_id == 0) { g_debug ("About to take action in %d seconds", UP_DAEMON_ACTION_DELAY); daemon->priv->action_timeout_id = g_timeout_add_seconds (UP_DAEMON_ACTION_DELAY, (GSourceFunc) take_action_timeout_cb, daemon); g_source_set_name_by_id (daemon->priv->action_timeout_id, "[upower] take_action_timeout_cb"); } else { g_debug ("Not taking action, timeout id already set"); } } else { if (daemon->priv->action_timeout_id > 0) { g_debug ("Removing timeout as action level changed"); g_source_remove (daemon->priv->action_timeout_id); } } } UpDeviceLevel up_daemon_compute_warning_level (UpDaemon *daemon, UpDeviceState state, UpDeviceKind kind, gboolean power_supply, gdouble percentage, gint64 time_to_empty) { gboolean use_percentage = TRUE; UpDeviceLevel default_level = UP_DEVICE_LEVEL_NONE; if (state != UP_DEVICE_STATE_DISCHARGING) return UP_DEVICE_LEVEL_NONE; /* Keyboard and mice usually have a coarser * battery level, so this avoids falling directly * into critical (or off) before any warnings */ if (kind == UP_DEVICE_KIND_MOUSE || kind == UP_DEVICE_KIND_KEYBOARD) { if (percentage < 13.0f) return UP_DEVICE_LEVEL_CRITICAL; else if (percentage < 26.0f) return UP_DEVICE_LEVEL_LOW; else return UP_DEVICE_LEVEL_NONE; } else if (kind == UP_DEVICE_KIND_UPS) { default_level = UP_DEVICE_LEVEL_DISCHARGING; } if (power_supply && !daemon->priv->use_percentage_for_policy && time_to_empty > 0.0) use_percentage = FALSE; if (use_percentage) { if (percentage > daemon->priv->low_percentage) return default_level; if (percentage > daemon->priv->critical_percentage) return UP_DEVICE_LEVEL_LOW; if (percentage > daemon->priv->action_percentage) return UP_DEVICE_LEVEL_CRITICAL; return UP_DEVICE_LEVEL_ACTION; } else { if (time_to_empty > daemon->priv->low_time) return default_level; if (time_to_empty > daemon->priv->critical_time) return UP_DEVICE_LEVEL_LOW; if (time_to_empty > daemon->priv->action_time) return UP_DEVICE_LEVEL_CRITICAL; return UP_DEVICE_LEVEL_ACTION; } g_assert_not_reached (); } static void up_daemon_update_warning_level (UpDaemon *daemon) { gboolean ret; UpDaemonPrivate *priv = daemon->priv; UpDeviceLevel warning_level; /* Check if the on_battery and warning_level state has changed */ ret = (up_daemon_get_on_battery_local (daemon) && !up_daemon_get_on_ac_local (daemon)); if (ret != priv->on_battery) { up_daemon_set_on_battery (daemon, ret); } warning_level = up_daemon_get_warning_level_local (daemon); if (warning_level != priv->warning_level) up_daemon_set_warning_level (daemon, warning_level); } /** * up_daemon_device_changed_cb: **/ static void up_daemon_device_changed_cb (UpDevice *device, GParamSpec *pspec, UpDaemon *daemon) { UpDeviceKind type; g_return_if_fail (UP_IS_DAEMON (daemon)); g_return_if_fail (UP_IS_DEVICE (device)); /* refresh battery devices when AC state changes */ g_object_get (device, "type", &type, NULL); if (type == UP_DEVICE_KIND_LINE_POWER) { /* refresh now */ up_daemon_refresh_battery_devices (daemon); } up_daemon_update_warning_level (daemon); } typedef struct { guint id; guint timeout; GSourceFunc callback; } TimeoutData; static void change_idle_timeout (UpDevice *device, GParamSpec *pspec, gpointer user_data) { TimeoutData *data; GSourceFunc callback; UpDaemon *daemon; daemon = up_device_get_daemon (device); data = g_hash_table_lookup (daemon->priv->poll_timeouts, device); callback = data->callback; up_daemon_stop_poll (G_OBJECT (device)); up_daemon_start_poll (G_OBJECT (device), callback); } static void device_destroyed (gpointer user_data, GObject *where_the_object_was) { UpDaemon *daemon = user_data; TimeoutData *data; data = g_hash_table_lookup (daemon->priv->poll_timeouts, where_the_object_was); if (data == NULL) return; g_source_remove (data->id); g_hash_table_remove (daemon->priv->poll_timeouts, where_the_object_was); } static gboolean fire_timeout_callback (gpointer user_data) { UpDevice *device = user_data; TimeoutData *data; UpDaemon *daemon; daemon = up_device_get_daemon (device); data = g_hash_table_lookup (daemon->priv->poll_timeouts, device); g_assert (data); g_debug ("Firing timeout for '%s' after %u seconds", up_device_get_object_path (device), data->timeout); /* Fire the actual callback */ (data->callback) (device); return G_SOURCE_CONTINUE; } static guint calculate_timeout (UpDevice *device) { UpDeviceLevel warning_level; g_object_get (G_OBJECT (device), "warning-level", &warning_level, NULL); if (warning_level >= UP_DEVICE_LEVEL_DISCHARGING) return 30; return 120; } void up_daemon_start_poll (GObject *object, GSourceFunc callback) { UpDaemon *daemon; UpDevice *device; TimeoutData *data; guint timeout; char *name; device = UP_DEVICE (object); daemon = up_device_get_daemon (device); if (g_hash_table_lookup (daemon->priv->poll_timeouts, device) != NULL) { g_warning ("Poll already started for device '%s'", up_device_get_object_path (device)); return; } data = g_new0 (TimeoutData, 1); data->callback = callback; timeout = calculate_timeout (device); data->timeout = timeout; g_signal_connect (device, "notify::warning-level", G_CALLBACK (change_idle_timeout), NULL); g_object_weak_ref (object, device_destroyed, daemon); data->id = g_timeout_add_seconds (timeout, fire_timeout_callback, device); name = g_strdup_printf ("[upower] UpDevice::poll for %s (%u secs)", up_device_get_object_path (device), timeout); g_source_set_name_by_id (data->id, name); g_free (name); g_hash_table_insert (daemon->priv->poll_timeouts, device, data); g_debug ("Setup poll for '%s' every %u seconds", up_device_get_object_path (device), timeout); } void up_daemon_stop_poll (GObject *object) { UpDevice *device; TimeoutData *data; UpDaemon *daemon; device = UP_DEVICE (object); daemon = up_device_get_daemon (device); data = g_hash_table_lookup (daemon->priv->poll_timeouts, device); if (data == NULL) return; g_source_remove (data->id); g_object_weak_unref (object, device_destroyed, daemon); g_hash_table_remove (daemon->priv->poll_timeouts, device); } /** * up_daemon_device_added_cb: **/ static void up_daemon_device_added_cb (UpBackend *backend, GObject *native, UpDevice *device, UpDaemon *daemon) { const gchar *object_path; UpDaemonPrivate *priv = daemon->priv; g_return_if_fail (UP_IS_DAEMON (daemon)); g_return_if_fail (UP_IS_DEVICE (device)); g_return_if_fail (G_IS_OBJECT (native)); /* add to device list */ up_device_list_insert (priv->power_devices, native, G_OBJECT (device)); /* connect, so we get changes */ g_signal_connect (device, "notify", G_CALLBACK (up_daemon_device_changed_cb), daemon); /* emit */ object_path = up_device_get_object_path (device); g_debug ("emitting added: %s", object_path); /* don't crash the session */ if (object_path == NULL) { g_warning ("INTERNAL STATE CORRUPT (device-added): not sending NULL, native:%p, device:%p", native, device); return; } g_signal_emit (daemon, signals[SIGNAL_DEVICE_ADDED], 0, object_path); } /** * up_daemon_device_removed_cb: **/ static void up_daemon_device_removed_cb (UpBackend *backend, GObject *native, UpDevice *device, UpDaemon *daemon) { const gchar *object_path; UpDaemonPrivate *priv = daemon->priv; g_return_if_fail (UP_IS_DAEMON (daemon)); g_return_if_fail (UP_IS_DEVICE (device)); g_return_if_fail (G_IS_OBJECT (native)); /* remove from list */ up_device_list_remove (priv->power_devices, G_OBJECT(device)); /* emit */ object_path = up_device_get_object_path (device); g_debug ("emitting device-removed: %s", object_path); /* don't crash the session */ if (object_path == NULL) { g_warning ("INTERNAL STATE CORRUPT (device-removed): not sending NULL, native:%p, device:%p", native, device); return; } g_signal_emit (daemon, signals[SIGNAL_DEVICE_REMOVED], 0, object_path); /* finalise the object */ g_object_unref (device); /* In case a battery was removed */ up_daemon_refresh_battery_devices (daemon); up_daemon_update_warning_level (daemon); } #define LOAD_OR_DEFAULT(val, str, def) val = (load_default ? def : up_config_get_uint (daemon->priv->config, str)) static void load_percentage_policy (UpDaemon *daemon, gboolean load_default) { LOAD_OR_DEFAULT (daemon->priv->low_percentage, "PercentageLow", 10); LOAD_OR_DEFAULT (daemon->priv->critical_percentage, "PercentageCritical", 3); LOAD_OR_DEFAULT (daemon->priv->action_percentage, "PercentageAction", 2); } static void load_time_policy (UpDaemon *daemon, gboolean load_default) { LOAD_OR_DEFAULT (daemon->priv->low_time, "TimeLow", 1200); LOAD_OR_DEFAULT (daemon->priv->critical_time, "TimeCritical", 300); LOAD_OR_DEFAULT (daemon->priv->action_time, "TimeAction", 120); } #define IS_DESCENDING(x, y, z) (x > y && y > z) static void policy_config_validate (UpDaemon *daemon) { if (daemon->priv->low_percentage >= 100 || daemon->priv->critical_percentage >= 100 || daemon->priv->action_percentage >= 100) { load_percentage_policy (daemon, TRUE); } else if (!IS_DESCENDING (daemon->priv->low_percentage, daemon->priv->critical_percentage, daemon->priv->action_percentage)) { load_percentage_policy (daemon, TRUE); } if (!IS_DESCENDING (daemon->priv->low_time, daemon->priv->critical_time, daemon->priv->action_time)) { load_time_policy (daemon, TRUE); } } /** * up_daemon_init: **/ static void up_daemon_init (UpDaemon *daemon) { daemon->priv = UP_DAEMON_GET_PRIVATE (daemon); daemon->priv->config = up_config_new (); daemon->priv->power_devices = up_device_list_new (); daemon->priv->display_device = up_device_new (); daemon->priv->use_percentage_for_policy = up_config_get_boolean (daemon->priv->config, "UsePercentageForPolicy"); load_percentage_policy (daemon, FALSE); load_time_policy (daemon, FALSE); policy_config_validate (daemon); daemon->priv->backend = up_backend_new (); g_signal_connect (daemon->priv->backend, "device-added", G_CALLBACK (up_daemon_device_added_cb), daemon); g_signal_connect (daemon->priv->backend, "device-removed", G_CALLBACK (up_daemon_device_removed_cb), daemon); daemon->priv->poll_timeouts = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free); } /** * up_daemon_error_quark: **/ GQuark up_daemon_error_quark (void) { static GQuark ret = 0; if (ret == 0) ret = g_quark_from_static_string ("up_daemon_error"); return ret; } #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC } /** * up_daemon_error_get_type: **/ GType up_daemon_error_get_type (void) { static GType etype = 0; if (etype == 0) { static const GEnumValue values[] = { ENUM_ENTRY (UP_DAEMON_ERROR_GENERAL, "GeneralError"), ENUM_ENTRY (UP_DAEMON_ERROR_NOT_SUPPORTED, "NotSupported"), ENUM_ENTRY (UP_DAEMON_ERROR_NO_SUCH_DEVICE, "NoSuchDevice"), { 0, 0, 0 } }; g_assert (UP_DAEMON_NUM_ERRORS == G_N_ELEMENTS (values) - 1); etype = g_enum_register_static ("UpDaemonError", values); } return etype; } /** * up_daemon_get_property: **/ static void up_daemon_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { UpDaemon *daemon = UP_DAEMON (object); UpDaemonPrivate *priv = daemon->priv; switch (prop_id) { case PROP_DAEMON_VERSION: g_value_set_string (value, PACKAGE_VERSION); break; case PROP_ON_BATTERY: g_value_set_boolean (value, priv->on_battery); break; case PROP_LID_IS_CLOSED: g_value_set_boolean (value, priv->lid_is_closed); break; case PROP_LID_IS_PRESENT: g_value_set_boolean (value, priv->lid_is_present); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } /** * up_daemon_class_init: **/ static void up_daemon_class_init (UpDaemonClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = up_daemon_finalize; object_class->get_property = up_daemon_get_property; g_type_class_add_private (klass, sizeof (UpDaemonPrivate)); signals[SIGNAL_DEVICE_ADDED] = g_signal_new ("device-added", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, 0, NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, DBUS_TYPE_G_OBJECT_PATH); signals[SIGNAL_DEVICE_REMOVED] = g_signal_new ("device-removed", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, 0, NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, DBUS_TYPE_G_OBJECT_PATH); g_object_class_install_property (object_class, PROP_DAEMON_VERSION, g_param_spec_string ("daemon-version", "Daemon Version", "The version of the running daemon", NULL, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LID_IS_PRESENT, g_param_spec_boolean ("lid-is-present", "Is a laptop", "If this computer is probably a laptop", FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_ON_BATTERY, g_param_spec_boolean ("on-battery", "On Battery", "Whether the system is running on battery", FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LID_IS_CLOSED, g_param_spec_boolean ("lid-is-closed", "Laptop lid is closed", "If the laptop lid is closed", FALSE, G_PARAM_READABLE)); dbus_g_object_type_install_info (UP_TYPE_DAEMON, &dbus_glib_up_daemon_object_info); dbus_g_error_domain_register (UP_DAEMON_ERROR, NULL, UP_DAEMON_TYPE_ERROR); } /** * up_daemon_finalize: **/ static void up_daemon_finalize (GObject *object) { UpDaemon *daemon = UP_DAEMON (object); UpDaemonPrivate *priv = daemon->priv; if (priv->action_timeout_id != 0) g_source_remove (priv->action_timeout_id); if (priv->props_idle_id != 0) g_source_remove (priv->props_idle_id); g_clear_pointer (&priv->poll_timeouts, g_hash_table_destroy); g_clear_pointer (&daemon->priv->changed_props, g_hash_table_unref); if (priv->proxy != NULL) g_object_unref (priv->proxy); if (priv->connection != NULL) dbus_g_connection_unref (priv->connection); g_object_unref (priv->power_devices); g_object_unref (priv->config); g_object_unref (priv->backend); G_OBJECT_CLASS (up_daemon_parent_class)->finalize (object); } /** * up_daemon_new: **/ UpDaemon * up_daemon_new (void) { return UP_DAEMON (g_object_new (UP_TYPE_DAEMON, NULL)); }