ede/ede-panel/applets/battery-monitor/BatteryMonitor.cpp
Sanel Zukan cbcb0e2ce4 Show status when battery query fails.
When there is an issue with getting battery details, panel would not
show any icon nor tooltip to mark that status. This changes adds failing
icon and appropriate tooltip message.
2016-07-13 16:07:02 +02:00

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"
)