ede/ede-panel/applets/battery-monitor/BatteryMonitor.cpp

303 lines
8.3 KiB
C++

/*
* Copyright (C) 2013-2016 Sanel Zukan
*
* 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
#include <config.h>
#endif
#include <stdio.h>
#include <FL/Fl_Box.H>
#include <edelib/Nls.h>
#include "Applet.h"
#ifdef EDELIB_HAVE_DBUS
#include <edelib/EdbusConnection.h>
#include <edelib/EdbusMessage.h>
#include <edelib/EdbusList.h>
#include <edelib/EdbusObjectPath.h>
#include <edelib/EdbusData.h>
#include <edelib/List.h>
#include <edelib/Debug.h>
#include <edelib/IconLoader.h>
EDELIB_NS_USING(EdbusConnection)
EDELIB_NS_USING(EdbusMessage)
EDELIB_NS_USING(EdbusList)
EDELIB_NS_USING(EdbusObjectPath)
EDELIB_NS_USING(EdbusData)
EDELIB_NS_USING(EdbusVariant)
EDELIB_NS_USING(IconLoader)
EDELIB_NS_USING(ICON_SIZE_SMALL)
EDELIB_NS_USING(list)
EDELIB_NS_USING(EDBUS_SYSTEM)
#define UPOWER_TYPE_BATTERY 2
#define UPOWER_STATE_CHARGING 1
#define UPOWER_STATE_DISCHARGING 2
#define UPOWER_STATE_EMPTY 3
#define UPOWER_STATE_CHARGED 4
#define UPOWER_SERVICE "org.freedesktop.UPower"
#define UPOWER_INTERFACE "org.freedesktop.UPower.Device"
#define UPOWER_PATH "/org/freedesktop/UPower"
#define BATTERY_MIN 10 /* minimal time before icon to BATTERY_CAUTION_IMG was changed */
#define BATTERY_IMG "battery"
#define BATTERY_CAUTION_IMG "battery-caution"
#define BATTERY_NO_CONNECTION "dialog-warning"
typedef list<EdbusObjectPath> BatteryList;
typedef list<EdbusObjectPath>::iterator BatteryListIt;
/*
* For now all query-ing is done via UPower. Applet is support multiple batteries, but
* status will be reported as cumulative state all of them.
* TODO: add support for other methods, like DeviceKit (is this obsolete??)
*/
class BatteryMonitor : public Fl_Box {
private:
const char *bimg;
char tip[128];
EdbusConnection con;
BatteryList batts;
public:
BatteryMonitor() : Fl_Box(0, 0, 30, 25), bimg(0) {
box(FL_FLAT_BOX);
if(!scan_and_init()) {
set_icon(0, true);
tooltip(_("Failed to query battery status"));
}
}
EdbusConnection &connection() { return con; }
void tooltip_printf(const char *fmt, ...);
int scan_and_init(void);
int update_icon_and_tooltip(void);
void set_icon(double percentage, bool failure = false);
BatteryList &get_batteries(void);
};
static bool bus_property_get(EdbusConnection &con,
const char *service,
const char *path,
const char *iface,
const char *prop,
EdbusMessage *ret)
{
EdbusMessage msg;
msg.create_method_call(service, path, "org.freedesktop.DBus.Properties", "Get");
msg << EdbusData::from_string(iface);
msg << EdbusData::from_string(prop);
E_RETURN_VAL_IF_FAIL(con.send_with_reply_and_block(msg, 1000, *ret), false);
E_RETURN_VAL_IF_FAIL(ret->size() == 1, false);
return true;
}
static bool is_battery(EdbusConnection &con, const char *path) {
EdbusMessage reply;
E_RETURN_VAL_IF_FAIL(bus_property_get(con, UPOWER_SERVICE, path, UPOWER_INTERFACE, "Type", &reply), false);
EdbusMessage::const_iterator it = reply.begin();
E_RETURN_VAL_IF_FAIL(it->is_variant(), false);
EdbusVariant v = it->to_variant();
E_RETURN_VAL_IF_FAIL(v.value.is_uint32(), false);
return v.value.to_uint32() == UPOWER_TYPE_BATTERY;
}
static bool get_percentage(EdbusConnection &con, const char *path, double *ret) {
EdbusMessage reply;
E_RETURN_VAL_IF_FAIL(bus_property_get(con, UPOWER_SERVICE, path, UPOWER_INTERFACE, "Percentage", &reply), false);
EdbusMessage::const_iterator it = reply.begin();
E_RETURN_VAL_IF_FAIL(it->is_variant(), false);
EdbusVariant v = it->to_variant();
E_RETURN_VAL_IF_FAIL(v.value.is_double(), false);
*ret = v.value.to_double();
return true;
}
static bool get_state(EdbusConnection &con, const char *path, int *ret) {
EdbusMessage reply;
E_RETURN_VAL_IF_FAIL(bus_property_get(con, UPOWER_SERVICE, path, UPOWER_INTERFACE, "State", &reply), false);
EdbusMessage::const_iterator it = reply.begin();
E_RETURN_VAL_IF_FAIL(it->is_variant(), false);
EdbusVariant v = it->to_variant();
E_RETURN_VAL_IF_FAIL(v.value.is_uint32(), false);
*ret = v.value.to_uint32();
return true;
}
static const char *get_state_str(EdbusConnection &con, const char *path) {
const char * fallback = _("unknown");
int ret;
E_RETURN_VAL_IF_FAIL(get_state(con, path, &ret), fallback);
switch(ret) {
case UPOWER_STATE_CHARGED: return _("charged");
case UPOWER_STATE_CHARGING: return _("charging");
case UPOWER_STATE_DISCHARGING: return _("discharging");
case UPOWER_STATE_EMPTY: return _("empty");
}
return fallback;
}
static int signal_cb(const EdbusMessage *msg, void *data) {
/* in case some org.freedesktop.DBus (like NameAcquired) received */
if(strcmp(msg->interface(), UPOWER_INTERFACE) != 0)
return 0;
BatteryMonitor *self = (BatteryMonitor*)data;
return self->update_icon_and_tooltip();
}
void BatteryMonitor::tooltip_printf(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
vsnprintf(tip, sizeof(tip), fmt, args);
va_end(args);
tooltip(tip);
}
int BatteryMonitor::scan_and_init(void) {
if(con.connected()) return 1;
E_RETURN_VAL_IF_FAIL(con.connect(EDBUS_SYSTEM), 0);
/* get battery devices */
EdbusMessage msg, reply;
msg.create_method_call(UPOWER_SERVICE, UPOWER_PATH, UPOWER_SERVICE, "EnumerateDevices");
E_RETURN_VAL_IF_FAIL(con.send_with_reply_and_block(msg, 1000, reply), 0);
E_RETURN_VAL_IF_FAIL(reply.size() == 1, 0);
EdbusMessage::const_iterator it = reply.begin();
E_RETURN_VAL_IF_FAIL(it->is_array(), 0);
EdbusList arr = it->to_array();
it = arr.begin();
EdbusMessage::const_iterator ite = arr.end();
for(; it != ite; ++it) {
if(!it->is_object_path()) continue;
EdbusObjectPath p = it->to_object_path();
if(is_battery(con, p.path())) {
/* filters so signal_cb() doesn't get clobbered with uninterested dbus signals */
con.add_signal_match(p.path(), UPOWER_INTERFACE, "Changed");
//con.add_signal_match(p.path(), UPOWER_SERVICE, "DeviceChanged");
batts.push_back(p);
}
}
update_icon_and_tooltip();
/* ready to receive signals */
con.signal_callback(signal_cb, this);
con.setup_listener_with_fltk();
return 1;
}
int BatteryMonitor::update_icon_and_tooltip(void) {
/* in case connection failed somehow */
if(!con.connected()) {
label("0");
return 0;
}
E_RETURN_VAL_IF_FAIL(batts.size() > 0, 0);
double p = 0, ret = 0;
if(batts.size() == 1) {
E_RETURN_VAL_IF_FAIL(get_percentage(con, batts.front().path(), &ret), 0);
p = ret;
tooltip_printf(_("Battery %s: %i%%"), get_state_str(con, batts.front().path()), (int)ret);
} else {
for(BatteryListIt it = batts.begin(), ite = batts.end(); it != ite; ++it) {
if(!get_percentage(con, it->path(), &ret)) continue;
p += ret;
}
p /= batts.size();
tooltip_printf(_("%i batteries: %i%%"), batts.size(), (int)p);
}
set_icon(p);
/* returning state is mainly for signal_cb() */
return 1;
}
#define BATTERY_MIN 10
void BatteryMonitor::set_icon(double percentage, bool failure) {
if(E_UNLIKELY(IconLoader::inited() == false)) {
char buf[8];
snprintf(buf, sizeof(buf), "%i%%", (int)percentage);
copy_label(buf);
return;
}
const char *icon;
if(failure)
icon = BATTERY_NO_CONNECTION;
else
icon = (percentage >= BATTERY_MIN) ? BATTERY_IMG : BATTERY_CAUTION_IMG;
/* small check to prevent image loading when not needed */
if(icon == bimg) return;
IconLoader::set(this, icon, ICON_SIZE_SMALL);
bimg = icon;
}
#else /* EDELIB_HAVE_DBUS */
class BatteryMonitor : public Fl_Box {
public:
BatteryMonitor() : Fl_Box(0, 0, 30, 25) {
tooltip(_("Battery status not available"));
label("bat");
}
};
#endif
EDE_PANEL_APPLET_EXPORT (
BatteryMonitor,
EDE_PANEL_APPLET_OPTION_ALIGN_RIGHT,
"Battery monitor",
"0.1",
"empty",
"Sanel Zukan"
)