* [PATCH BlueZ v2 1/5] doc: Add new telephony related profiles interfaces
@ 2025-12-11 18:34 Frédéric Danis
2025-12-11 18:34 ` [PATCH BlueZ v2 2/5] audio/telephony: Add shared interfaces implementation Frédéric Danis
` (5 more replies)
0 siblings, 6 replies; 7+ messages in thread
From: Frédéric Danis @ 2025-12-11 18:34 UTC (permalink / raw)
To: linux-bluetooth
These are interfaces are meant to be generic to the telephony related
"headset" profiles like HSP HS, HFP HF, and CCP.
---
Makefile.am | 4 +
doc/org.bluez.Call.rst | 140 ++++++++++++++++++++++
doc/org.bluez.Telephony.rst | 225 ++++++++++++++++++++++++++++++++++++
3 files changed, 369 insertions(+)
create mode 100644 doc/org.bluez.Call.rst
create mode 100644 doc/org.bluez.Telephony.rst
diff --git a/Makefile.am b/Makefile.am
index e152ae648..060a90cfc 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -397,6 +397,7 @@ man_MANS += doc/org.bluez.obex.Client.5 doc/org.bluez.obex.Session.5 \
doc/org.bluez.obex.Message.5 \
doc/org.bluez.obex.AgentManager.5 doc/org.bluez.obex.Agent.5 \
doc/org.bluez.obex.Image.5
+man_MANS += doc/org.bluez.Telephony.5 doc/org.bluez.Call.5
endif
manual_pages += src/bluetoothd.8
manual_pages += doc/hci.7 doc/mgmt.7 doc/l2cap.7 doc/rfcomm.7 doc/sco.7 \
@@ -433,6 +434,7 @@ manual_pages += doc/org.bluez.obex.Client.5 doc/org.bluez.obex.Session.5 \
doc/org.bluez.obex.Message.5 \
doc/org.bluez.obex.AgentManager.5 doc/org.bluez.obex.Agent.5 \
doc/org.bluez.obex.Image.5
+manual_pages += doc/org.bluez.Telephony.5 doc/org.bluez.Call.5
EXTRA_DIST += src/genbuiltin src/bluetooth.conf \
src/main.conf profiles/network/network.conf \
@@ -517,6 +519,8 @@ EXTRA_DIST += doc/org.bluez.obex.Client.rst doc/org.bluez.obex.Session.rst \
doc/org.bluez.obex.AgentManager.rst doc/org.bluez.obex.Agent.rst \
doc/org.bluez.obex.Image.rst
+EXTRA_DIST += doc/org.bluez.Telephony.rst doc/org.bluez.Call.rst
+
EXTRA_DIST += doc/pics-opp.txt doc/pixit-opp.txt \
doc/pts-opp.txt
diff --git a/doc/org.bluez.Call.rst b/doc/org.bluez.Call.rst
new file mode 100644
index 000000000..8825e0f17
--- /dev/null
+++ b/doc/org.bluez.Call.rst
@@ -0,0 +1,140 @@
+===============
+org.bluez.Call1
+===============
+
+--------------------------------------------
+BlueZ D-Bus Telephony Call API documentation
+--------------------------------------------
+
+:Version: BlueZ
+:Date: May 2025
+:Manual section: 5
+:Manual group: Linux System Administration
+
+Interface
+=========
+
+:Service: org.bluez
+:Interface: org.bluez.Call1 [experimental]
+:Object path: [variable prefix]/{hci0,hci1,...}/dev_{BDADDR}/telephony#/call#
+
+Methods
+-------
+
+void Answer()
+`````````````
+
+Answers an incoming call. Only valid if the state of the call is "incoming".
+
+Possible Errors:
+:org.bluez.Error.InvalidState
+:org.bluez.Error.Failed
+
+void Hangup()
+`````````````
+
+Hangs up the call.
+
+For an incoming call, the call is hung up using ATH or equivalent. For a
+waiting call, the remote party is notified by using the User Determined User
+Busy (UDUB) condition. This is generally implemented using CHLD=0.
+
+Please note that the GSM specification does not allow the release of a held
+call when a waiting call exists. This is because 27.007 allows CHLD=1X to
+operate only on active calls. Hence a held call cannot be hung up without
+affecting the state of the incoming call (e.g. using other CHLD alternatives).
+Most manufacturers provide vendor extensions that do allow the state of the
+held call to be modified using CHLD=1X or equivalent. It should be noted that
+Bluetooth HFP specifies the classic 27.007 behavior and does not allow CHLD=1X
+to modify the state of held calls.
+
+Based on the discussion above, it should also be noted that releasing a
+particular party of a held multiparty call might not be possible on some
+implementations. It is recommended for the applications to structure their UI
+accordingly.
+
+NOTE: Releasing active calls does not produce side-effects. That is the state
+of held or waiting calls is not affected. As an exception, in the case where a
+single active call and a waiting call are present, releasing the active call
+will result in the waiting call transitioning to the 'incoming' state.
+
+Possible Errors:
+:org.bluez.Error.InvalidState
+:org.bluez.Error.Failed
+
+Properties
+----------
+
+string LineIdentification [readonly]
+````````````````````````````````````
+
+Contains the Line Identification information returned by the network, if
+present. For incoming calls this is effectively the CLIP. For outgoing calls
+this attribute will hold the dialed number, or the COLP if received by the
+audio gateway.
+
+Please note that COLP may be different from the dialed number. A special
+"withheld" value means the remote party refused to provide caller ID and the
+"override category" option was not provisioned for the current subscriber.
+
+string IncomingLine [readonly, optional]
+````````````````````````````````````````
+
+Contains the Called Line Identification information returned by the network.
+This is only available for incoming calls and indicates the local subscriber
+number which was dialed by the remote party. This is useful for subscribers
+which have a multiple line service with their network provider and would like
+to know what line the call is coming in on.
+
+string Name [readonly]
+``````````````````````
+
+Contains the Name Identification information returned by the network, if
+present.
+
+boolean Multiparty [readonly]
+`````````````````````````````
+
+Contains the indication if the call is part of a multiparty call or not.
+
+Notifications if a call becomes part or leaves a multiparty call are sent.
+
+string State [readonly]
+```````````````````````
+
+Contains the state of the current call.
+
+Possible values:
+
+:"active":
+
+ The call is active
+
+:"held":
+
+ The call is on hold
+
+:"dialing":
+
+ The call is being dialed
+
+:"alerting":
+
+ The remote party is being alerted
+
+:"incoming":
+
+ Incoming call in progress
+
+:"waiting":
+
+ Call is waiting
+
+:"response_and_hold":
+
+ Incoming call has been set on hold
+
+:"disconnected":
+
+ No further use of this object is allowed, it will be
+ destroyed shortly
diff --git a/doc/org.bluez.Telephony.rst b/doc/org.bluez.Telephony.rst
new file mode 100644
index 000000000..a722e2a38
--- /dev/null
+++ b/doc/org.bluez.Telephony.rst
@@ -0,0 +1,225 @@
+====================
+org.bluez.Telephony1
+====================
+
+-----------------------------------------------------
+BlueZ D-Bus Telephony Audio Gateway API documentation
+-----------------------------------------------------
+
+:Version: BlueZ
+:Date: May 2025
+:Manual section: 5
+:Manual group: Linux System Administration
+
+Interface
+=========
+
+:Service: org.bluez
+:Interface: org.bluez.Telephony1 [experimental]
+:Object path: [variable prefix]/{hci0,hci1,...}/dev_{BDADDR}/telephony#
+
+Methods
+-------
+
+object Dial(string uri)
+``````````````````````````
+
+The uri is comprised of the URI scheme followed by the Caller ID (this could
+be a telephone number or username), separated by a colon.
+
+Examples of common URI schemes can be found in Internet Assigned Numbers
+Authority (IANA) URI Schemes:
+https://iana.org/assignments/uri-schemes/uri-schemes.xhtml
+
+This initiates a new outgoing call. Returns the object path to the newly
+created call.
+
+For HFP the URI is "tel:" followed by the telephone number.
+
+The telephone number must be a string containing the following characters:
+`[0-9+*#,ABCD]{1,80}` The character set can contain numbers, `+`, `*`, `#`,
+`,` and the letters `A` to `D`. Besides this sanity checking no further number
+validation is performed. It is assumed that the gateway and/or the network
+will perform further validation.
+
+If telephone number is an empty string, it will try to call last dialed number.
+
+NOTE: If an active call (single or multiparty) exists, then it is
+automatically put on hold if the dial procedure is successful.
+
+Possible Errors:
+
+:org.bluez.Error.InvalidState:
+:org.bluez.Error.InvalidArguments:
+:org.bluez.Error.NotSupported:
+:org.bluez.Error.Failed:
+
+void SwapCalls()
+````````````````
+
+Swaps Active and Held calls. The effect of this is that all calls (0 or more
+including calls in a multi-party conversation) that were Active are now Held,
+and all calls (0 or more) that were Held are now Active.
+
+GSM specification does not allow calls to be swapped in the case where Held,
+Active and Waiting calls exist. Some modems implement this anyway, thus it is
+manufacturer specific whether this method will succeed in the case of Held,
+Active and Waiting calls.
+
+Possible Errors:
+:org.bluez.Error.InvalidState
+:org.bluez.Error.Failed
+
+void ReleaseAndAnswer()
+```````````````````````
+
+Releases currently active call (0 or more) and answers the currently waiting
+call. Please note that if the current call is a multiparty call, then all
+parties in the multi-party call will be released.
+
+Possible Errors:
+:org.bluez.Error.InvalidState
+:org.bluez.Error.Failed
+
+void ReleaseAndSwap()
+`````````````````````
+
+Releases currently active call (0 or more) and activates any currently held
+calls. Please note that if the current call is a multiparty call, then all
+parties in the multi-party call will be released.
+
+Possible Errors:
+:org.bluez.Error.InvalidState
+:org.bluez.Error.Failed
+
+void HoldAndAnswer()
+````````````````````
+
+Puts the current call (including multi-party calls) on hold and answers the
+currently waiting call. Calling this function when a user already has a both
+Active and Held calls is invalid, since in GSM a user can have only a single
+Held call at a time.
+
+Possible Errors:
+:org.bluez.Error.InvalidState
+:org.bluez.Error.Failed
+
+void HangupAll()
+````````````````
+
+Releases all calls except waiting calls. This includes multiparty calls.
+
+Possible Errors:
+:org.bluez.Error.InvalidState
+:org.bluez.Error.Failed
+
+void HangupActive()
+```````````````````
+
+Releases active calls. This includes multiparty active calls.
+
+Possible Errors:
+:org.bluez.Error.InvalidState
+:org.bluez.Error.Failed
+
+void HangupHeld()
+`````````````````
+
+Releases held calls except waiting calls. This includes multiparty held calls.
+
+Possible Errors:
+:org.bluez.Error.InvalidState
+:org.bluez.Error.Failed
+
+array{object} CreateMultiparty()
+````````````````````````````````
+
+Joins active and held calls together into a multi-party call. If one of the
+calls is already a multi-party call, then the other call is added to the
+multiparty conversation. Returns the new list of calls participating in the
+multiparty call.
+
+There can only be one subscriber controlled multi-party call according to the
+GSM specification.
+
+Possible Errors:
+:org.bluez.Error.InvalidState
+:org.bluez.Error.Failed
+
+void SendTones(string tones)
+````````````````````````````
+
+Sends the DTMF tones to the network. The tones have a fixed duration.
+Tones can be one of: '0' - '9', '*', '#', 'A', 'B', 'C', 'D'. The last four
+are typically not used in normal circumstances.
+
+Possible Errors:
+:org.bluez.Error.InvalidState
+:org.bluez.Error.InvalidArgs
+:org.bluez.Error.Failed
+
+Properties
+----------
+
+string UUID [readonly]
+``````````````````````
+
+UUID of the profile which the Telephony Audio Gateway is for.
+
+array{string} SupportedURISchemes [readonly]
+````````````````````````````````````````````
+
+Contains the list of supported URI schemes.
+
+string State [readonly]
+```````````````````````
+
+Contains the state of the current connection.
+
+Possible values:
+
+:"connecting":
+
+ RFComm connection in progress
+
+:"slc_connecting":
+
+ Service Level Connection in progress
+
+:"connected":
+
+ RFComm and Service Level Connection are connected
+
+:"disconnecting":
+
+ No further use of this object is allowed, it will be destroyed shortly
+
+boolean Service [readonly]
+``````````````````````````
+
+Network service availability.
+
+byte Signal [readonly]
+``````````````````````
+
+Network level signal from 0 to 5.
+
+boolean Roaming [readonly]
+``````````````````````````
+
+Network roaming usage.
+
+byte BattChg [readonly]
+```````````````````````
+
+Battery level from 0 to 5.
+
+string OperatorName [readonly, optional]
+````````````````````````````````````````
+
+Operator name
+
+boolean InbandRingtone [readonly]
+`````````````````````````````````
+
+In-band Ringtone availability.
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH BlueZ v2 2/5] audio/telephony: Add shared interfaces implementation
2025-12-11 18:34 [PATCH BlueZ v2 1/5] doc: Add new telephony related profiles interfaces Frédéric Danis
@ 2025-12-11 18:34 ` Frédéric Danis
2025-12-11 18:34 ` [PATCH BlueZ v2 3/5] audio/hfp-hf: Add skeleton for HFP profile Frédéric Danis
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Frédéric Danis @ 2025-12-11 18:34 UTC (permalink / raw)
To: linux-bluetooth
This implements the Telephony and Call DBus interfaces which will be
shared by telephony related "headset" profiles.
---
v1->v2: Improve commit message
profiles/audio/telephony.c | 885 +++++++++++++++++++++++++++++++++++++
profiles/audio/telephony.h | 115 +++++
2 files changed, 1000 insertions(+)
create mode 100644 profiles/audio/telephony.c
create mode 100644 profiles/audio/telephony.h
diff --git a/profiles/audio/telephony.c b/profiles/audio/telephony.c
new file mode 100644
index 000000000..454b6882c
--- /dev/null
+++ b/profiles/audio/telephony.c
@@ -0,0 +1,885 @@
+/* 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 <stdio.h>
+#include <errno.h>
+#include <limits.h>
+
+#include <stdint.h>
+
+#include <glib.h>
+
+#include "bluetooth/bluetooth.h"
+#include "bluetooth/sdp.h"
+#include "bluetooth/sdp_lib.h"
+#include "bluetooth/uuid.h"
+
+#include "gdbus/gdbus.h"
+
+#include "btio/btio.h"
+#include "src/adapter.h"
+#include "src/btd.h"
+#include "src/dbus-common.h"
+#include "src/device.h"
+#include "src/error.h"
+#include "src/log.h"
+#include "src/plugin.h"
+#include "src/profile.h"
+#include "src/service.h"
+#include "src/shared/hfp.h"
+
+#include "telephony.h"
+
+#define TELEPHONY_INTERFACE "org.bluez.Telephony1"
+#define TELEPHONY_CALL_INTERFACE "org.bluez.Call1"
+
+struct telephony {
+ struct btd_service *service;
+ struct btd_device *device;
+ char *path;
+ bdaddr_t src;
+ bdaddr_t dst;
+ void *profile_data;
+ struct telephony_callbacks *cbs;
+ GSList *uri_schemes;
+ enum connection_state state;
+ bool network_service;
+ uint8_t signal;
+ bool roaming;
+ uint8_t battchg;
+ char *operator_name;
+ bool inband_ringtone;
+};
+
+static const char *state_to_string(enum connection_state state)
+{
+ switch (state) {
+ case CONNECTING:
+ return "connecting";
+ case SESSION_CONNECTING:
+ return "session_connecting";
+ case CONNECTED:
+ return "connected";
+ case DISCONNECTING:
+ return "disconnecting";
+ }
+
+ return NULL;
+}
+
+static const char *call_state_to_string(enum call_state state)
+{
+ switch (state) {
+ case CALL_STATE_ACTIVE:
+ return "active";
+ case CALL_STATE_HELD:
+ return "held";
+ case CALL_STATE_DIALING:
+ return "dialing";
+ case CALL_STATE_ALERTING:
+ return "alerting";
+ case CALL_STATE_INCOMING:
+ return "incoming";
+ case CALL_STATE_WAITING:
+ return "waiting";
+ case CALL_STATE_RESPONSE_AND_HOLD:
+ return "response_and_hold";
+ case CALL_STATE_DISCONNECTED:
+ return "disconnected";
+ }
+
+ return NULL;
+}
+
+struct telephony *telephony_new(struct btd_service *service,
+ void *profile_data,
+ struct telephony_callbacks *cbs)
+{
+ struct btd_device *device = btd_service_get_device(service);
+ const char *path = device_get_path(device);
+ struct btd_adapter *adapter = device_get_adapter(device);
+ struct telephony *ag;
+ static int id;
+
+ ag = g_new0(struct telephony, 1);
+ bacpy(&ag->src, btd_adapter_get_address(adapter));
+ bacpy(&ag->dst, device_get_address(device));
+ ag->service = btd_service_ref(service);
+ ag->device = btd_device_ref(device);
+ ag->path = g_strdup_printf("%s/telephony%u", path, id++);
+ ag->profile_data = profile_data;
+ ag->cbs = cbs;
+
+ return ag;
+}
+
+void telephony_free(struct telephony *telephony)
+{
+ btd_service_unref(telephony->service);
+ btd_device_unref(telephony->device);
+ g_free(telephony->operator_name);
+ g_slist_free_full(telephony->uri_schemes, g_free);
+ g_free(telephony->path);
+ g_free(telephony);
+}
+
+static DBusMessage *dial(DBusConnection *conn, DBusMessage *msg,
+ void *user_data)
+{
+ struct telephony *telephony = user_data;
+
+ if (telephony->cbs && telephony->cbs->dial)
+ return telephony->cbs->dial(conn, msg,
+ telephony->profile_data);
+
+ return btd_error_not_supported(msg);
+}
+
+static DBusMessage *swap_calls(DBusConnection *conn, DBusMessage *msg,
+ void *user_data)
+{
+ struct telephony *telephony = user_data;
+
+ if (telephony->cbs && telephony->cbs->swap_calls)
+ return telephony->cbs->swap_calls(conn, msg,
+ telephony->profile_data);
+
+ return btd_error_not_supported(msg);
+}
+
+static DBusMessage *release_and_answer(DBusConnection *conn, DBusMessage *msg,
+ void *user_data)
+{
+ struct telephony *telephony = user_data;
+
+ if (telephony->cbs && telephony->cbs->release_and_answer)
+ return telephony->cbs->release_and_answer(conn, msg,
+ telephony->profile_data);
+
+ return btd_error_not_supported(msg);
+}
+
+static DBusMessage *release_and_swap(DBusConnection *conn, DBusMessage *msg,
+ void *user_data)
+{
+ struct telephony *telephony = user_data;
+
+ if (telephony->cbs && telephony->cbs->release_and_swap)
+ return telephony->cbs->release_and_swap(conn, msg,
+ telephony->profile_data);
+
+ return btd_error_not_supported(msg);
+}
+
+static DBusMessage *hold_and_answer(DBusConnection *conn, DBusMessage *msg,
+ void *user_data)
+{
+ struct telephony *telephony = user_data;
+
+ if (telephony->cbs && telephony->cbs->hold_and_answer)
+ return telephony->cbs->hold_and_answer(conn, msg,
+ telephony->profile_data);
+
+ return btd_error_not_supported(msg);
+}
+
+static DBusMessage *hangup_all(DBusConnection *conn, DBusMessage *msg,
+ void *user_data)
+{
+ struct telephony *telephony = user_data;
+
+ if (telephony->cbs && telephony->cbs->hangup_all)
+ return telephony->cbs->hangup_all(conn, msg,
+ telephony->profile_data);
+
+ return btd_error_not_supported(msg);
+}
+
+static DBusMessage *create_multiparty(DBusConnection *conn, DBusMessage *msg,
+ void *user_data)
+{
+ struct telephony *telephony = user_data;
+
+ if (telephony->cbs && telephony->cbs->create_multiparty)
+ return telephony->cbs->create_multiparty(conn, msg,
+ telephony->profile_data);
+
+ return btd_error_not_supported(msg);
+}
+
+static DBusMessage *send_tones(DBusConnection *conn, DBusMessage *msg,
+ void *user_data)
+{
+ struct telephony *telephony = user_data;
+
+ if (telephony->cbs && telephony->cbs->send_tones)
+ return telephony->cbs->send_tones(conn, msg,
+ telephony->profile_data);
+
+ return btd_error_not_supported(msg);
+}
+
+static gboolean property_get_uuid(const GDBusPropertyTable *property,
+ DBusMessageIter *iter,
+ void *user_data)
+{
+ struct telephony *telephony = user_data;
+ struct btd_profile *p = btd_service_get_profile(telephony->service);
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &p->remote_uuid);
+
+ return TRUE;
+}
+
+static gboolean property_get_uri_schemes(const GDBusPropertyTable *property,
+ DBusMessageIter *iter,
+ void *user_data)
+{
+ struct telephony *telephony = user_data;
+ DBusMessageIter entry;
+ GSList *l;
+
+ dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
+ DBUS_TYPE_STRING_AS_STRING, &entry);
+
+ for (l = telephony->uri_schemes; l != NULL; l = l->next)
+ dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING,
+ &l->data);
+
+ dbus_message_iter_close_container(iter, &entry);
+
+ return TRUE;
+}
+
+static gboolean property_get_state(const GDBusPropertyTable *property,
+ DBusMessageIter *iter,
+ void *user_data)
+{
+ struct telephony *telephony = user_data;
+ const char *string;
+
+ string = state_to_string(telephony->state);
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &string);
+
+ return TRUE;
+}
+
+static gboolean property_get_service(const GDBusPropertyTable *property,
+ DBusMessageIter *iter,
+ void *user_data)
+{
+ struct telephony *telephony = user_data;
+ dbus_bool_t value;
+
+ value = telephony->network_service;
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value);
+
+ return TRUE;
+}
+
+static gboolean property_get_signal(const GDBusPropertyTable *property,
+ DBusMessageIter *iter,
+ void *user_data)
+{
+ struct telephony *telephony = user_data;
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE,
+ &telephony->signal);
+
+ return TRUE;
+}
+
+static gboolean property_get_roaming(const GDBusPropertyTable *property,
+ DBusMessageIter *iter,
+ void *user_data)
+{
+ struct telephony *telephony = user_data;
+ dbus_bool_t value;
+
+ value = telephony->roaming;
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value);
+
+ return TRUE;
+}
+
+static gboolean property_get_battchg(const GDBusPropertyTable *property,
+ DBusMessageIter *iter,
+ void *user_data)
+{
+ struct telephony *telephony = user_data;
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE,
+ &telephony->battchg);
+
+ return TRUE;
+}
+
+static gboolean property_operator_name_exists(
+ const GDBusPropertyTable *property,
+ void *user_data)
+{
+ struct telephony *telephony = user_data;
+
+ return telephony->operator_name != NULL;
+}
+
+static gboolean property_get_operator_name(const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *user_data)
+{
+ struct telephony *telephony = user_data;
+
+ if (telephony->operator_name == NULL)
+ return FALSE;
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING,
+ &telephony->operator_name);
+
+ return TRUE;
+}
+
+static gboolean property_get_inband_ringtone(const GDBusPropertyTable *property,
+ DBusMessageIter *iter,
+ void *user_data)
+{
+ struct telephony *telephony = user_data;
+ dbus_bool_t value;
+
+ value = telephony->inband_ringtone;
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value);
+
+ return TRUE;
+}
+
+static const GDBusMethodTable telephony_methods[] = {
+ { GDBUS_ASYNC_METHOD("Dial", GDBUS_ARGS({"uri", "s"}), NULL,
+ dial) },
+ { GDBUS_ASYNC_METHOD("SwapCalls", NULL, NULL, swap_calls) },
+ { GDBUS_ASYNC_METHOD("ReleaseAndAnswer", NULL, NULL,
+ release_and_answer) },
+ { GDBUS_ASYNC_METHOD("ReleaseAndSwap", NULL, NULL,
+ release_and_swap) },
+ { GDBUS_ASYNC_METHOD("HoldAndAnswer", NULL, NULL,
+ hold_and_answer) },
+ { GDBUS_ASYNC_METHOD("HangupAll", NULL, NULL, hangup_all) },
+ { GDBUS_ASYNC_METHOD("CreateMultiparty", NULL,
+ GDBUS_ARGS({ "calls", "ao" }),
+ create_multiparty) },
+ { GDBUS_ASYNC_METHOD("SendTones", GDBUS_ARGS({"number", "s"}), NULL,
+ send_tones) },
+ { }
+};
+
+static const GDBusPropertyTable telephony_properties[] = {
+ { "UUID", "s", property_get_uuid },
+ { "SupportedURISchemes", "as", property_get_uri_schemes },
+ { "State", "s", property_get_state },
+ { "Service", "b", property_get_service },
+ { "Signal", "y", property_get_signal },
+ { "Roaming", "b", property_get_roaming },
+ { "BattChg", "y", property_get_battchg },
+ { "OperatorName", "s", property_get_operator_name, NULL,
+ property_operator_name_exists },
+ { "InbandRingtone", "b", property_get_inband_ringtone },
+ { }
+};
+
+static void path_unregister(void *data)
+{
+ struct telephony *telephony = data;
+
+ DBG("Unregistered interface %s on path %s", TELEPHONY_INTERFACE,
+ telephony->path);
+}
+
+int telephony_register_interface(struct telephony *telephony)
+{
+ if (telephony->cbs == NULL)
+ return -EINVAL;
+
+ if (!g_dbus_register_interface(btd_get_dbus_connection(),
+ telephony->path,
+ TELEPHONY_INTERFACE,
+ telephony_methods, NULL,
+ telephony_properties, telephony,
+ path_unregister)) {
+ return -EINVAL;
+ }
+
+ DBG("Registered interface %s on path %s", TELEPHONY_INTERFACE,
+ telephony->path);
+
+ return 0;
+}
+
+void telephony_unregister_interface(struct telephony *telephony)
+{
+ g_dbus_unregister_interface(btd_get_dbus_connection(), telephony->path,
+ TELEPHONY_INTERFACE);
+}
+
+struct btd_service *telephony_get_service(struct telephony *telephony)
+{
+ return telephony->service;
+}
+
+struct btd_device *telephony_get_device(struct telephony *telephony)
+{
+ return telephony->device;
+}
+
+const char *telephony_get_path(struct telephony *telephony)
+{
+ return telephony->path;
+}
+
+bdaddr_t telephony_get_src(struct telephony *telephony)
+{
+ return telephony->src;
+}
+
+bdaddr_t telephony_get_dst(struct telephony *telephony)
+{
+ return telephony->dst;
+}
+
+void *telephony_get_profile_data(struct telephony *telephony)
+{
+ return telephony->profile_data;
+}
+
+void telephony_add_uri_scheme(struct telephony *telephony, const char *scheme)
+{
+ telephony->uri_schemes = g_slist_append(telephony->uri_schemes,
+ g_strdup(scheme));
+
+ g_dbus_emit_property_changed(btd_get_dbus_connection(),
+ telephony->path, TELEPHONY_INTERFACE,
+ "SupportedURISchemes");
+}
+
+void telephony_remove_uri_scheme(struct telephony *telephony,
+ const char *scheme)
+{
+ GSList *l;
+
+ l = g_slist_find_custom(telephony->uri_schemes, scheme,
+ (GCompareFunc)strcasecmp);
+ if (!l)
+ return;
+
+ telephony->uri_schemes = g_slist_delete_link(telephony->uri_schemes,
+ l);
+
+ g_dbus_emit_property_changed(btd_get_dbus_connection(),
+ telephony->path, TELEPHONY_INTERFACE,
+ "SupportedURISchemes");
+}
+
+void telephony_set_state(struct telephony *telephony,
+ enum connection_state state)
+{
+ char address[18];
+
+ if (telephony->state == state)
+ return;
+
+ ba2str(&telephony->dst, address);
+ DBG("device %s state %s -> %s", address,
+ state_to_string(telephony->state),
+ state_to_string(state));
+
+ telephony->state = state;
+
+ g_dbus_emit_property_changed(btd_get_dbus_connection(),
+ telephony->path, TELEPHONY_INTERFACE,
+ "State");
+}
+
+enum connection_state telephony_get_state(struct telephony *telephony)
+{
+ return telephony->state;
+}
+
+void telephony_set_network_service(struct telephony *telephony, bool service)
+{
+ char address[18];
+
+ if (telephony->network_service == service)
+ return;
+
+ ba2str(&telephony->dst, address);
+ DBG("device %s network service %u -> %u", address,
+ telephony->network_service,
+ service);
+
+ telephony->network_service = service;
+
+ g_dbus_emit_property_changed(btd_get_dbus_connection(),
+ telephony->path, TELEPHONY_INTERFACE,
+ "Service");
+}
+
+bool telephony_get_network_service(struct telephony *telephony)
+{
+ return telephony->network_service;
+}
+
+void telephony_set_signal(struct telephony *telephony, uint8_t signal)
+{
+ char address[18];
+
+ if (telephony->signal == signal)
+ return;
+
+ ba2str(&telephony->dst, address);
+ DBG("device %s signal %u -> %u", address, telephony->signal, signal);
+
+ telephony->signal = signal;
+
+ g_dbus_emit_property_changed(btd_get_dbus_connection(),
+ telephony->path, TELEPHONY_INTERFACE,
+ "Signal");
+}
+
+uint8_t telephony_get_signal(struct telephony *telephony)
+{
+ return telephony->signal;
+}
+
+void telephony_set_roaming(struct telephony *telephony, bool roaming)
+{
+ char address[18];
+
+ if (telephony->roaming == roaming)
+ return;
+
+ ba2str(&telephony->dst, address);
+ DBG("device %s roaming %u -> %u", address,
+ telephony->roaming,
+ roaming);
+
+ telephony->roaming = roaming;
+
+ g_dbus_emit_property_changed(btd_get_dbus_connection(),
+ telephony->path, TELEPHONY_INTERFACE,
+ "Roaming");
+}
+
+bool telephony_get_roaming(struct telephony *telephony)
+{
+ return telephony->roaming;
+}
+
+void telephony_set_battchg(struct telephony *telephony, uint8_t battchg)
+{
+ char address[18];
+
+ if (telephony->battchg == battchg)
+ return;
+
+ ba2str(&telephony->dst, address);
+ DBG("device %s battchg %u -> %u", address, telephony->battchg, battchg);
+
+ telephony->battchg = battchg;
+
+ g_dbus_emit_property_changed(btd_get_dbus_connection(),
+ telephony->path, TELEPHONY_INTERFACE,
+ "BattChg");
+}
+
+uint8_t telephony_get_battchg(struct telephony *telephony)
+{
+ return telephony->battchg;
+}
+
+void telephony_set_operator_name(struct telephony *telephony,
+ const char *name)
+{
+ char address[18];
+
+ if (telephony->operator_name &&
+ g_str_equal(telephony->operator_name, name))
+ return;
+
+ ba2str(&telephony->dst, address);
+ DBG("device %s operator name %s -> %s", address,
+ telephony->operator_name, name);
+
+ if (telephony->operator_name)
+ g_free(telephony->operator_name);
+ telephony->operator_name = g_strdup(name);
+
+ g_dbus_emit_property_changed(btd_get_dbus_connection(),
+ telephony->path, TELEPHONY_INTERFACE,
+ "OperatorName");
+}
+
+const char *telephony_get_operator_name(struct telephony *telephony)
+{
+ return telephony->operator_name;
+}
+
+void telephony_set_inband_ringtone(struct telephony *telephony, bool enabled)
+{
+ char address[18];
+
+ if (telephony->inband_ringtone == enabled)
+ return;
+
+ ba2str(&telephony->dst, address);
+ DBG("device %s inband ringtone %u -> %u", address,
+ telephony->inband_ringtone,
+ enabled);
+
+ telephony->inband_ringtone = enabled;
+
+ g_dbus_emit_property_changed(btd_get_dbus_connection(),
+ telephony->path, TELEPHONY_INTERFACE,
+ "InbandRingtone");
+}
+
+bool telephony_get_inband_ringtone(struct telephony *telephony)
+{
+ return telephony->inband_ringtone;
+}
+
+struct call *telephony_new_call(struct telephony *telephony,
+ uint8_t idx,
+ enum call_state state,
+ void *user_data)
+{
+ struct call *call;
+
+ call = g_new0(struct call, 1);
+ call->device = telephony;
+ call->state = state;
+ call->idx = idx;
+ call->path = g_strdup_printf("%s/call%u", telephony->path, call->idx);
+
+ return call;
+}
+
+void telephony_free_call(struct call *call)
+{
+ if (call->pending_msg)
+ dbus_message_unref(call->pending_msg);
+
+ g_free(call->name);
+ g_free(call->incoming_line);
+ g_free(call->line_id);
+ g_free(call->path);
+ g_free(call);
+}
+
+static DBusMessage *call_answer(DBusConnection *conn, DBusMessage *msg,
+ void *call_data)
+{
+ struct call *call = call_data;
+ struct telephony *telephony = call->device;
+
+ return telephony->cbs->call_answer(conn, msg, call_data);
+}
+
+static DBusMessage *call_hangup(DBusConnection *conn, DBusMessage *msg,
+ void *call_data)
+{
+ struct call *call = call_data;
+ struct telephony *telephony = call->device;
+
+ return telephony->cbs->call_hangup(conn, msg, call_data);
+}
+
+static gboolean call_line_id_exists(const GDBusPropertyTable *property,
+ void *data)
+{
+ struct call *call = data;
+
+ return call->line_id != NULL;
+}
+
+static gboolean call_property_get_line_id(
+ const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *data)
+{
+ struct call *call = data;
+
+ if (call->line_id == NULL)
+ return FALSE;
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &call->line_id);
+
+ return TRUE;
+}
+
+static gboolean call_incoming_line_exists(const GDBusPropertyTable *property,
+ void *data)
+{
+ struct call *call = data;
+
+ return call->incoming_line != NULL;
+}
+
+static gboolean call_property_get_incoming_line(
+ const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *data)
+{
+ struct call *call = data;
+
+ if (call->incoming_line == NULL)
+ return FALSE;
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING,
+ &call->incoming_line);
+
+ return TRUE;
+}
+
+static gboolean call_name_exists(const GDBusPropertyTable *property,
+ void *data)
+{
+ struct call *call = data;
+
+ return call->name != NULL;
+}
+
+static gboolean call_property_get_name(const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *data)
+{
+ struct call *call = data;
+
+ if (call->name == NULL)
+ return FALSE;
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &call->name);
+
+ return TRUE;
+}
+
+static gboolean call_property_get_multiparty(
+ const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *data)
+{
+ struct call *call = data;
+ dbus_bool_t value;
+
+ value = call->multiparty;
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value);
+
+ return TRUE;
+}
+
+static gboolean call_property_get_state(const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *data)
+{
+ struct call *call = data;
+ const char *string;
+
+ string = call_state_to_string(call->state);
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &string);
+
+ return TRUE;
+}
+
+static const GDBusMethodTable telephony_call_methods[] = {
+ { GDBUS_ASYNC_METHOD("Answer", NULL, NULL, call_answer) },
+ { GDBUS_ASYNC_METHOD("Hangup", NULL, NULL, call_hangup) },
+ { }
+};
+
+static const GDBusPropertyTable telephony_call_properties[] = {
+ { "LineIdentification", "s", call_property_get_line_id, NULL,
+ call_line_id_exists },
+ { "IncomingLine", "s", call_property_get_incoming_line, NULL,
+ call_incoming_line_exists },
+ { "Name", "s", call_property_get_name, NULL, call_name_exists },
+ { "Multiparty", "b", call_property_get_multiparty },
+ { "State", "s", call_property_get_state },
+ { }
+};
+
+static void call_path_unregister(void *user_data)
+{
+ struct call *call = user_data;
+
+ DBG("Unregistered interface %s on path %s", TELEPHONY_CALL_INTERFACE,
+ call->path);
+
+ telephony_free_call(call);
+}
+
+int telephony_call_register_interface(struct call *call)
+{
+ if (call->device->cbs == NULL)
+ return -EINVAL;
+
+ if (!g_dbus_register_interface(btd_get_dbus_connection(),
+ call->path,
+ TELEPHONY_CALL_INTERFACE,
+ telephony_call_methods, NULL,
+ telephony_call_properties, call,
+ call_path_unregister)) {
+ return -EINVAL;
+ }
+
+ DBG("Registered interface %s on path %s", TELEPHONY_CALL_INTERFACE,
+ call->path);
+
+ return 0;
+}
+
+void telephony_call_unregister_interface(struct call *call)
+{
+ g_dbus_unregister_interface(btd_get_dbus_connection(),
+ call->path,
+ TELEPHONY_CALL_INTERFACE);
+}
+
+void telephony_call_set_state(struct call *call, enum call_state state)
+{
+ if (call->state == state)
+ return;
+
+ DBG("%s state %s -> %s", call->path, call_state_to_string(call->state),
+ call_state_to_string(state));
+
+ call->state = state;
+
+ g_dbus_emit_property_changed(btd_get_dbus_connection(),
+ call->path, TELEPHONY_CALL_INTERFACE,
+ "State");
+}
+
+void telephony_call_set_line_id(struct call *call, const char *line_id)
+{
+ if (call->line_id && g_str_equal(call->line_id, line_id))
+ return;
+
+ g_free(call->line_id);
+ call->line_id = g_strdup(line_id);
+
+ DBG("%s line_id: %s", call->path, call->line_id);
+
+ g_dbus_emit_property_changed(btd_get_dbus_connection(),
+ call->path, TELEPHONY_CALL_INTERFACE,
+ "LineIdentification");
+}
diff --git a/profiles/audio/telephony.h b/profiles/audio/telephony.h
new file mode 100644
index 000000000..aefda9307
--- /dev/null
+++ b/profiles/audio/telephony.h
@@ -0,0 +1,115 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright © 2025 Collabora Ltd.
+ *
+ *
+ */
+
+enum connection_state {
+ CONNECTING = 0,
+ SESSION_CONNECTING,
+ CONNECTED,
+ DISCONNECTING
+};
+
+enum call_state {
+ CALL_STATE_ACTIVE = 0,
+ CALL_STATE_HELD,
+ CALL_STATE_DIALING,
+ CALL_STATE_ALERTING,
+ CALL_STATE_INCOMING,
+ CALL_STATE_WAITING,
+ CALL_STATE_RESPONSE_AND_HOLD,
+ CALL_STATE_DISCONNECTED,
+};
+
+struct telephony;
+
+struct telephony_callbacks {
+ DBusMessage *(*dial)(DBusConnection *conn, DBusMessage *msg,
+ void *profile_data);
+ DBusMessage *(*swap_calls)(DBusConnection *conn, DBusMessage *msg,
+ void *profile_data);
+ DBusMessage *(*release_and_answer)(DBusConnection *conn,
+ DBusMessage *msg,
+ void *profile_data);
+ DBusMessage *(*release_and_swap)(DBusConnection *conn,
+ DBusMessage *msg,
+ void *profile_data);
+ DBusMessage *(*hold_and_answer)(DBusConnection *conn,
+ DBusMessage *msg,
+ void *profile_data);
+ DBusMessage *(*hangup_all)(DBusConnection *conn, DBusMessage *msg,
+ void *profile_data);
+ DBusMessage *(*create_multiparty)(DBusConnection *conn,
+ DBusMessage *msg,
+ void *profile_data);
+ DBusMessage *(*send_tones)(DBusConnection *conn, DBusMessage *msg,
+ void *profile_data);
+
+ DBusMessage *(*call_answer)(DBusConnection *conn, DBusMessage *msg,
+ void *call_data);
+ DBusMessage *(*call_hangup)(DBusConnection *conn, DBusMessage *msg,
+ void *call_data);
+};
+
+struct call {
+ struct telephony *device;
+ char *path;
+ uint8_t idx;
+
+ char *line_id;
+ char *incoming_line;
+ char *name;
+ bool multiparty;
+ enum call_state state;
+
+ DBusMessage *pending_msg;
+};
+
+struct telephony *telephony_new(struct btd_service *service,
+ void *profile_data,
+ struct telephony_callbacks *cbs);
+void telephony_free(struct telephony *telephony);
+int telephony_register_interface(struct telephony *telephony);
+void telephony_unregister_interface(struct telephony *telephony);
+
+struct btd_service *telephony_get_service(struct telephony *telephony);
+struct btd_device *telephony_get_device(struct telephony *telephony);
+const char *telephony_get_path(struct telephony *telephony);
+bdaddr_t telephony_get_src(struct telephony *telephony);
+bdaddr_t telephony_get_dst(struct telephony *telephony);
+void *telephony_get_profile_data(struct telephony *telephony);
+void telephony_add_uri_scheme(struct telephony *telephony, const char *scheme);
+void telephony_remove_uri_scheme(struct telephony *telephony,
+ const char *scheme);
+void telephony_set_state(struct telephony *telephony,
+ enum connection_state state);
+enum connection_state telephony_get_state(struct telephony *telephony);
+void telephony_set_network_service(struct telephony *telephony, bool service);
+bool telephony_get_network_service(struct telephony *telephony);
+void telephony_set_signal(struct telephony *telephony, uint8_t signal);
+uint8_t telephony_get_signal(struct telephony *telephony);
+void telephony_set_roaming(struct telephony *telephony, bool roaming);
+bool telephony_get_roaming(struct telephony *telephony);
+void telephony_set_battchg(struct telephony *telephony, uint8_t battchg);
+uint8_t telephony_get_battchg(struct telephony *telephony);
+void telephony_set_operator_name(struct telephony *telephony,
+ const char *name);
+const char *telephony_get_operator_name(struct telephony *telephony);
+void telephony_set_inband_ringtone(struct telephony *telephony, bool enabled);
+bool telephony_get_inband_ringtone(struct telephony *telephony);
+
+struct call *telephony_new_call(struct telephony *telephony,
+ uint8_t idx,
+ enum call_state state,
+ void *user_data);
+void telephony_free_call(struct call *call);
+int telephony_call_register_interface(struct call *call);
+void telephony_call_unregister_interface(struct call *call);
+
+void telephony_call_set_state(struct call *call, enum call_state state);
+void telephony_call_set_line_id(struct call *call, const char *line_id);
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH BlueZ v2 3/5] audio/hfp-hf: Add skeleton for HFP profile
2025-12-11 18:34 [PATCH BlueZ v2 1/5] doc: Add new telephony related profiles interfaces Frédéric Danis
2025-12-11 18:34 ` [PATCH BlueZ v2 2/5] audio/telephony: Add shared interfaces implementation Frédéric Danis
@ 2025-12-11 18:34 ` Frédéric Danis
2025-12-11 18:34 ` [PATCH BlueZ v2 4/5] audio/hfp-hf: Add HFP SLC connection and event support Frédéric Danis
` (3 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Frédéric Danis @ 2025-12-11 18:34 UTC (permalink / raw)
To: linux-bluetooth
This starts the Telephony interface for HFP HF, allowing to connect to
the remote HFP AG device.
---
v1->v2:
- Fix 'make distcheck' by adding missing profiles/audio/telephony.h to
builtin_sources
- Improve commit message
Makefile.plugins | 5 +
configure.ac | 7 ++
profiles/audio/hfp-hf.c | 220 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 232 insertions(+)
create mode 100644 profiles/audio/hfp-hf.c
diff --git a/Makefile.plugins b/Makefile.plugins
index 3572ee845..3a95b5fb5 100644
--- a/Makefile.plugins
+++ b/Makefile.plugins
@@ -160,3 +160,8 @@ if ASHA
builtin_modules += asha
builtin_sources += profiles/audio/asha.h profiles/audio/asha.c
endif
+
+if HFP
+builtin_modules += hfp
+builtin_sources += profiles/audio/telephony.h profiles/audio/telephony.c profiles/audio/hfp-hf.c
+endif
diff --git a/configure.ac b/configure.ac
index 16b81aca3..b6f6789be 100644
--- a/configure.ac
+++ b/configure.ac
@@ -248,6 +248,13 @@ if test "${enable_asha}" != "no"; then
AC_DEFINE(HAVE_ASHA, 1, [Define to 1 if you have ASHA support.])
fi
+AC_ARG_ENABLE(hfp, AS_HELP_STRING([--disable-hfp],
+ [disable HFP support]), [enable_hfp=${enableval}])
+AM_CONDITIONAL(HFP, test "${enable_hfp}" != "no")
+if test "${enable_hfp}" != "no"; then
+ AC_DEFINE(HAVE_HFP, 1, [Define to 1 if you have HFP support.])
+fi
+
AC_ARG_ENABLE(tools, AS_HELP_STRING([--disable-tools],
[disable Bluetooth tools]), [enable_tools=${enableval}])
AM_CONDITIONAL(TOOLS, test "${enable_tools}" != "no")
diff --git a/profiles/audio/hfp-hf.c b/profiles/audio/hfp-hf.c
new file mode 100644
index 000000000..5375168cf
--- /dev/null
+++ b/profiles/audio/hfp-hf.c
@@ -0,0 +1,220 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2014 Intel Corporation. All rights reserved.
+ * Copyright © 2025 Collabora Ltd.
+ *
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define _GNU_SOURCE
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <limits.h>
+
+#include <stdint.h>
+
+#include <glib.h>
+
+#include "bluetooth/bluetooth.h"
+#include "bluetooth/sdp.h"
+#include "bluetooth/sdp_lib.h"
+#include "bluetooth/uuid.h"
+
+#include "gdbus/gdbus.h"
+
+#include "btio/btio.h"
+#include "src/adapter.h"
+#include "src/btd.h"
+#include "src/dbus-common.h"
+#include "src/device.h"
+#include "src/log.h"
+#include "src/plugin.h"
+#include "src/profile.h"
+#include "src/service.h"
+
+#include "telephony.h"
+
+#define URI "tel"
+#define URI_PREFIX URI ":"
+
+struct hfp_device {
+ struct telephony *telephony;
+ uint16_t version;
+ GIOChannel *io;
+};
+
+static void device_destroy(struct hfp_device *dev)
+{
+ DBG("%s", telephony_get_path(dev->telephony));
+
+ if (dev->io) {
+ g_io_channel_unref(dev->io);
+ dev->io = NULL;
+ }
+
+ telephony_unregister_interface(dev->telephony);
+}
+
+static void connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
+{
+ struct hfp_device *dev = user_data;
+ struct btd_service *service = telephony_get_service(dev->telephony);
+
+ DBG("");
+
+ if (err) {
+ error("%s", err->message);
+ goto failed;
+ }
+
+ g_io_channel_set_close_on_unref(chan, FALSE);
+
+ btd_service_connecting_complete(service, 0);
+
+ return;
+
+failed:
+ g_io_channel_shutdown(chan, TRUE, NULL);
+ device_destroy(dev);
+}
+
+struct telephony_callbacks hfp_callbacks = {
+};
+
+static int hfp_connect(struct btd_service *service)
+{
+ struct hfp_device *dev;
+ struct btd_profile *p;
+ const sdp_record_t *rec;
+ sdp_list_t *list, *protos;
+ sdp_profile_desc_t *desc;
+ int channel;
+ bdaddr_t src, dst;
+ GError *err = NULL;
+
+ DBG("");
+
+ dev = btd_service_get_user_data(service);
+
+ p = btd_service_get_profile(service);
+ rec = btd_device_get_record(telephony_get_device(dev->telephony),
+ p->remote_uuid);
+ if (!rec)
+ return -EIO;
+
+ if (sdp_get_profile_descs(rec, &list) == 0) {
+ desc = list->data;
+ dev->version = desc->version;
+ }
+ sdp_list_free(list, free);
+
+ if (sdp_get_access_protos(rec, &protos) < 0) {
+ error("unable to get access protocols from record");
+ return -EIO;
+ }
+
+ channel = sdp_get_proto_port(protos, RFCOMM_UUID);
+ sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free, NULL);
+ sdp_list_free(protos, NULL);
+ if (channel <= 0) {
+ error("unable to get RFCOMM channel from record");
+ return -EIO;
+ }
+
+ src = telephony_get_src(dev->telephony);
+ dst = telephony_get_dst(dev->telephony);
+ dev->io = bt_io_connect(connect_cb, dev,
+ NULL, &err,
+ BT_IO_OPT_SOURCE_BDADDR, &src,
+ BT_IO_OPT_DEST_BDADDR, &dst,
+ BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
+ BT_IO_OPT_CHANNEL, channel,
+ BT_IO_OPT_INVALID);
+ if (dev->io == NULL) {
+ error("unable to start connection");
+ return -EIO;
+ }
+
+ return telephony_register_interface(dev->telephony);
+}
+
+static int hfp_disconnect(struct btd_service *service)
+{
+ DBG("");
+
+ btd_service_disconnecting_complete(service, 0);
+
+ return 0;
+}
+
+static int hfp_probe(struct btd_service *service)
+{
+ struct btd_device *device = btd_service_get_device(service);
+ const char *path = device_get_path(device);
+ struct hfp_device *dev;
+
+ DBG("%s", path);
+
+ dev = g_new0(struct hfp_device, 1);
+ if (!dev)
+ return -EINVAL;
+
+ dev->telephony = telephony_new(service, dev, &hfp_callbacks);
+ btd_service_set_user_data(service, dev);
+ telephony_add_uri_scheme(dev->telephony, URI);
+
+ return 0;
+}
+
+static void hfp_remove(struct btd_service *service)
+{
+ struct btd_device *device = btd_service_get_device(service);
+ const char *path = device_get_path(device);
+ struct hfp_device *dev;
+
+ DBG("%s", path);
+
+ dev = btd_service_get_user_data(service);
+
+ telephony_free(dev->telephony);
+ g_free(dev);
+}
+
+static struct btd_profile hfp_hf_profile = {
+ .name = "hfp",
+ .priority = BTD_PROFILE_PRIORITY_MEDIUM,
+
+ .remote_uuid = HFP_AG_UUID,
+ .device_probe = hfp_probe,
+ .device_remove = hfp_remove,
+
+ .auto_connect = true,
+ .connect = hfp_connect,
+ .disconnect = hfp_disconnect,
+
+ .experimental = true,
+};
+
+static int hfp_init(void)
+{
+ btd_profile_register(&hfp_hf_profile);
+
+ return 0;
+}
+
+static void hfp_exit(void)
+{
+ btd_profile_unregister(&hfp_hf_profile);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(hfp, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT,
+ hfp_init, hfp_exit)
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH BlueZ v2 4/5] audio/hfp-hf: Add HFP SLC connection and event support
2025-12-11 18:34 [PATCH BlueZ v2 1/5] doc: Add new telephony related profiles interfaces Frédéric Danis
2025-12-11 18:34 ` [PATCH BlueZ v2 2/5] audio/telephony: Add shared interfaces implementation Frédéric Danis
2025-12-11 18:34 ` [PATCH BlueZ v2 3/5] audio/hfp-hf: Add skeleton for HFP profile Frédéric Danis
@ 2025-12-11 18:34 ` Frédéric Danis
2025-12-11 18:34 ` [PATCH BlueZ v2 5/5] client/telephony: Add new submenu Frédéric Danis
` (2 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Frédéric Danis @ 2025-12-11 18:34 UTC (permalink / raw)
To: linux-bluetooth
This implements the HFP HF SLC connection and events support for the
Telephony DBus interface.
---
v1->v2: Improve commit message
profiles/audio/hfp-hf.c | 96 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 96 insertions(+)
diff --git a/profiles/audio/hfp-hf.c b/profiles/audio/hfp-hf.c
index 5375168cf..d25cda389 100644
--- a/profiles/audio/hfp-hf.c
+++ b/profiles/audio/hfp-hf.c
@@ -40,6 +40,7 @@
#include "src/plugin.h"
#include "src/profile.h"
#include "src/service.h"
+#include "src/shared/hfp.h"
#include "telephony.h"
@@ -50,12 +51,25 @@ struct hfp_device {
struct telephony *telephony;
uint16_t version;
GIOChannel *io;
+ struct hfp_hf *hf;
};
+static void hfp_hf_debug(const char *str, void *user_data)
+{
+ DBG_IDX(0xffff, "%s", str);
+}
+
static void device_destroy(struct hfp_device *dev)
{
DBG("%s", telephony_get_path(dev->telephony));
+ telephony_set_state(dev->telephony, DISCONNECTING);
+
+ if (dev->hf) {
+ hfp_hf_unref(dev->hf);
+ dev->hf = NULL;
+ }
+
if (dev->io) {
g_io_channel_unref(dev->io);
dev->io = NULL;
@@ -64,6 +78,62 @@ static void device_destroy(struct hfp_device *dev)
telephony_unregister_interface(dev->telephony);
}
+static void hfp_hf_update_indicator(enum hfp_indicator indicator, uint32_t val,
+ void *user_data)
+{
+ struct hfp_device *dev = user_data;
+
+ switch (indicator) {
+ case HFP_INDICATOR_SERVICE:
+ telephony_set_network_service(dev->telephony, val);
+ break;
+ case HFP_INDICATOR_CALL:
+ break;
+ case HFP_INDICATOR_CALLSETUP:
+ break;
+ case HFP_INDICATOR_CALLHELD:
+ break;
+ case HFP_INDICATOR_SIGNAL:
+ telephony_set_signal(dev->telephony, val);
+ break;
+ case HFP_INDICATOR_ROAM:
+ telephony_set_roaming(dev->telephony, val);
+ break;
+ case HFP_INDICATOR_BATTCHG:
+ telephony_set_battchg(dev->telephony, val);
+ break;
+ case HFP_INDICATOR_LAST:
+ default:
+ DBG("Unknown signal indicator: %u", indicator);
+ }
+}
+
+static void hfp_hf_session_ready_cb(enum hfp_result res, enum hfp_error cme_err,
+ void *user_data)
+{
+ struct hfp_device *dev = user_data;
+
+ if (res != HFP_RESULT_OK) {
+ error("Session setup error: %d, dropping connection", res);
+ hfp_hf_disconnect(dev->hf);
+ return;
+ }
+
+ telephony_set_state(dev->telephony, CONNECTED);
+}
+
+static struct hfp_hf_callbacks hf_session_callbacks = {
+ .session_ready = hfp_hf_session_ready_cb,
+ .update_indicator = hfp_hf_update_indicator,
+};
+
+static void hfp_disconnect_watch(void *user_data)
+{
+ DBG("");
+
+ device_destroy(user_data);
+}
+
static void connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
{
struct hfp_device *dev = user_data;
@@ -76,8 +146,27 @@ static void connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
goto failed;
}
+ dev->hf = hfp_hf_new(g_io_channel_unix_get_fd(chan));
+ if (!dev->hf) {
+ error("Could not create hfp io");
+ goto failed;
+ }
+
+ hfp_hf_set_debug(dev->hf, hfp_hf_debug, NULL, NULL);
g_io_channel_set_close_on_unref(chan, FALSE);
+ hfp_hf_set_close_on_unref(dev->hf, true);
+ hfp_hf_set_disconnect_handler(dev->hf, hfp_disconnect_watch,
+ dev, NULL);
+ hfp_hf_session_register(dev->hf, &hf_session_callbacks, dev);
+
+ if (!hfp_hf_session(dev->hf)) {
+ error("Could not start SLC creation");
+ hfp_hf_disconnect(dev->hf);
+ goto failed;
+ }
+
+ telephony_set_state(dev->telephony, SESSION_CONNECTING);
btd_service_connecting_complete(service, 0);
return;
@@ -149,8 +238,15 @@ static int hfp_connect(struct btd_service *service)
static int hfp_disconnect(struct btd_service *service)
{
+ struct hfp_device *dev;
+
DBG("");
+ dev = btd_service_get_user_data(service);
+
+ if (dev->hf)
+ hfp_hf_disconnect(dev->hf);
+
btd_service_disconnecting_complete(service, 0);
return 0;
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH BlueZ v2 5/5] client/telephony: Add new submenu
2025-12-11 18:34 [PATCH BlueZ v2 1/5] doc: Add new telephony related profiles interfaces Frédéric Danis
` (2 preceding siblings ...)
2025-12-11 18:34 ` [PATCH BlueZ v2 4/5] audio/hfp-hf: Add HFP SLC connection and event support Frédéric Danis
@ 2025-12-11 18:34 ` Frédéric Danis
2025-12-11 19:37 ` [BlueZ,v2,1/5] doc: Add new telephony related profiles interfaces bluez.test.bot
2025-12-12 15:30 ` [PATCH BlueZ v2 1/5] " patchwork-bot+bluetooth
5 siblings, 0 replies; 7+ messages in thread
From: Frédéric Danis @ 2025-12-11 18:34 UTC (permalink / raw)
To: linux-bluetooth
This add a new submenu to bluetoothctl to be able to manage telephony
related "headset" profiles.
It currently allows to control calls from an HFP phone, i.e. starts a
call, hangup a call, accepting or rejecting an incoming call.
Audio part is not yet supported.
---
v1->v2: Improve commit message
Makefile.tools | 9 +-
client/bluetoothctl-telephony.rst | 95 ++++++
client/main.c | 3 +
client/telephony.c | 528 ++++++++++++++++++++++++++++++
client/telephony.h | 12 +
5 files changed, 644 insertions(+), 3 deletions(-)
create mode 100644 client/bluetoothctl-telephony.rst
create mode 100644 client/telephony.c
create mode 100644 client/telephony.h
diff --git a/Makefile.tools b/Makefile.tools
index 561b03d0b..cc8aab55c 100644
--- a/Makefile.tools
+++ b/Makefile.tools
@@ -15,7 +15,8 @@ client_bluetoothctl_SOURCES = client/main.c \
client/player.h client/player.c \
client/mgmt.h client/mgmt.c \
client/assistant.h client/assistant.c \
- client/hci.h client/hci.c
+ client/hci.h client/hci.c \
+ client/telephony.h client/telephony.c
client_bluetoothctl_LDADD = lib/libbluetooth-internal.la \
gdbus/libgdbus-internal.la src/libshared-glib.la \
$(GLIB_LIBS) $(DBUS_LIBS) -lreadline
@@ -361,7 +362,8 @@ man_MANS += tools/rctest.1 tools/l2ping.1 tools/btattach.1 tools/isotest.1 \
client/bluetoothctl-advertise.1 client/bluetoothctl-endpoint.1 \
client/bluetoothctl-gatt.1 client/bluetoothctl-player.1 \
client/bluetoothctl-scan.1 client/bluetoothctl-transport.1 \
- client/bluetoothctl-assistant.1 client/bluetoothctl-hci.1
+ client/bluetoothctl-assistant.1 client/bluetoothctl-hci.1 \
+ client/bluetoothctl-telephony.1
endif
@@ -494,7 +496,8 @@ manual_pages += tools/hciattach.1 tools/hciconfig.1 \
client/bluetoothctl-scan.1 \
client/bluetoothctl-transport.1 \
client/bluetoothctl-assistant.1 \
- client/bluetoothctl-hci.1
+ client/bluetoothctl-hci.1 \
+ client/bluetoothctl-telephony.1
if HID2HCI
udevdir = $(UDEV_DIR)
diff --git a/client/bluetoothctl-telephony.rst b/client/bluetoothctl-telephony.rst
new file mode 100644
index 000000000..a6870e781
--- /dev/null
+++ b/client/bluetoothctl-telephony.rst
@@ -0,0 +1,95 @@
+======================
+bluetoothctl-telephony
+======================
+
+-----------------
+Telephony Submenu
+-----------------
+
+:Version: BlueZ
+:Copyright: Free use of this software is granted under the terms of the GNU
+ Lesser General Public Licenses (LGPL).
+:Date: May 2025
+:Manual section: 1
+:Manual group: Linux System Administration
+
+SYNOPSIS
+========
+
+**bluetoothctl** [--options] [telephony.commands]
+
+Telephony Commands
+==================
+
+list
+----
+
+List available audio gateways.
+
+:Usage: **> list**
+
+show
+----
+
+Show audio gateway information.
+
+:Usage: **> show [audiogw]**
+
+select
+------
+
+Select default audio gateway.
+
+:Usage: **> select <audiogw>**
+
+dial
+----
+
+Dial number.
+
+:Usage: **> dial <number> [audiogw]**
+
+hangup-all
+----------
+
+Hangup all calls.
+
+:Usage: **> hangup-all**
+
+list-calls
+----------
+
+List available calls.
+
+:Usage: **> list-calls**
+
+show-call
+---------
+
+Show call information.
+
+:Usage: **> show-call <call>**
+
+answer
+------
+
+Answer call.
+
+:Usage: **> answer <call>**
+
+hangup
+------
+
+Hangup call.
+
+:Usage: **> hangup <call>**
+
+RESOURCES
+=========
+
+http://www.bluez.org
+
+REPORTING BUGS
+==============
+
+linux-bluetooth@vger.kernel.org
diff --git a/client/main.c b/client/main.c
index 2203a2a2b..cb016a579 100644
--- a/client/main.c
+++ b/client/main.c
@@ -38,6 +38,7 @@
#include "mgmt.h"
#include "assistant.h"
#include "hci.h"
+#include "telephony.h"
/* String display constants */
#define COLORED_NEW COLOR_GREEN "NEW" COLOR_OFF
@@ -3922,6 +3923,7 @@ int main(int argc, char *argv[])
mgmt_add_submenu();
assistant_add_submenu();
hci_add_submenu();
+ telephony_add_submenu();
bt_shell_set_prompt(PROMPT_OFF, NULL);
bt_shell_handle_non_interactive_help();
@@ -3962,6 +3964,7 @@ int main(int argc, char *argv[])
mgmt_remove_submenu();
assistant_remove_submenu();
hci_remove_submenu();
+ telephony_remove_submenu();
g_dbus_client_unref(client);
diff --git a/client/telephony.c b/client/telephony.c
new file mode 100644
index 000000000..cc92fe33a
--- /dev/null
+++ b/client/telephony.c
@@ -0,0 +1,528 @@
+// 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);
+}
diff --git a/client/telephony.h b/client/telephony.h
new file mode 100644
index 000000000..a869da240
--- /dev/null
+++ b/client/telephony.h
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright © 2025 Collabora Ltd.
+ *
+ *
+ */
+
+void telephony_add_submenu(void);
+void telephony_remove_submenu(void);
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* RE: [BlueZ,v2,1/5] doc: Add new telephony related profiles interfaces
2025-12-11 18:34 [PATCH BlueZ v2 1/5] doc: Add new telephony related profiles interfaces Frédéric Danis
` (3 preceding siblings ...)
2025-12-11 18:34 ` [PATCH BlueZ v2 5/5] client/telephony: Add new submenu Frédéric Danis
@ 2025-12-11 19:37 ` bluez.test.bot
2025-12-12 15:30 ` [PATCH BlueZ v2 1/5] " patchwork-bot+bluetooth
5 siblings, 0 replies; 7+ messages in thread
From: bluez.test.bot @ 2025-12-11 19:37 UTC (permalink / raw)
To: linux-bluetooth, frederic.danis
[-- Attachment #1: Type: text/plain, Size: 1262 bytes --]
This is automated email and please do not reply to this email!
Dear submitter,
Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=1032412
---Test result---
Test Summary:
CheckPatch PENDING 0.33 seconds
GitLint PENDING 0.36 seconds
BuildEll PASS 20.34 seconds
BluezMake PASS 654.40 seconds
MakeCheck PASS 22.25 seconds
MakeDistcheck PASS 245.83 seconds
CheckValgrind PASS 306.49 seconds
CheckSmatch PASS 356.69 seconds
bluezmakeextell PASS 186.03 seconds
IncrementalBuild PENDING 0.43 seconds
ScanBuild PASS 1055.47 seconds
Details
##############################
Test: CheckPatch - PENDING
Desc: Run checkpatch.pl script
Output:
##############################
Test: GitLint - PENDING
Desc: Run gitlint
Output:
##############################
Test: IncrementalBuild - PENDING
Desc: Incremental build with the patches in the series
Output:
---
Regards,
Linux Bluetooth
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH BlueZ v2 1/5] doc: Add new telephony related profiles interfaces
2025-12-11 18:34 [PATCH BlueZ v2 1/5] doc: Add new telephony related profiles interfaces Frédéric Danis
` (4 preceding siblings ...)
2025-12-11 19:37 ` [BlueZ,v2,1/5] doc: Add new telephony related profiles interfaces bluez.test.bot
@ 2025-12-12 15:30 ` patchwork-bot+bluetooth
5 siblings, 0 replies; 7+ messages in thread
From: patchwork-bot+bluetooth @ 2025-12-12 15:30 UTC (permalink / raw)
To: =?utf-8?b?RnLDqWTDqXJpYyBEYW5pcyA8ZnJlZGVyaWMuZGFuaXNAY29sbGFib3JhLmNvbT4=?=
Cc: linux-bluetooth
Hello:
This series was applied to bluetooth/bluez.git (master)
by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>:
On Thu, 11 Dec 2025 19:34:25 +0100 you wrote:
> These are interfaces are meant to be generic to the telephony related
> "headset" profiles like HSP HS, HFP HF, and CCP.
> ---
> Makefile.am | 4 +
> doc/org.bluez.Call.rst | 140 ++++++++++++++++++++++
> doc/org.bluez.Telephony.rst | 225 ++++++++++++++++++++++++++++++++++++
> 3 files changed, 369 insertions(+)
> create mode 100644 doc/org.bluez.Call.rst
> create mode 100644 doc/org.bluez.Telephony.rst
Here is the summary with links:
- [BlueZ,v2,1/5] doc: Add new telephony related profiles interfaces
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=270b899e9630
- [BlueZ,v2,2/5] audio/telephony: Add shared interfaces implementation
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=7204618e59ad
- [BlueZ,v2,3/5] audio/hfp-hf: Add skeleton for HFP profile
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=cfc372d4f21b
- [BlueZ,v2,4/5] audio/hfp-hf: Add HFP SLC connection and event support
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=5c7bb3096021
- [BlueZ,v2,5/5] client/telephony: Add new submenu
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=b3677421feee
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2025-12-12 15:33 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-12-11 18:34 [PATCH BlueZ v2 1/5] doc: Add new telephony related profiles interfaces Frédéric Danis
2025-12-11 18:34 ` [PATCH BlueZ v2 2/5] audio/telephony: Add shared interfaces implementation Frédéric Danis
2025-12-11 18:34 ` [PATCH BlueZ v2 3/5] audio/hfp-hf: Add skeleton for HFP profile Frédéric Danis
2025-12-11 18:34 ` [PATCH BlueZ v2 4/5] audio/hfp-hf: Add HFP SLC connection and event support Frédéric Danis
2025-12-11 18:34 ` [PATCH BlueZ v2 5/5] client/telephony: Add new submenu Frédéric Danis
2025-12-11 19:37 ` [BlueZ,v2,1/5] doc: Add new telephony related profiles interfaces bluez.test.bot
2025-12-12 15:30 ` [PATCH BlueZ v2 1/5] " patchwork-bot+bluetooth
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).