/**
* @file bluetooth.c
* Bluetooth module -- this implements bluez tracking for MCE
*
* Copyright (C) 2014-2019 Jolla Ltd.
*
* @author Simo Piiroinen
*
* mce is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* mce 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mce. If not, see .
*/
#include "../mce.h"
#include "../mce-log.h"
#include "../mce-dbus.h"
#include "../libwakelock.h"
#include
#include
#include
/* Unlike the other standard dbus interfaces, the object manager seems
* not to be defined in dbus-shared.h header file ... */
#ifndef DBUS_INTERFACE_OBJECT_MANAGER
# define DBUS_INTERFACE_OBJECT_MANAGER "org.freedesktop.DBus.ObjectManager"
#endif
/* ------------------------------------------------------------------------- *
* SUSPEND_BLOCK
* ------------------------------------------------------------------------- */
static gboolean bluetooth_suspend_block_timer_cb(gpointer aptr);
static void bluetooth_suspend_block_stop(void);
static void bluetooth_suspend_block_start(void);
/* ------------------------------------------------------------------------- *
* DBUS_HANDLERS
* ------------------------------------------------------------------------- */
static gboolean bluetooth_dbus_bluez4_signal_cb(DBusMessage *const msg);
static gboolean bluetooth_dbus_bluez5_signal_cb(DBusMessage *const msg);
static void bluetooth_dbus_init(void);
static void bluetooth_dbus_quit(void);
/* ------------------------------------------------------------------------- *
* MODULE_LOAD_UNLOAD
* ------------------------------------------------------------------------- */
G_MODULE_EXPORT const gchar *g_module_check_init(GModule *module);
G_MODULE_EXPORT void g_module_unload(GModule *module);
/* ========================================================================= *
* SUSPEND_BLOCK
* ========================================================================= */
/** How long bluetooth dbus activity is allowed to delay suspend */
#define BLUETOOTH_SUSPEND_BLOCK_MS 2000
/** Timer id for cancelling suspend blocking */
static guint bluetooth_suspend_block_timer_id = 0;
/** Timer callback for cancelling suspend blocking
*
* @param aptr user data pointer (not used)
*
* @return FALSE to stop timer from repeating
*/
static gboolean bluetooth_suspend_block_timer_cb(gpointer aptr)
{
(void)aptr; // not used
if( bluetooth_suspend_block_timer_id ) {
bluetooth_suspend_block_timer_id = 0;
mce_log(LL_DEVEL, "bt suspend blocking ended");
wakelock_unlock("mce_bluez_wait");
}
return FALSE;
}
/** Cancel suspend blocking
*/
static void bluetooth_suspend_block_stop(void)
{
if( bluetooth_suspend_block_timer_id ) {
g_source_remove(bluetooth_suspend_block_timer_id),
bluetooth_suspend_block_timer_id = 0;
mce_log(LL_DEVEL, "bt suspend blocking cancelled");
wakelock_unlock("mce_bluez_wait");
}
}
/** Start/extend suspend blocking
*/
static void bluetooth_suspend_block_start(void)
{
if( bluetooth_suspend_block_timer_id ) {
g_source_remove(bluetooth_suspend_block_timer_id);
}
else {
wakelock_lock("mce_bluez_wait", -1);
mce_log(LL_DEVEL, "bt suspend blocking started");
}
bluetooth_suspend_block_timer_id =
g_timeout_add(BLUETOOTH_SUSPEND_BLOCK_MS,
bluetooth_suspend_block_timer_cb, 0);
}
/* ========================================================================= *
* DBUS_HANDLERS
* ========================================================================= */
/** Handle signal originating from bluez4
*
* MCE is not interested in the signal content per se, any incoming
* signals just mean there is bluetooth activity and mce should allow
* related ipc and processing to happen without the device getting
* suspened too soon.
*
* @param msg dbus signal message
*
* @return TRUE
*/
static gboolean
bluetooth_dbus_bluez4_signal_cb(DBusMessage *const msg)
{
if( mce_log_p(LL_DEBUG) ) {
char *repr = mce_dbus_message_repr(msg);
mce_log(LL_DEBUG, "%s", repr ?: "bluez sig");
free(repr);
}
bluetooth_suspend_block_start();
return TRUE;
}
/** Handle signal originating from bluez5
*
* MCE is not interested in the signal content per se, any incoming
* signals just mean there is bluetooth activity and mce should allow
* related ipc and processing to happen without the device getting
* suspened too soon.
*
* @param msg dbus signal message
*
* @return TRUE
*/
static gboolean
bluetooth_dbus_bluez5_signal_cb(DBusMessage *const msg)
{
static const char name[] = "org.bluez";
/* Note: The signal match rule can and should use the
* well-known name, but the actual signals that
* we receive are going to have the private name
* as sender.
*/
/* Get name owner from tracking cache. Assume that
* no bluez signals are sent before the well known
* name is claimed or after it is released. */
const char *owner = mce_dbus_nameowner_get(name);
if( !owner )
goto EXIT;
/* Check if the signal sender matches the supposed
* owner (or the well-known-name, just in case) */
const char *sender = dbus_message_get_sender(msg);
if( !sender )
goto EXIT;
if( strcmp(sender, owner) && strcmp(sender, name) )
goto EXIT;
if( mce_log_p(LL_DEBUG) ) {
char *repr = mce_dbus_message_repr(msg);
mce_log(LL_DEBUG, "%s", repr ?: "bluez sig");
free(repr);
}
bluetooth_suspend_block_start();
EXIT:
return TRUE;
}
/** Array of dbus message handlers */
static mce_dbus_handler_t bluetooth_dbus_handlers[] =
{
/* bluez4 signals */
{
.interface = "org.bluez.Manager",
.type = DBUS_MESSAGE_TYPE_SIGNAL,
.callback = bluetooth_dbus_bluez4_signal_cb,
},
{
.interface = "org.bluez.Adapter",
.type = DBUS_MESSAGE_TYPE_SIGNAL,
.callback = bluetooth_dbus_bluez4_signal_cb,
},
{
.interface = "org.bluez.Device",
.type = DBUS_MESSAGE_TYPE_SIGNAL,
.callback = bluetooth_dbus_bluez4_signal_cb,
},
{
.interface = "org.bluez.Input",
.type = DBUS_MESSAGE_TYPE_SIGNAL,
.callback = bluetooth_dbus_bluez4_signal_cb,
},
{
.interface = "org.bluez.Audio",
.type = DBUS_MESSAGE_TYPE_SIGNAL,
.callback = bluetooth_dbus_bluez4_signal_cb,
},
{
.interface = "org.bluez.SerialProxyManager",
.type = DBUS_MESSAGE_TYPE_SIGNAL,
.callback = bluetooth_dbus_bluez4_signal_cb,
},
/* bluez5 signals */
{
.sender = "org.bluez",
.interface = DBUS_INTERFACE_OBJECT_MANAGER,
.type = DBUS_MESSAGE_TYPE_SIGNAL,
.name = "InterfacesAdded",
.callback = bluetooth_dbus_bluez5_signal_cb,
},
{
.sender = "org.bluez",
.interface = DBUS_INTERFACE_OBJECT_MANAGER,
.type = DBUS_MESSAGE_TYPE_SIGNAL,
.name = "InterfacesRemoved",
.callback = bluetooth_dbus_bluez5_signal_cb,
},
{
.sender = "org.bluez",
.interface = DBUS_INTERFACE_PROPERTIES,
.type = DBUS_MESSAGE_TYPE_SIGNAL,
.name = "PropertiesChanged",
.callback = bluetooth_dbus_bluez5_signal_cb,
},
/* sentinel */
{
.interface = 0
}
};
/** Add dbus handlers
*/
static void bluetooth_dbus_init(void)
{
mce_dbus_handler_register_array(bluetooth_dbus_handlers);
}
/** Remove dbus handlers
*/
static void bluetooth_dbus_quit(void)
{
mce_dbus_handler_unregister_array(bluetooth_dbus_handlers);
}
/* ========================================================================= *
* DATAPIPE_TRACKING
* ========================================================================= */
/** Availability of bluez; from bluez_service_state_pipe */
static service_state_t bluez_service_state = SERVICE_STATE_UNDEF;
/** Datapipe trigger for bluez availability
*
* @param data bluez D-Bus service availability (as a void pointer)
*/
static void bluetooth_datapipe_bluez_service_state_cb(gconstpointer const data)
{
service_state_t prev = bluez_service_state;
bluez_service_state = GPOINTER_TO_INT(data);
if( bluez_service_state == prev )
goto EXIT;
mce_log(LL_DEVEL, "bluez dbus service: %s -> %s",
service_state_repr(prev),
service_state_repr(bluez_service_state));
switch( bluez_service_state ) {
case SERVICE_STATE_RUNNING:
case SERVICE_STATE_STOPPED:
bluetooth_suspend_block_start();
break;
default:
break;
}
EXIT:
return;
}
/** Array of datapipe handlers */
static datapipe_handler_t bluetooth_datapipe_handlers[] =
{
// output triggers
{
.datapipe = &bluez_service_state_pipe,
.output_cb = bluetooth_datapipe_bluez_service_state_cb,
},
// sentinel
{
.datapipe = 0,
}
};
static datapipe_bindings_t bluetooth_datapipe_bindings =
{
.module = "bluetooth",
.handlers = bluetooth_datapipe_handlers,
};
/** Append triggers/filters to datapipes
*/
static void bluetooth_datapipe_init(void)
{
mce_datapipe_init_bindings(&bluetooth_datapipe_bindings);
}
/** Remove triggers/filters from datapipes
*/
static void bluetooth_datapipe_quit(void)
{
mce_datapipe_quit_bindings(&bluetooth_datapipe_bindings);
}
/* ========================================================================= *
* MODULE_LOAD_UNLOAD
* ========================================================================= */
/** Init function for the bluetooth module
*
* @param module (not used)
*
* @return NULL on success, a string with an error message on failure
*/
const gchar *g_module_check_init(GModule *module)
{
(void)module;
bluetooth_datapipe_init();
bluetooth_dbus_init();
return 0;
}
/** Exit function for the bluetooth module
*
* @param module (not used)
*/
void g_module_unload(GModule *module)
{
(void)module;
bluetooth_datapipe_quit();
bluetooth_dbus_quit();
bluetooth_suspend_block_stop();
}