/*
* Copyright 2014 Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3, as published
* by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranties of
* MERCHANTABILITY, SATISFACTORY QUALITY, 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, see .
*
* Authors:
* Charles Kerr
*/
#include
#include
#include // BUS_POWERD_NAME
#include
#include // std::shared_ptr
namespace ayatana {
namespace indicator {
namespace datetime {
/***
****
***/
class PowerdWakeupTimer::Impl
{
public:
Impl(const std::shared_ptr& clock):
m_clock(clock),
m_cancellable(g_cancellable_new())
{
g_bus_get(G_BUS_TYPE_SYSTEM, m_cancellable, on_bus_ready, this);
}
~Impl()
{
clear_current_cookie();
g_cancellable_cancel(m_cancellable);
g_clear_object(&m_cancellable);
if (m_sub_id)
g_dbus_connection_signal_unsubscribe(m_bus.get(), m_sub_id);
if (m_watch_tag)
g_bus_unwatch_name(m_watch_tag);
}
void set_wakeup_time(const DateTime& d)
{
m_wakeup_time = d;
update_cookie();
}
core::Signal<>& timeout() { return m_timeout; }
private:
void emit_timeout() { return m_timeout(); }
static void on_bus_ready(GObject * /*unused*/,
GAsyncResult * res,
gpointer gself)
{
GError * error;
GDBusConnection * bus;
error = nullptr;
bus = g_bus_get_finish(res, &error);
if (bus == nullptr)
{
if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
g_warning("%s Couldn't get system bus: %s", G_STRLOC, error->message);
}
else
{
static_cast(gself)->init_bus(bus);
}
g_clear_object(&bus);
g_clear_error(&error);
}
void init_bus(GDBusConnection* connection)
{
m_bus.reset(G_DBUS_CONNECTION(g_object_ref(G_OBJECT(connection))),
[](GDBusConnection *c){g_object_unref(G_OBJECT(c));});
m_sub_id = g_dbus_connection_signal_subscribe(m_bus.get(),
BUS_POWERD_NAME,
BUS_POWERD_INTERFACE,
"Wakeup",
BUS_POWERD_PATH,
nullptr,
G_DBUS_SIGNAL_FLAGS_NONE,
on_wakeup_signal,
this, // userdata
nullptr); // userdata free
m_watch_tag = g_bus_watch_name_on_connection(m_bus.get(),
BUS_POWERD_NAME,
G_BUS_NAME_WATCHER_FLAGS_NONE,
on_name_appeared_static,
nullptr, // name-vanished,
this, // userdata
nullptr); // userdata free
}
static void
on_wakeup_signal(GDBusConnection * /*connection*/,
const gchar * sender_name,
const gchar * /*object_path*/,
const gchar * /*interface_name*/,
const gchar * /*signal_name*/,
GVariant * /*parameters*/,
gpointer gself)
{
g_debug("%s %s broadcast a hw wakeup signal", G_STRLOC, sender_name);
static_cast(gself)->emit_timeout();
}
static void
on_name_appeared_static(GDBusConnection * /*connection*/,
const gchar * name,
const gchar * name_owner,
gpointer gself)
{
g_debug("%s %s owns %s now; let's ask for a new cookie", G_STRLOC, name, name_owner);
static_cast(gself)->update_cookie();
}
/***
**** requestWakeup
***/
void update_cookie()
{
if (!m_bus)
return;
// if we've already got a cookie, clear it
clear_current_cookie();
g_warn_if_fail(m_cookie.empty());
// get a new cookie, if necessary
if (m_wakeup_time.is_set())
{
g_debug("%s calling %s::requestWakeup(%s)",
G_STRLOC, BUS_POWERD_NAME,
m_wakeup_time.format("%F %T").c_str());
auto args = g_variant_new("(st)",
GETTEXT_PACKAGE,
uint64_t(m_wakeup_time.to_unix()));
g_dbus_connection_call(m_bus.get(),
BUS_POWERD_NAME,
BUS_POWERD_PATH,
BUS_POWERD_INTERFACE,
"requestWakeup", // method_name
args,
G_VARIANT_TYPE("(s)"), // reply_type
G_DBUS_CALL_FLAGS_NONE,
-1, // use default timeout
m_cancellable,
on_request_wakeup_done,
this);
}
}
static void on_request_wakeup_done(GObject * o,
GAsyncResult * res,
gpointer gself)
{
GError * error;
GVariant * ret;
error = nullptr;
ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(o), res, &error);
if (ret == nullptr)
{
/* powerd isn't on the desktop, but we don't need hardware wakeups there
anyway... so no need to warn on SERVICE_UNKNOWN */
if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
!g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN))
{
g_warning("%s Could not set hardware wakeup: %s", G_STRLOC, error->message);
}
}
else
{
const char* s = nullptr;
g_variant_get(ret, "(&s)", &s);
g_debug("%s %s::requestWakeup() sent cookie %s",
G_STRLOC, BUS_POWERD_NAME, s);
auto& cookie = static_cast(gself)->m_cookie;
if (s != nullptr)
cookie = s;
else
cookie.clear();
}
// cleanup
g_clear_pointer(&ret, g_variant_unref);
g_clear_error(&error);
}
/***
**** clearWakeup
***/
void clear_current_cookie()
{
if (!m_cookie.empty())
{
g_debug("%s calling %s::clearWakeup(%s)",
G_STRLOC, BUS_POWERD_NAME, m_cookie.c_str());
g_dbus_connection_call(m_bus.get(),
BUS_POWERD_NAME,
BUS_POWERD_PATH,
BUS_POWERD_INTERFACE,
"clearWakeup", // method_name
g_variant_new("(s)", m_cookie.c_str()),
nullptr, // no response type
G_DBUS_CALL_FLAGS_NONE,
-1, // use default timeout
nullptr, // cancellable
on_clear_wakeup_done,
nullptr);
m_cookie.clear();
}
}
// this is only here to log errors
static void on_clear_wakeup_done(GObject * o,
GAsyncResult * res,
gpointer /*unused*/)
{
GError * error;
GVariant * ret;
error = nullptr;
ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(o), res, &error);
if (!ret && !g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
g_warning("%s Couldn't clear hardware wakeup: %s", G_STRLOC, error->message);
// cleanup
g_clear_pointer(&ret, g_variant_unref);
g_clear_error(&error);
}
/***
****
***/
core::Signal<> m_timeout;
const std::shared_ptr& m_clock;
DateTime m_wakeup_time;
std::shared_ptr m_bus;
GCancellable * m_cancellable = nullptr;
std::string m_cookie;
guint m_watch_tag = 0;
guint m_sub_id = 0;
};
/***
****
***/
PowerdWakeupTimer::PowerdWakeupTimer(const std::shared_ptr& clock):
p(new Impl(clock))
{
}
PowerdWakeupTimer::~PowerdWakeupTimer()
{
}
void PowerdWakeupTimer::set_wakeup_time(const DateTime& d)
{
p->set_wakeup_time(d);
}
core::Signal<>& PowerdWakeupTimer::timeout()
{
return p->timeout();
}
/***
****
***/
} // namespace datetime
} // namespace indicator
} // namespace ayatana