blob: cc92fe33a353de69b0ffa22d46bf1bbd8448ec0f [file]
// SPDX-License-Identifier: GPL-2.0-or-later
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright © 2025 Collabora Ltd.
*
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#define _GNU_SOURCE
#include <stdlib.h>
#include <glib.h>
#include "gdbus/gdbus.h"
#include "src/shared/shell.h"
#include "print.h"
#include "telephony.h"
/* String display constants */
#define COLORED_NEW COLOR_GREEN "NEW" COLOR_OFF
#define COLORED_CHG COLOR_YELLOW "CHG" COLOR_OFF
#define COLORED_DEL COLOR_RED "DEL" COLOR_OFF
#define BLUEZ_TELEPHONY_INTERFACE "org.bluez.Telephony1"
#define BLUEZ_TELEPHONY_CALL_INTERFACE "org.bluez.Call1"
static DBusConnection *dbus_conn;
static GDBusProxy *default_ag;
static GList *ags;
static GList *calls;
static GDBusClient *client;
static bool check_default_ag(void)
{
if (!default_ag) {
bt_shell_printf("No default audio gateway available\n");
return FALSE;
}
return TRUE;
}
static char *generic_generator(const char *text, int state, GList *source)
{
static int index;
if (!source)
return NULL;
if (!state)
index = 0;
return g_dbus_proxy_path_lookup(source, &index, text);
}
static char *ag_generator(const char *text, int state)
{
return generic_generator(text, state, ags);
}
static char *call_generator(const char *text, int state)
{
return generic_generator(text, state, calls);
}
static char *proxy_description(GDBusProxy *proxy, const char *title,
const char *description)
{
const char *path;
path = g_dbus_proxy_get_path(proxy);
return g_strdup_printf("%s%s%s%s %s ",
description ? "[" : "",
description ? : "",
description ? "] " : "",
title, path);
}
static void print_ag(void *data, void *user_data)
{
GDBusProxy *proxy = data;
const char *description = user_data;
char *str;
str = proxy_description(proxy, "Telephony", description);
bt_shell_printf("%s%s\n", str,
default_ag == proxy ? "[default]" : "");
g_free(str);
}
static void print_call(void *data, void *user_data)
{
GDBusProxy *proxy = data;
const char *description = user_data;
const char *path, *line_id;
DBusMessageIter iter;
path = g_dbus_proxy_get_path(proxy);
if (g_dbus_proxy_get_property(proxy, "LineIdentification", &iter))
dbus_message_iter_get_basic(&iter, &line_id);
else
line_id = "<unknown>";
bt_shell_printf("%s%s%sCall %s %s\n", description ? "[" : "",
description ? : "",
description ? "] " : "",
path, line_id);
}
static void cmd_list(int argc, char *arg[])
{
g_list_foreach(ags, print_ag, NULL);
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void cmd_show(int argc, char *argv[])
{
GDBusProxy *proxy;
if (argc < 2) {
if (check_default_ag() == FALSE)
return bt_shell_noninteractive_quit(EXIT_FAILURE);
proxy = default_ag;
} else {
proxy = g_dbus_proxy_lookup(ags, NULL, argv[1],
BLUEZ_TELEPHONY_INTERFACE);
if (!proxy) {
bt_shell_printf("Audio gateway %s not available\n",
argv[1]);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
bt_shell_printf("Audio gateway %s\n", g_dbus_proxy_get_path(proxy));
print_property(proxy, "UUID");
print_property(proxy, "SupportedURISchemes");
print_property(proxy, "State");
print_property(proxy, "Service");
print_property(proxy, "Signal");
print_property(proxy, "Roaming");
print_property(proxy, "BattChg");
print_property(proxy, "OperatorName");
print_property(proxy, "InbandRingtone");
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void cmd_select(int argc, char *argv[])
{
GDBusProxy *proxy;
proxy = g_dbus_proxy_lookup(ags, NULL, argv[1],
BLUEZ_TELEPHONY_INTERFACE);
if (proxy == NULL) {
bt_shell_printf("Audio gateway %s not available\n", argv[1]);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
if (default_ag == proxy)
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
default_ag = proxy;
print_ag(proxy, NULL);
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void dial_reply(DBusMessage *message, void *user_data)
{
DBusError error;
dbus_error_init(&error);
if (dbus_set_error_from_message(&error, message) == TRUE) {
bt_shell_printf("Failed to answer: %s\n", error.name);
dbus_error_free(&error);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("Dial successful\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
static void dial_setup(DBusMessageIter *iter, void *user_data)
{
const char *number = user_data;
dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &number);
}
static void cmd_dial(int argc, char *argv[])
{
GDBusProxy *proxy;
if (argc < 3) {
if (check_default_ag() == FALSE)
return bt_shell_noninteractive_quit(EXIT_FAILURE);
proxy = default_ag;
} else {
proxy = g_dbus_proxy_lookup(ags, NULL, argv[2],
BLUEZ_TELEPHONY_INTERFACE);
if (!proxy) {
bt_shell_printf("Audio gateway %s not available\n",
argv[1]);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
if (g_dbus_proxy_method_call(proxy, "Dial", dial_setup,
dial_reply, argv[1], NULL) == FALSE) {
bt_shell_printf("Failed to dial\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("Attempting to dial\n");
}
static void hangupall_reply(DBusMessage *message, void *user_data)
{
DBusError error;
dbus_error_init(&error);
if (dbus_set_error_from_message(&error, message) == TRUE) {
bt_shell_printf("Failed to answer: %s\n", error.name);
dbus_error_free(&error);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("Hangup all successful\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
static void cmd_hangupall(int argc, char *argv[])
{
GDBusProxy *proxy;
if (argc < 2) {
if (check_default_ag() == FALSE)
return bt_shell_noninteractive_quit(EXIT_FAILURE);
proxy = default_ag;
} else {
proxy = g_dbus_proxy_lookup(ags, NULL, argv[1],
BLUEZ_TELEPHONY_INTERFACE);
if (!proxy) {
bt_shell_printf("Audio gateway %s not available\n",
argv[1]);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
if (g_dbus_proxy_method_call(proxy, "HangupAll", NULL,
hangupall_reply, NULL, NULL) == FALSE) {
bt_shell_printf("Failed to hangup all calls\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("Attempting to hangup all calls\n");
}
static void cmd_list_calls(int argc, char *arg[])
{
g_list_foreach(calls, print_call, NULL);
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void cmd_show_call(int argc, char *argv[])
{
GDBusProxy *proxy;
if (argc < 2)
return bt_shell_noninteractive_quit(EXIT_FAILURE);
proxy = g_dbus_proxy_lookup(calls, NULL, argv[1],
BLUEZ_TELEPHONY_CALL_INTERFACE);
if (!proxy) {
bt_shell_printf("Call %s not available\n", argv[1]);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("Call %s\n", g_dbus_proxy_get_path(proxy));
print_property(proxy, "LineIdentification");
print_property(proxy, "IncomingLine");
print_property(proxy, "Name");
print_property(proxy, "Multiparty");
print_property(proxy, "State");
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void answer_reply(DBusMessage *message, void *user_data)
{
DBusError error;
dbus_error_init(&error);
if (dbus_set_error_from_message(&error, message) == TRUE) {
bt_shell_printf("Failed to answer: %s\n", error.name);
dbus_error_free(&error);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("Answer successful\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
static void cmd_answer_call(int argc, char *argv[])
{
GDBusProxy *proxy;
if (argc < 2)
return bt_shell_noninteractive_quit(EXIT_FAILURE);
proxy = g_dbus_proxy_lookup(calls, NULL, argv[1],
BLUEZ_TELEPHONY_CALL_INTERFACE);
if (!proxy) {
bt_shell_printf("Call %s not available\n", argv[1]);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
if (g_dbus_proxy_method_call(proxy, "Answer", NULL,
answer_reply, NULL, NULL) == FALSE) {
bt_shell_printf("Failed to answer call\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("Attempting to answer\n");
}
static void hangup_reply(DBusMessage *message, void *user_data)
{
DBusError error;
dbus_error_init(&error);
if (dbus_set_error_from_message(&error, message) == TRUE) {
bt_shell_printf("Failed to answer: %s\n", error.name);
dbus_error_free(&error);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("Hangup successful\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
static void cmd_hangup_call(int argc, char *argv[])
{
GDBusProxy *proxy;
if (argc < 2)
return bt_shell_noninteractive_quit(EXIT_FAILURE);
proxy = g_dbus_proxy_lookup(calls, NULL, argv[1],
BLUEZ_TELEPHONY_CALL_INTERFACE);
if (!proxy) {
bt_shell_printf("Call %s not available\n", argv[1]);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
if (g_dbus_proxy_method_call(proxy, "Hangup", NULL,
hangup_reply, NULL, NULL) == FALSE) {
bt_shell_printf("Failed to hangup call\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("Attempting to hangup\n");
}
static void ag_added(GDBusProxy *proxy)
{
ags = g_list_append(ags, proxy);
if (default_ag == NULL)
default_ag = proxy;
print_ag(proxy, COLORED_NEW);
}
static void call_added(GDBusProxy *proxy)
{
calls = g_list_append(calls, proxy);
print_call(proxy, COLORED_NEW);
}
static void proxy_added(GDBusProxy *proxy, void *user_data)
{
const char *interface;
interface = g_dbus_proxy_get_interface(proxy);
if (!strcmp(interface, BLUEZ_TELEPHONY_INTERFACE))
ag_added(proxy);
else if (!strcmp(interface, BLUEZ_TELEPHONY_CALL_INTERFACE))
call_added(proxy);
}
static void ag_removed(GDBusProxy *proxy)
{
print_ag(proxy, COLORED_DEL);
if (default_ag == proxy)
default_ag = NULL;
ags = g_list_remove(ags, proxy);
}
static void call_removed(GDBusProxy *proxy)
{
calls = g_list_remove(calls, proxy);
print_call(proxy, COLORED_DEL);
}
static void proxy_removed(GDBusProxy *proxy, void *user_data)
{
const char *interface;
interface = g_dbus_proxy_get_interface(proxy);
if (!strcmp(interface, BLUEZ_TELEPHONY_INTERFACE))
ag_removed(proxy);
else if (!strcmp(interface, BLUEZ_TELEPHONY_CALL_INTERFACE))
call_removed(proxy);
}
static void ag_property_changed(GDBusProxy *proxy, const char *name,
DBusMessageIter *iter)
{
char *str;
str = proxy_description(proxy, "Telephony", COLORED_CHG);
print_iter(str, name, iter);
g_free(str);
}
static void call_property_changed(GDBusProxy *proxy, const char *name,
DBusMessageIter *iter)
{
char *str;
str = proxy_description(proxy, "Call", COLORED_CHG);
print_iter(str, name, iter);
g_free(str);
}
static void property_changed(GDBusProxy *proxy, const char *name,
DBusMessageIter *iter, void *user_data)
{
const char *interface;
interface = g_dbus_proxy_get_interface(proxy);
if (!strcmp(interface, BLUEZ_TELEPHONY_INTERFACE))
ag_property_changed(proxy, name, iter);
else if (!strcmp(interface, BLUEZ_TELEPHONY_CALL_INTERFACE))
call_property_changed(proxy, name, iter);
}
static void telephony_menu_pre_run(const struct bt_shell_menu *menu)
{
dbus_conn = bt_shell_get_env("DBUS_CONNECTION");
if (!dbus_conn || client)
return;
client = g_dbus_client_new(dbus_conn, "org.bluez", "/org/bluez");
g_dbus_client_set_proxy_handlers(client, proxy_added, proxy_removed,
property_changed, NULL);
}
static const struct bt_shell_menu telephony_menu = {
.name = "telephony",
.desc = "Telephony Submenu",
.pre_run = telephony_menu_pre_run,
.entries = {
{ "list", NULL, cmd_list, "List available audio gateway" },
{ "show", "[telephony]", cmd_show, "Audio gateway information",
ag_generator},
{ "select", "<telephony>", cmd_select,
"Select default audio gateway",
ag_generator},
{ "dial", "<number> [telephony]", cmd_dial, "Dial number",
ag_generator},
{ "hangup-all", "[telephony]", cmd_hangupall, "Hangup all calls",
ag_generator},
{ "list-calls", NULL, cmd_list_calls, "List calls" },
{ "show-call", "<call>", cmd_show_call, "Show call information",
call_generator},
{ "answer", "<call>", cmd_answer_call, "Answer call",
call_generator},
{ "hangup", "<call>", cmd_hangup_call, "Hangup call",
call_generator},
{} },
};
void telephony_add_submenu(void)
{
bt_shell_add_submenu(&telephony_menu);
}
void telephony_remove_submenu(void)
{
g_dbus_client_unref(client);
}