Skip to content
Snippets Groups Projects
power-manager-button.c 55.3 KiB
Newer Older
Eric Koegel's avatar
Eric Koegel committed
 * * Copyright (C) 2014 Eric Koegel <eric@xfce.org>
 * * Copyright (C) 2019 Kacper Piwiński
 *
 * 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
 */

#ifdef HAVE_CONFIG_H
Gaël Bonithon's avatar
Gaël Bonithon committed
#include "config.h"
Gaël Bonithon's avatar
Gaël Bonithon committed
#include "power-manager-button.h"
#include "scalemenuitem.h"
Gaël Bonithon's avatar
Gaël Bonithon committed
#include "common/xfpm-brightness.h"
#include "common/xfpm-common.h"
#include "common/xfpm-config.h"
#include "common/xfpm-debug.h"
#include "common/xfpm-enum-glib.h"
Gaël Bonithon's avatar
Gaël Bonithon committed
#include "common/xfpm-icons.h"
#include "common/xfpm-power-common.h"

#ifdef XFPM_SYSTRAY
#include "src/xfpm-inhibit.h"
#endif
Gaël Bonithon's avatar
Gaël Bonithon committed
#include <libxfce4ui/libxfce4ui.h>
#include <libxfce4util/libxfce4util.h>
#include <upower.h>
#include <xfconf/xfconf.h>
Eric Koegel's avatar
Eric Koegel committed

#define SET_LEVEL_TIMEOUT (50)
struct PowerManagerButtonPrivate
#ifdef XFCE_PLUGIN
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  XfcePanelPlugin *plugin;
Gaël Bonithon's avatar
Gaël Bonithon committed
  GDBusProxy *inhibit_proxy;
Gaël Bonithon's avatar
Gaël Bonithon committed
  XfpmInhibit *inhibit;
Gaël Bonithon's avatar
Gaël Bonithon committed
  XfconfChannel *channel;
Simon Steinbeiss's avatar
Simon Steinbeiss committed

Gaël Bonithon's avatar
Gaël Bonithon committed
  UpClient *upower;
Simon Steinbeiss's avatar
Simon Steinbeiss committed

  /* A list of BatteryDevices  */
Gaël Bonithon's avatar
Gaël Bonithon committed
  GList *devices;
Simon Steinbeiss's avatar
Simon Steinbeiss committed

  /* The left-click popup menu, if one is being displayed */
Gaël Bonithon's avatar
Gaël Bonithon committed
  GtkWidget *menu;
Simon Steinbeiss's avatar
Simon Steinbeiss committed

  /* The actual panel icon image */
Gaël Bonithon's avatar
Gaël Bonithon committed
  GtkWidget *panel_icon_image;
  GtkWidget *panel_presentation_mode;
  GtkWidget *panel_label;
  GtkWidget *hbox;
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  /* Keep track of icon name to redisplay during size changes */
Gaël Bonithon's avatar
Gaël Bonithon committed
  gchar *panel_icon_name;
  gchar *panel_fallback_icon_name;
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  /* Keep track of the last icon size for use during updates */
Gaël Bonithon's avatar
Gaël Bonithon committed
  gint panel_icon_width;
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  /* Keep track of the tooltip */
Gaël Bonithon's avatar
Gaël Bonithon committed
  gchar *tooltip;
Simon Steinbeiss's avatar
Simon Steinbeiss committed

  /* Upower 0.99 has a display device that can be used for the
   * panel image and tooltip description */
Gaël Bonithon's avatar
Gaël Bonithon committed
  UpDevice *display_device;
Simon Steinbeiss's avatar
Simon Steinbeiss committed

Gaël Bonithon's avatar
Gaël Bonithon committed
  XfpmBrightness *brightness;
Simon Steinbeiss's avatar
Simon Steinbeiss committed

  /* display brightness slider widget */
Gaël Bonithon's avatar
Gaël Bonithon committed
  GtkWidget *range;
Simon Steinbeiss's avatar
Simon Steinbeiss committed

  /* filter range value changed events for snappier UI feedback */
Gaël Bonithon's avatar
Gaël Bonithon committed
  guint set_level_timeout;
Gaël Bonithon's avatar
Gaël Bonithon committed
  gint show_panel_label;
  gboolean presentation_mode;
  gboolean show_presentation_indicator;
typedef struct
Gaël Bonithon's avatar
Gaël Bonithon committed
  cairo_surface_t *surface; /* Icon */
  GtkWidget *img; /* Icon image in the menu */
  gchar *details; /* Description of the device + state */
  gchar *object_path; /* UpDevice object path */
  UpDevice *device; /* Pointer to the UpDevice */
  gulong changed_signal_id; /* device changed callback id */
  gulong expose_signal_id; /* expose-event callback id */
  GtkWidget *menu_item; /* The device's item on the menu (if shown) */
} BatteryDevice;
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  PROP_0 = 0,
  PROP_SHOW_PANEL_LABEL,
  PROP_PRESENTATION_MODE,
  PROP_SHOW_PRESENTATION_INDICATOR,
} POWER_MANAGER_BUTTON_PROPERTIES;

Simon Steinbeiss's avatar
Simon Steinbeiss committed
  SIG_ICON_NAME_CHANGED = 0,
  SIG_TOOLTIP_CHANGED,
  SIG_N_SIGNALS,
Gaël Bonithon's avatar
Gaël Bonithon committed
static guint __signals[SIG_N_SIGNALS] = { 0 };
G_DEFINE_TYPE_WITH_PRIVATE (PowerManagerButton, power_manager_button, GTK_TYPE_TOGGLE_BUTTON)
Gaël Bonithon's avatar
Gaël Bonithon committed
static void
power_manager_button_finalize (GObject *object);
static GList *
find_device_in_list (PowerManagerButton *button,
                     const gchar *object_path);
static gboolean
power_manager_button_device_icon_draw (GtkWidget *img,
                                       cairo_t *cr,
                                       gpointer userdata);
static void
power_manager_button_set_icon (PowerManagerButton *button);
static void
power_manager_button_set_label (PowerManagerButton *button,
                                gdouble percentage,
                                guint64 time_to_empty_or_full);
#ifdef XFCE_PLUGIN
Gaël Bonithon's avatar
Gaël Bonithon committed
static void
power_manager_button_toggle_presentation_mode (GtkMenuItem *mi,
                                               GtkSwitch *sw);
static void
power_manager_button_update_presentation_indicator (PowerManagerButton *button);
#endif
Gaël Bonithon's avatar
Gaël Bonithon committed
static void
power_manager_button_update_label (PowerManagerButton *button,
                                   UpDevice *device);
static gboolean
power_manager_button_press_event (GtkWidget *widget,
                                  GdkEventButton *event);
static gboolean
power_manager_button_menu_add_device (PowerManagerButton *button,
                                      BatteryDevice *battery_device,
                                      gboolean append);
static void
battery_device_remove_surface (BatteryDevice *battery_device);
Gaël Bonithon's avatar
Gaël Bonithon committed
static BatteryDevice *
get_display_device (PowerManagerButton *button)
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  GList *item = NULL;
  gdouble highest_percentage = 0;
  BatteryDevice *display_device = NULL;
Gaël Bonithon's avatar
Gaël Bonithon committed
  g_return_val_if_fail (POWER_MANAGER_IS_BUTTON (button), NULL);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  if (button->priv->display_device)
  {
    item = find_device_in_list (button, up_device_get_object_path (button->priv->display_device));
    if (item)
Simon Steinbeiss's avatar
Simon Steinbeiss committed
      return item->data;
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  }
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  /* We want to find the battery or ups device with the highest percentage
   * and use that to get our tooltip from */
  for (item = g_list_first (button->priv->devices); item != NULL; item = g_list_next (item))
  {
    BatteryDevice *battery_device = item->data;
    guint type = 0;
    gdouble percentage;

Gaël Bonithon's avatar
Gaël Bonithon committed
    if (!battery_device->device || !UP_IS_DEVICE (battery_device->device))
Simon Steinbeiss's avatar
Simon Steinbeiss committed
    {
      continue;
    }

    g_object_get (battery_device->device,
                  "kind", &type,
                  "percentage", &percentage,
                  NULL);

    if (type == UP_DEVICE_KIND_BATTERY || type == UP_DEVICE_KIND_UPS)
Simon Steinbeiss's avatar
Simon Steinbeiss committed
      if (highest_percentage < percentage)
      {
        display_device = battery_device;
        highest_percentage = percentage;
      }
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  }
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  return display_device;
Gaël Bonithon's avatar
Gaël Bonithon committed
static GList *
find_device_in_list (PowerManagerButton *button,
                     const gchar *object_path)
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  GList *item = NULL;
Gaël Bonithon's avatar
Gaël Bonithon committed
  g_return_val_if_fail (POWER_MANAGER_IS_BUTTON (button), NULL);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  for (item = g_list_first (button->priv->devices); item != NULL; item = g_list_next (item))
  {
    BatteryDevice *battery_device = item->data;
    if (battery_device == NULL)
      XFPM_DEBUG ("!battery_device");
Simon Steinbeiss's avatar
Simon Steinbeiss committed
      continue;
Simon Steinbeiss's avatar
Simon Steinbeiss committed
    if (g_strcmp0 (battery_device->object_path, object_path) == 0)
      return item;
  }

  return NULL;
}

static void
power_manager_button_set_icon (PowerManagerButton *button)
{
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  g_return_if_fail (GTK_IS_WIDGET (button->priv->panel_presentation_mode));
Gaël Bonithon's avatar
Gaël Bonithon committed
  if (gtk_icon_theme_has_icon (gtk_icon_theme_get_default (), button->priv->panel_icon_name))
    gtk_image_set_from_icon_name (GTK_IMAGE (button->priv->panel_icon_image), button->priv->panel_icon_name, GTK_ICON_SIZE_BUTTON);
  else
    gtk_image_set_from_icon_name (GTK_IMAGE (button->priv->panel_icon_image), button->priv->panel_fallback_icon_name, GTK_ICON_SIZE_BUTTON);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  gtk_image_set_pixel_size (GTK_IMAGE (button->priv->panel_icon_image), button->priv->panel_icon_width);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  gtk_image_set_pixel_size (GTK_IMAGE (button->priv->panel_presentation_mode), button->priv->panel_icon_width);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  /* Notify others the icon name changed */
  g_signal_emit (button, __signals[SIG_ICON_NAME_CHANGED], 0);
Eric Koegel's avatar
Eric Koegel committed
static void
power_manager_button_set_tooltip (PowerManagerButton *button)
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  BatteryDevice *display_device = get_display_device (button);

  if (!GTK_IS_WIDGET (button))
  {
    g_critical ("power_manager_button_set_tooltip: !GTK_IS_WIDGET (button)");
    return;
  }

  if (button->priv->tooltip != NULL)
  {
    g_free (button->priv->tooltip);
    button->priv->tooltip = NULL;
  }

  if (display_device)
  {
    /* if we have something, display it */
Gaël Bonithon's avatar
Gaël Bonithon committed
    if (display_device->details)
Gaël Bonithon's avatar
Gaël Bonithon committed
      button->priv->tooltip = g_strdup (display_device->details);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
      gtk_widget_set_tooltip_markup (GTK_WIDGET (button), display_device->details);
      /* Tooltip changed! */
      g_signal_emit (button, __signals[SIG_TOOLTIP_CHANGED], 0);
      return;
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  }
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  /* Odds are this is a desktop without any batteries attached */
  button->priv->tooltip = g_strdup (_("Display battery levels for attached devices"));
  gtk_widget_set_tooltip_text (GTK_WIDGET (button), button->priv->tooltip);
  /* Tooltip changed! */
  g_signal_emit (button, __signals[SIG_TOOLTIP_CHANGED], 0);
const gchar *
power_manager_button_get_icon_name (PowerManagerButton *button)
  return button->priv->panel_icon_name;
const gchar *
power_manager_button_get_tooltip (PowerManagerButton *button)
{
  return button->priv->tooltip;
Gaël Bonithon's avatar
Gaël Bonithon committed
power_manager_button_set_label (PowerManagerButton *button,
                                gdouble percentage,
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  gchar *label_string = NULL;
Gaël Bonithon's avatar
Gaël Bonithon committed
  gint hours;
  gint minutes;
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  gchar *remaining_time = NULL;

  /* Create the short timestring in the format hh:mm */
Gaël Bonithon's avatar
Gaël Bonithon committed
  minutes = (int) ((time_to_empty_or_full / 60.0) + 0.5);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  if (minutes < 60)
  {
    if (minutes < 10)
      remaining_time = g_strdup_printf ("0:0%d", minutes);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
      remaining_time = g_strdup_printf ("0:%d", minutes);
  }
  else
  {
    hours = minutes / 60;
    minutes = minutes % 60;
    if (minutes < 10)
      remaining_time = g_strdup_printf ("%d:0%d", hours, minutes);
    else
      remaining_time = g_strdup_printf ("%d:%d", hours, minutes);
  }
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  /* Set the label accordingly or hide it if the battery is full */
  if (button->priv->show_panel_label == PANEL_LABEL_PERCENTAGE)
Simon Steinbeiss's avatar
Simon Steinbeiss committed
    label_string = g_strdup_printf ("%d%%", (int) percentage);
  else if (button->priv->show_panel_label == PANEL_LABEL_TIME)
Simon Steinbeiss's avatar
Simon Steinbeiss committed
    label_string = g_strdup_printf ("%s", remaining_time);
  else if (button->priv->show_panel_label == PANEL_LABEL_PERCENTAGE_AND_TIME)
    label_string = g_strdup_printf ("%d%% - %s", (int) percentage, remaining_time);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  gtk_label_set_text (GTK_LABEL (button->priv->panel_label), label_string);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  g_free (label_string);
  g_free (remaining_time);
static gboolean
Gaël Bonithon's avatar
Gaël Bonithon committed
power_manager_button_device_icon_draw (GtkWidget *img,
                                       cairo_t *cr,
                                       gpointer userdata)
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  UpDevice *device = NULL;
  guint type = 0, state = 0;
  gdouble percentage;
  gint height, width;
  gdouble min_height = 2;
  PangoLayout *layout = NULL;
  PangoRectangle ink_extent, log_extent;
  GtkAllocation allocation;

  /* sanity checks */
  if (!img || !GTK_IS_WIDGET (img))
    return FALSE;
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  if (UP_IS_DEVICE (userdata))
  {
Gaël Bonithon's avatar
Gaël Bonithon committed
    device = UP_DEVICE (userdata);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
    g_object_get (device,
                  "kind", &type,
                  "state", &state,
                  "percentage", &percentage,
                  NULL);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
    /* Don't draw the progressbar for Battery */
    if (type == UP_DEVICE_KIND_BATTERY)
      return FALSE;
  }
  else
  {
    /* If the UpDevice hasn't fully updated yet it then we'll want
     * a question mark for sure. */
    state = UP_DEVICE_STATE_UNKNOWN;
  }

  gtk_widget_get_allocation (img, &allocation);

  width = allocation.width;
  height = allocation.height;

  if (state != UP_DEVICE_STATE_UNKNOWN)
  {
    /* Draw the trough of the progressbar */
    cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
    cairo_set_line_width (cr, 1.0);
    cairo_rectangle (cr, width - 3.5, allocation.y + 1.5, 5, height - 2);
    cairo_set_source_rgb (cr, 0.87, 0.87, 0.87);
    cairo_fill_preserve (cr);
    cairo_set_source_rgb (cr, 0.53, 0.54, 0.52);
    cairo_stroke (cr);

    /* Draw the fill of the progressbar
       Use yellow for 20% and below, green for 100%, red for 5% and below and blue for the rest */
    cairo_set_operator (cr, CAIRO_OPERATOR_OVER);

    if ((height * (percentage / 100)) > min_height)
Gaël Bonithon's avatar
Gaël Bonithon committed
      min_height = (height - 3) * (percentage / 100);
Simon Steinbeiss's avatar
Simon Steinbeiss committed

    cairo_rectangle (cr, width - 3, allocation.y + height - min_height - 1, 4, min_height);
    if (percentage > 5 && percentage < 20)
Gaël Bonithon's avatar
Gaël Bonithon committed
      cairo_set_source_rgb (cr, 0.93, 0.83, 0.0);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
    else if (percentage > 20 && percentage < 100)
Gaël Bonithon's avatar
Gaël Bonithon committed
      cairo_set_source_rgb (cr, 0.2, 0.4, 0.64);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
    else if (percentage == 100)
Gaël Bonithon's avatar
Gaël Bonithon committed
      cairo_set_source_rgb (cr, 0.45, 0.82, 0.08);
Gaël Bonithon's avatar
Gaël Bonithon committed
      cairo_set_source_rgb (cr, 0.94, 0.16, 0.16);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
    cairo_fill (cr);

    cairo_rectangle (cr, width - 2.5, allocation.y + 2.5, 3, height - 4);
    cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.75);
    cairo_stroke (cr);
  }
  else
  {
    /* Draw a bubble with a question mark for devices with unknown state */
    cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
    cairo_set_line_width (cr, 1.0);
Gaël Bonithon's avatar
Gaël Bonithon committed
    cairo_arc (cr, width - 4.5, allocation.y + 6.5, 6, 0, 2 * 3.14159);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
    cairo_set_source_rgb (cr, 0.2, 0.54, 0.9);
    cairo_fill_preserve (cr);
    cairo_set_source_rgb (cr, 0.1, 0.37, 0.6);
    cairo_stroke (cr);

    layout = gtk_widget_create_pango_layout (GTK_WIDGET (img), "?");
    pango_layout_set_font_description (layout, pango_font_description_from_string ("Sans Bold 9"));
    pango_layout_get_pixel_extents (layout, &ink_extent, &log_extent);
    cairo_move_to (cr, (width - 5.5) - (log_extent.width / 2), (allocation.y + 5.5) - (log_extent.height / 2));
    cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
    pango_cairo_show_layout (cr, layout);
  }

  if (layout)
    g_object_unref (layout);

  return FALSE;
static void
Gaël Bonithon's avatar
Gaël Bonithon committed
power_manager_button_update_device_icon_and_details (PowerManagerButton *button,
                                                     UpDevice *device)
Gaël Bonithon's avatar
Gaël Bonithon committed
  GList *item;
  BatteryDevice *battery_device;
  BatteryDevice *display_device;
  const gchar *object_path = up_device_get_object_path (device);
  gchar *details = NULL;
  gchar *icon_name = NULL;
  gchar *menu_icon_name = NULL;
  gint scale_factor;
  GdkPixbuf *pix;
  cairo_surface_t *surface = NULL;
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  if (!POWER_MANAGER_IS_BUTTON (button))
    return;
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  item = find_device_in_list (button, object_path);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  if (item == NULL)
    return;
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  battery_device = item->data;
  if (button->priv->upower != NULL)
  {
    icon_name = get_device_icon_name (button->priv->upower, device, TRUE);
    menu_icon_name = get_device_icon_name (button->priv->upower, device, FALSE);
    details = get_device_description (button->priv->upower, device);
  }
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  /* If UPower doesn't give us an icon, just use the default */
Gaël Bonithon's avatar
Gaël Bonithon committed
  if (g_strcmp0 (menu_icon_name, "") == 0)
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  {
    /* ignore empty icon names */
    g_free (menu_icon_name);
    menu_icon_name = NULL;
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  }
  if (menu_icon_name == NULL)
    menu_icon_name = g_strdup (PANEL_DEFAULT_ICON);
  scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (button));
  pix = gtk_icon_theme_load_icon_for_scale (gtk_icon_theme_get_default (),
                                            menu_icon_name,
                                            32,
                                            scale_factor,
                                            GTK_ICON_LOOKUP_USE_BUILTIN
Gaël Bonithon's avatar
Gaël Bonithon committed
                                              | GTK_ICON_LOOKUP_FORCE_SIZE,
                                            NULL);
  if (G_LIKELY (pix != NULL))
  {
    surface = gdk_cairo_surface_create_from_pixbuf (pix,
                                                    scale_factor,
                                                    gtk_widget_get_window (GTK_WIDGET (button)));
    g_object_unref (pix);
  }
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  if (battery_device->details)
    g_free (battery_device->details);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  battery_device->details = details;
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  /* If we had an image before, remove it and the callback */
Gaël Bonithon's avatar
Gaël Bonithon committed
  battery_device_remove_surface (battery_device);
  battery_device->surface = surface;
Simon Steinbeiss's avatar
Simon Steinbeiss committed

  /* Get the display device, which may now be this one */
  display_device = get_display_device (button);
  if (battery_device == display_device)
  {
    XFPM_DEBUG ("this is the display device, updating");
Simon Steinbeiss's avatar
Simon Steinbeiss committed
    /* update the icon */
    g_free (button->priv->panel_icon_name);
correctmost's avatar
correctmost committed
    g_free (button->priv->panel_fallback_icon_name);
#ifdef XFCE_PLUGIN
    button->priv->panel_icon_name = g_strdup (icon_name);
    button->priv->panel_fallback_icon_name = g_strdup_printf ("%s-%s", menu_icon_name, "symbolic");
Simon Steinbeiss's avatar
Simon Steinbeiss committed
    button->priv->panel_icon_name = g_strdup (icon_name);
    button->priv->panel_fallback_icon_name = g_strdup (menu_icon_name);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
    power_manager_button_set_icon (button);
    /* update the tooltip */
    power_manager_button_set_tooltip (button);
    /* update the label */
    power_manager_button_update_label (button, device);
  }
  g_free (icon_name);
  g_free (menu_icon_name);
Simon Steinbeiss's avatar
Simon Steinbeiss committed

  /* If the menu is being displayed, update it */
  if (button->priv->menu && battery_device->menu_item)
  {
    gtk_menu_item_set_label (GTK_MENU_ITEM (battery_device->menu_item), details);

    /* update the image, keep track of the signal ids and the img
     * so we can disconnect it later */
    battery_device->img = gtk_image_new_from_surface (battery_device->surface);
    g_object_ref (battery_device->img);
Gaël Bonithon's avatar
Gaël Bonithon committed
    G_GNUC_BEGIN_IGNORE_DEPRECATIONS
    gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (battery_device->menu_item), battery_device->img);
    G_GNUC_END_IGNORE_DEPRECATIONS
Simon Steinbeiss's avatar
Simon Steinbeiss committed
    battery_device->expose_signal_id = g_signal_connect_after (G_OBJECT (battery_device->img),
                                                               "draw",
                                                               G_CALLBACK (power_manager_button_device_icon_draw),
                                                               device);
  }
static void
Gaël Bonithon's avatar
Gaël Bonithon committed
device_changed_cb (UpDevice *device,
                   GParamSpec *pspec,
                   PowerManagerButton *button)
Gaël Bonithon's avatar
Gaël Bonithon committed
  power_manager_button_update_device_icon_and_details (button, device);
Gaël Bonithon's avatar
Gaël Bonithon committed
power_manager_button_add_device (UpDevice *device,
                                 PowerManagerButton *button)
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  BatteryDevice *battery_device;
  guint type = 0;
Gaël Bonithon's avatar
Gaël Bonithon committed
  const gchar *object_path = up_device_get_object_path (device);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  gulong signal_id;
Gaël Bonithon's avatar
Gaël Bonithon committed
  g_return_if_fail (POWER_MANAGER_IS_BUTTON (button));
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  /* don't add the same device twice */
  if (find_device_in_list (button, object_path))
    return;
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  battery_device = g_new0 (BatteryDevice, 1);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  /* hack, this depends on XFPM_DEVICE_TYPE_* being in sync with UP_DEVICE_KIND_* */
  g_object_get (device,
                "kind", &type,
                NULL);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  signal_id = g_signal_connect (device, "notify", G_CALLBACK (device_changed_cb), button);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  /* populate the struct */
  battery_device->object_path = g_strdup (object_path);
  battery_device->changed_signal_id = signal_id;
Gaël Bonithon's avatar
Gaël Bonithon committed
  battery_device->device = g_object_ref (device);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  /* add it to the list */
  button->priv->devices = g_list_append (button->priv->devices, battery_device);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  /* Add the icon and description for the device */
  power_manager_button_update_device_icon_and_details (button, device);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  /* If the menu is being shown, add this new device to it */
  if (button->priv->menu)
  {
    power_manager_button_menu_add_device (button, battery_device, FALSE);
  }
/* This function unrefs the pix and img from the battery device and
 * disconnects the expose-event callback on the img.
 */
static void
battery_device_remove_surface (BatteryDevice *battery_device)
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  if (battery_device == NULL)
    return;
  if (battery_device->surface != NULL)
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  {
    if (GTK_IS_WIDGET (battery_device->img))
Simon Steinbeiss's avatar
Simon Steinbeiss committed
      if (battery_device->expose_signal_id != 0)
      {
Gaël Bonithon's avatar
Gaël Bonithon committed
        g_signal_handler_disconnect (battery_device->img, battery_device->expose_signal_id);
        battery_device->expose_signal_id = 0;
Simon Steinbeiss's avatar
Simon Steinbeiss committed
      }
      g_object_unref (battery_device->img);
      battery_device->img = NULL;
    cairo_surface_destroy (battery_device->surface);
    battery_device->surface = NULL;
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  }
Gaël Bonithon's avatar
Gaël Bonithon committed
remove_battery_device (PowerManagerButton *button,
                       BatteryDevice *battery_device)
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  g_return_if_fail (POWER_MANAGER_IS_BUTTON (button));
  g_return_if_fail (battery_device != NULL);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  /* If it is being shown in the menu, remove it */
  if (battery_device->menu_item && button->priv->menu)
    gtk_container_remove (GTK_CONTAINER (button->priv->menu), battery_device->menu_item);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  g_free (battery_device->details);
  g_free (battery_device->object_path);
  battery_device_remove_surface (battery_device);
Gaël Bonithon's avatar
Gaël Bonithon committed
  if (battery_device->device != NULL && UP_IS_DEVICE (battery_device->device))
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  {
    /* disconnect the signal handler if we were using it */
    if (battery_device->changed_signal_id != 0)
      g_signal_handler_disconnect (battery_device->device, battery_device->changed_signal_id);
    battery_device->changed_signal_id = 0;
Simon Steinbeiss's avatar
Simon Steinbeiss committed
    g_object_unref (battery_device->device);
    battery_device->device = NULL;
  }
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  g_free (battery_device);
Gaël Bonithon's avatar
Gaël Bonithon committed
power_manager_button_remove_device (PowerManagerButton *button,
                                    const gchar *object_path)
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  GList *item;
  BatteryDevice *battery_device;
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  item = find_device_in_list (button, object_path);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  if (item == NULL)
    return;
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  battery_device = item->data;
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  /* Remove its resources */
  remove_battery_device (button, battery_device);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  /* remove it item and free the battery device */
  button->priv->devices = g_list_delete_link (button->priv->devices, item);
Gaël Bonithon's avatar
Gaël Bonithon committed
device_added_cb (UpClient *upower,
                 UpDevice *device,
                 PowerManagerButton *button)
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  power_manager_button_add_device (device, button);
Gaël Bonithon's avatar
Gaël Bonithon committed
device_removed_cb (UpClient *upower,
                   const gchar *object_path,
                   PowerManagerButton *button)
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  power_manager_button_remove_device (button, object_path);
power_manager_button_add_all_devices (PowerManagerButton *button)
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  GPtrArray *array = NULL;
  guint i;
  if (button->priv->upower != NULL)
  {
    button->priv->display_device = up_client_get_display_device (button->priv->upower);
    power_manager_button_add_device (button->priv->display_device, button);
    array = up_client_get_devices2 (button->priv->upower);
  }
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  if (array)
  {
    for (i = 0; i < array->len; i++)
Simon Steinbeiss's avatar
Simon Steinbeiss committed
      UpDevice *device = g_ptr_array_index (array, i);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
      power_manager_button_add_device (device, button);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
    g_ptr_array_free (array, TRUE);
  }
static void
power_manager_button_remove_all_devices (PowerManagerButton *button)
{
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  GList *item = NULL;
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  g_return_if_fail (POWER_MANAGER_IS_BUTTON (button));
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  for (item = g_list_first (button->priv->devices); item != NULL; item = g_list_next (item))
  {
    BatteryDevice *battery_device = item->data;
    if (battery_device == NULL)
      XFPM_DEBUG ("!battery_device");
Simon Steinbeiss's avatar
Simon Steinbeiss committed
      continue;
Simon Steinbeiss's avatar
Simon Steinbeiss committed

    /* Remove its resources */
    remove_battery_device (button, battery_device);
  }
Gaël Bonithon's avatar
Gaël Bonithon committed
power_manager_button_scroll_event (GtkWidget *widget,
                                   GdkEventScroll *ev)
  PowerManagerButton *button = POWER_MANAGER_BUTTON (widget);
  if (button->priv->brightness == NULL)
  if (ev->direction == GDK_SCROLL_UP || ev->direction == GDK_SCROLL_DOWN)
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  {
Gaël Bonithon's avatar
Gaël Bonithon committed
    gboolean (*scroll_brightness) (XfpmBrightness *) = ev->direction == GDK_SCROLL_UP ? xfpm_brightness_increase
                                                                                      : xfpm_brightness_decrease;
    if (scroll_brightness (button->priv->brightness) && button->priv->range != NULL)
    {
      gint32 level;
      if (xfpm_brightness_get_level (button->priv->brightness, &level))
        gtk_range_set_value (GTK_RANGE (button->priv->range), level);
    }
Simon Steinbeiss's avatar
Simon Steinbeiss committed
    return TRUE;
  }

power_manager_button_set_property (GObject *object,
                                   guint property_id,
                                   const GValue *value,
                                   GParamSpec *pspec)
#ifdef XFCE_PLUGIN
  PowerManagerButton *button = POWER_MANAGER_BUTTON (object);
#endif
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  switch (property_id)
  {
Simon Steinbeiss's avatar
Simon Steinbeiss committed
    case PROP_SHOW_PANEL_LABEL:
      button->priv->show_panel_label = g_value_get_int (value);
      power_manager_button_update_label (button, button->priv->display_device);
      break;
    case PROP_PRESENTATION_MODE:
      button->priv->presentation_mode = g_value_get_boolean (value);
      if (GTK_IS_WIDGET (button->priv->panel_presentation_mode))
Gaël Bonithon's avatar
Gaël Bonithon committed
        power_manager_button_update_presentation_indicator (button);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
      break;
    case PROP_SHOW_PRESENTATION_INDICATOR:
      button->priv->show_presentation_indicator = g_value_get_boolean (value);
      if (GTK_IS_WIDGET (button->priv->panel_presentation_mode))
Gaël Bonithon's avatar
Gaël Bonithon committed
        power_manager_button_update_presentation_indicator (button);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
      break;
#else
    case PROP_SHOW_PANEL_LABEL:
    case PROP_PRESENTATION_MODE:
    case PROP_SHOW_PRESENTATION_INDICATOR:
      break;
Simon Steinbeiss's avatar
Simon Steinbeiss committed
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
  }
Gaël Bonithon's avatar
Gaël Bonithon committed
power_manager_button_get_property (GObject *object,
                                   guint property_id,
                                   GValue *value,
                                   GParamSpec *pspec)
#ifdef XFCE_PLUGIN
  PowerManagerButton *button = POWER_MANAGER_BUTTON (object);
#endif
Gaël Bonithon's avatar
Gaël Bonithon committed
  switch (property_id)
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  {
Simon Steinbeiss's avatar
Simon Steinbeiss committed
    case PROP_SHOW_PANEL_LABEL:
      g_value_set_int (value, button->priv->show_panel_label);
      break;
    case PROP_PRESENTATION_MODE:
      g_value_set_boolean (value, button->priv->presentation_mode);
      break;
    case PROP_SHOW_PRESENTATION_INDICATOR:
      g_value_set_boolean (value, button->priv->show_presentation_indicator);
      break;
#else
    case PROP_SHOW_PANEL_LABEL:
    case PROP_PRESENTATION_MODE:
    case PROP_SHOW_PRESENTATION_INDICATOR:
      break;
Simon Steinbeiss's avatar
Simon Steinbeiss committed
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
  }
static void
set_brightness_properties (PowerManagerButton *button)
{
  gint32 level = xfconf_channel_get_int (button->priv->channel, XFPM_PROPERTIES_PREFIX BRIGHTNESS_SLIDER_MIN_LEVEL, DEFAULT_BRIGHTNESS_SLIDER_MIN_LEVEL);
  guint step_count = xfconf_channel_get_uint (button->priv->channel, XFPM_PROPERTIES_PREFIX BRIGHTNESS_STEP_COUNT, DEFAULT_BRIGHTNESS_STEP_COUNT);
  gboolean exponential = xfconf_channel_get_bool (button->priv->channel, XFPM_PROPERTIES_PREFIX BRIGHTNESS_EXPONENTIAL, DEFAULT_BRIGHTNESS_EXPONENTIAL);

  /* order accounts: default min level depends on step count */
  xfpm_brightness_set_step_count (button->priv->brightness, step_count, exponential);
  xfpm_brightness_set_min_level (button->priv->brightness, level);
  if (button->priv->range != NULL)
    gtk_range_set_range (GTK_RANGE (button->priv->range),
                         xfpm_brightness_get_min_level (button->priv->brightness),
                         xfpm_brightness_get_max_level (button->priv->brightness));
}

power_manager_button_class_init (PowerManagerButtonClass *klass)
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

  object_class->finalize = power_manager_button_finalize;
  object_class->set_property = power_manager_button_set_property;
  object_class->get_property = power_manager_button_get_property;

  widget_class->button_press_event = power_manager_button_press_event;
  widget_class->scroll_event = power_manager_button_scroll_event;

  __signals[SIG_TOOLTIP_CHANGED] = g_signal_new ("tooltip-changed",
                                                 POWER_MANAGER_TYPE_BUTTON,
                                                 G_SIGNAL_RUN_LAST,
Gaël Bonithon's avatar
Gaël Bonithon committed
                                                 G_STRUCT_OFFSET (PowerManagerButtonClass, tooltip_changed),
Simon Steinbeiss's avatar
Simon Steinbeiss committed
                                                 NULL, NULL,
                                                 g_cclosure_marshal_VOID__VOID,
                                                 G_TYPE_NONE, 0);

  __signals[SIG_ICON_NAME_CHANGED] = g_signal_new ("icon-name-changed",
                                                   POWER_MANAGER_TYPE_BUTTON,
                                                   G_SIGNAL_RUN_LAST,
Gaël Bonithon's avatar
Gaël Bonithon committed
                                                   G_STRUCT_OFFSET (PowerManagerButtonClass, icon_name_changed),
                                                   NULL, NULL,
                                                   g_cclosure_marshal_VOID__VOID,
                                                   G_TYPE_NONE, 0);

Gaël Bonithon's avatar
Gaël Bonithon committed
#define XFPM_PARAM_FLAGS (G_PARAM_READWRITE \
                          | G_PARAM_CONSTRUCT \
                          | G_PARAM_STATIC_NAME \
                          | G_PARAM_STATIC_NICK \
                          | G_PARAM_STATIC_BLURB)
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  g_object_class_install_property (object_class, PROP_SHOW_PANEL_LABEL,
                                   g_param_spec_int (SHOW_PANEL_LABEL,
                                                     NULL, NULL,
                                                     0, N_PANEL_LABELS - 1, DEFAULT_SHOW_PANEL_LABEL,
Simon Steinbeiss's avatar
Simon Steinbeiss committed
                                                     XFPM_PARAM_FLAGS));

  g_object_class_install_property (object_class, PROP_PRESENTATION_MODE,
                                   g_param_spec_boolean (PRESENTATION_MODE,
                                                         NULL, NULL,
                                                         DEFAULT_PRESENTATION_MODE,
Simon Steinbeiss's avatar
Simon Steinbeiss committed
                                                         XFPM_PARAM_FLAGS));

  g_object_class_install_property (object_class, PROP_SHOW_PRESENTATION_INDICATOR,
                                   g_param_spec_boolean (SHOW_PRESENTATION_INDICATOR,
                                                         NULL, NULL,
                                                         DEFAULT_SHOW_PRESENTATION_INDICATOR,
Simon Steinbeiss's avatar
Simon Steinbeiss committed
                                                         XFPM_PARAM_FLAGS));
#ifdef XFCE_PLUGIN
static void
inhibit_proxy_ready_cb (GObject *source_object,
                        GAsyncResult *res,
                        gpointer user_data)
{
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  GError *error = NULL;
  PowerManagerButton *button = POWER_MANAGER_BUTTON (user_data);

  button->priv->inhibit_proxy = g_dbus_proxy_new_finish (res, &error);
  if (error != NULL)
  {
    g_warning ("error getting inhibit proxy: %s", error->message);
    g_clear_error (&error);
  }
power_manager_button_init (PowerManagerButton *button)
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  GError *error = NULL;
  GtkCssProvider *css_provider;
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  button->priv = power_manager_button_get_instance_private (button);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  gtk_widget_set_can_default (GTK_WIDGET (button), FALSE);
  gtk_widget_set_can_focus (GTK_WIDGET (button), FALSE);
  gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
  gtk_widget_set_focus_on_click (GTK_WIDGET (button), FALSE);
  gtk_widget_set_name (GTK_WIDGET (button), "xfce4-power-manager-plugin");

  button->priv->brightness = xfpm_brightness_new ();
  button->priv->set_level_timeout = 0;

Gaël Bonithon's avatar
Gaël Bonithon committed
  button->priv->upower = up_client_new ();
  if (!xfconf_init (&error))
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  {
Gaël Bonithon's avatar
Gaël Bonithon committed
      g_critical ("xfconf_init failed: %s", error->message);
      g_error_free (error);
    }
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  }
  else
  {
    button->priv->channel = xfconf_channel_get (XFPM_CHANNEL);
    if (button->priv->brightness != NULL)
    {
      set_brightness_properties (button);
      g_signal_connect_object (button->priv->channel, "property-changed::" XFPM_PROPERTIES_PREFIX BRIGHTNESS_SLIDER_MIN_LEVEL,
                               G_CALLBACK (set_brightness_properties), button, G_CONNECT_SWAPPED);
      g_signal_connect_object (button->priv->channel, "property-changed::" XFPM_PROPERTIES_PREFIX BRIGHTNESS_STEP_COUNT,
                               G_CALLBACK (set_brightness_properties), button, G_CONNECT_SWAPPED);
      g_signal_connect_object (button->priv->channel, "property-changed::" XFPM_PROPERTIES_PREFIX BRIGHTNESS_EXPONENTIAL,
                               G_CALLBACK (set_brightness_properties), button, G_CONNECT_SWAPPED);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  }
#ifdef XFCE_PLUGIN
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  g_dbus_proxy_new (g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL),
                    G_DBUS_PROXY_FLAGS_NONE,
                    NULL,
                    "org.freedesktop.PowerManagement",
                    "/org/freedesktop/PowerManagement/Inhibit",
                    "org.freedesktop.PowerManagement.Inhibit",
                    NULL,
                    inhibit_proxy_ready_cb,
                    button);
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  button->priv->inhibit = xfpm_inhibit_new ();
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  /* Sane defaults for the systray and panel icon */
Simon Steinbeiss's avatar
Simon Steinbeiss committed
  button->priv->panel_icon_name = g_strdup (PANEL_DEFAULT_ICON_SYMBOLIC);
  button->priv->panel_fallback_icon_name = g_strdup (PANEL_DEFAULT_ICON_SYMBOLIC);