Hi Arkadiusz, On Mon, Mar 3, 2025 at 2:51 PM Arkadiusz Bokowy <arkadiusz.bokowy@xxxxxxxxx> wrote: > > --- > client/bluetoothctl.rst | 20 +++++++-- Documentation shall be split into its own patch. > client/main.c | 93 +++++++++++++++++++++++++++++++++++------ Same for uuid-helper. > src/uuid-helper.c | 32 +++++++------- > 3 files changed, 114 insertions(+), 31 deletions(-) > > diff --git a/client/bluetoothctl.rst b/client/bluetoothctl.rst > index b6c2efa35..c60bf719f 100644 > --- a/client/bluetoothctl.rst > +++ b/client/bluetoothctl.rst > @@ -9,7 +9,7 @@ Bluetooth Control Command Line Tool > :Version: BlueZ > :Copyright: Free use of this software is granted under the terms of the GNU > Lesser General Public Licenses (LGPL). > -:Date: November 2022 > +:Date: March 2024 > :Manual section: 1 > :Manual group: Linux System Administration > > @@ -262,6 +262,13 @@ Connect device. > > This will initiate a connection to a device. > > +By default this commands tries to connect all the profiles the remote device > +supports and have been flagged as auto-connectable. In case when the UUID of > +the remote service is given only that service will be connected. The UUID can > +be either a short form (16-bit UUID) or a long form (128-bit UUID). There are > +also some special values for well-known profiles like "a2dp-sink", > +"a2dp-source", "hfp-hf", "hfp-ag", "ftp" or "spp". > + > To connect with an LE device the controller must have an active scan report of > the device it wants to connect to. > > @@ -269,17 +276,24 @@ If no advertising report is received before the timeout a > le-connection-abort-by-local error will be issued. In that case either try > again to connect assuming the device is advertising. > > -:Usage: **> connect <dev>** > +:Usage: **> connect <dev> [uuid]** > +:Example: **> connect 1C:48:F9:9D:81:5C hsp-hs** > +:Example: **> connect 1C:48:F9:9D:81:5C 00001108-0000-1000-8000-00805f9b34fb** > +:Example: **> connect 1C:48:F9:9D:81:5C 0x1108** > > disconnect > ---------- > > Disconnect device. > > +By default this commands disconnects all profiles and then terminates the > +connection. In case when the UUID of the remote service is given only that > +service will be disconnected. > + > For LE when disconnecting from an active connection the device address is not > needed. > > -:Usage: **> disconnect <dev>** > +:Usage: **> disconnect <dev> [uuid]** > > info > ---- > diff --git a/client/main.c b/client/main.c > index 6b938da3f..3f2bfcf6b 100644 > --- a/client/main.c > +++ b/client/main.c > @@ -1981,13 +1981,44 @@ static void cmd_remove(int argc, char *argv[]) > remove_device(proxy); > } > > +struct connection_data { > + GDBusProxy *proxy; > + char *uuid; > +}; > + > +static void connection_setup(DBusMessageIter *iter, void *user_data) > +{ > + struct connection_data *data = user_data; > + > + if (!data->uuid) > + return; > + > + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &data->uuid); > +} > + > +static void format_connection_profile(char *output, size_t size, > + const char *uuid) > +{ > + const char *text; > + > + text = bt_uuidstr_to_str(uuid); > + if (!text) > + text = uuid; > + > + snprintf(output, size, " profile \"%s\"", text); > +} > + > static void connect_reply(DBusMessage *message, void *user_data) > { > - GDBusProxy *proxy = user_data; > + struct connection_data *data = user_data; > + GDBusProxy *proxy = data->proxy; > DBusError error; > > dbus_error_init(&error); > > + g_free(data->uuid); > + g_free(data); > + > if (dbus_set_error_from_message(&error, message) == TRUE) { > bt_shell_printf("Failed to connect: %s %s\n", error.name, > error.message); > @@ -2003,6 +2034,9 @@ static void connect_reply(DBusMessage *message, void *user_data) > > static void cmd_connect(int argc, char *argv[]) > { > + struct connection_data *data; > + const char *method = "Connect"; > + char profile[128] = ""; > GDBusProxy *proxy; > > if (check_default_ctrl() == FALSE) > @@ -2014,31 +2048,49 @@ static void cmd_connect(int argc, char *argv[]) > return bt_shell_noninteractive_quit(EXIT_FAILURE); > } > > - if (g_dbus_proxy_method_call(proxy, "Connect", NULL, connect_reply, > - proxy, NULL) == FALSE) { > + data = new0(struct connection_data, 1); > + data->proxy = proxy; > + > + if (argc == 3) { > + method = "ConnectProfile"; > + data->uuid = g_strdup(argv[2]); > + format_connection_profile(profile, sizeof(profile), argv[2]); > + } > + > + if (g_dbus_proxy_method_call(proxy, method, connection_setup, > + connect_reply, data, NULL) == FALSE) { > bt_shell_printf("Failed to connect\n"); > return bt_shell_noninteractive_quit(EXIT_FAILURE); > } > > - bt_shell_printf("Attempting to connect to %s\n", argv[1]); > + bt_shell_printf("Attempting to connect%s to %s\n", profile, argv[1]); > } > > static void disconn_reply(DBusMessage *message, void *user_data) > { > - GDBusProxy *proxy = user_data; > + struct connection_data *data = user_data; > + const bool profile_disconnected = data->uuid != NULL; > + GDBusProxy *proxy = data->proxy; > DBusError error; > > dbus_error_init(&error); > > + g_free(data->uuid); > + g_free(data); > + > if (dbus_set_error_from_message(&error, message) == TRUE) { > bt_shell_printf("Failed to disconnect: %s\n", error.name); > dbus_error_free(&error); > return bt_shell_noninteractive_quit(EXIT_FAILURE); > } > > - bt_shell_printf("Successful disconnected\n"); > + bt_shell_printf("Disconnection successful\n"); > > - if (proxy == default_dev) > + /* If only a single profile was disconnected, the device itself might > + * still be connected. In that case, let the property change handler > + * take care of setting the default device to NULL. > + */ > + if (proxy == default_dev && !profile_disconnected) > set_default_device(NULL, NULL); > > return bt_shell_noninteractive_quit(EXIT_SUCCESS); > @@ -2046,19 +2098,31 @@ static void disconn_reply(DBusMessage *message, void *user_data) > > static void cmd_disconn(int argc, char *argv[]) > { > + struct connection_data *data; > + const char *method = "Disconnect"; > + char profile[128] = ""; > GDBusProxy *proxy; > > proxy = find_device(argc, argv); > if (!proxy) > return bt_shell_noninteractive_quit(EXIT_FAILURE); > > - if (g_dbus_proxy_method_call(proxy, "Disconnect", NULL, disconn_reply, > - proxy, NULL) == FALSE) { > + data = new0(struct connection_data, 1); > + data->proxy = proxy; > + > + if (argc == 3) { > + method = "DisconnectProfile"; > + data->uuid = g_strdup(argv[2]); > + format_connection_profile(profile, sizeof(profile), argv[2]); > + } > + > + if (g_dbus_proxy_method_call(proxy, method, connection_setup, > + disconn_reply, data, NULL) == FALSE) { > bt_shell_printf("Failed to disconnect\n"); > return bt_shell_noninteractive_quit(EXIT_FAILURE); > } > > - bt_shell_printf("Attempting to disconnect from %s\n", > + bt_shell_printf("Attempting to disconnect%s from %s\n", profile, > proxy_address(proxy)); > } > > @@ -3253,10 +3317,13 @@ static const struct bt_shell_menu main_menu = { > dev_generator }, > { "remove", "<dev>", cmd_remove, "Remove device", > dev_generator }, > - { "connect", "<dev>", cmd_connect, "Connect device", > - dev_generator }, > - { "disconnect", "[dev]", cmd_disconn, "Disconnect device", > + { "connect", "<dev> [uuid]", cmd_connect, > + "Connect a device and all its profiles or " > + "optionally connect a single profile only", > dev_generator }, > + { "disconnect", "[dev] [uuid]", cmd_disconn, > + "Disconnect a device or optionally disconnect " > + "a single profile only", dev_generator }, > { "wake", "[dev] [on/off]", cmd_wake, "Get/Set wake support", > dev_generator }, > { } }, > diff --git a/src/uuid-helper.c b/src/uuid-helper.c > index f32ee0a85..640592fd2 100644 > --- a/src/uuid-helper.c > +++ b/src/uuid-helper.c > @@ -101,29 +101,31 @@ static struct { > const char *name; > uint16_t class; > } bt_services[] = { > - { "pbap", PBAP_SVCLASS_ID }, > - { "sap", SAP_SVCLASS_ID }, > - { "ftp", OBEX_FILETRANS_SVCLASS_ID }, > - { "bpp", BASIC_PRINTING_SVCLASS_ID }, > + { "a2dp-sink", AUDIO_SINK_SVCLASS_ID }, > + { "a2dp-source",AUDIO_SOURCE_SVCLASS_ID }, > { "bip", IMAGING_SVCLASS_ID }, > - { "synch", IRMC_SYNC_SVCLASS_ID }, > + { "bpp", BASIC_PRINTING_SVCLASS_ID }, > { "dun", DIALUP_NET_SVCLASS_ID }, > - { "opp", OBEX_OBJPUSH_SVCLASS_ID }, > { "fax", FAX_SVCLASS_ID }, > - { "spp", SERIAL_PORT_SVCLASS_ID }, > - { "hsp", HEADSET_SVCLASS_ID }, > - { "hsp-hs", HEADSET_SVCLASS_ID }, > - { "hsp-ag", HEADSET_AGW_SVCLASS_ID }, > + { "ftp", OBEX_FILETRANS_SVCLASS_ID }, > + { "gnss", GNSS_SERVER_SVCLASS_ID }, > { "hfp", HANDSFREE_SVCLASS_ID }, > - { "hfp-hf", HANDSFREE_SVCLASS_ID }, > { "hfp-ag", HANDSFREE_AGW_SVCLASS_ID }, > - { "pbap-pce", PBAP_PCE_SVCLASS_ID }, > - { "pbap-pse", PBAP_PSE_SVCLASS_ID }, > - { "map-mse", MAP_MSE_SVCLASS_ID }, > + { "hfp-hf", HANDSFREE_SVCLASS_ID }, > + { "hsp", HEADSET_SVCLASS_ID }, > + { "hsp-ag", HEADSET_AGW_SVCLASS_ID }, > + { "hsp-hs", HEADSET_SVCLASS_ID }, > { "map-mas", MAP_MSE_SVCLASS_ID }, > { "map-mce", MAP_MCE_SVCLASS_ID }, > { "map-mns", MAP_MCE_SVCLASS_ID }, > - { "gnss", GNSS_SERVER_SVCLASS_ID }, > + { "map-mse", MAP_MSE_SVCLASS_ID }, > + { "opp", OBEX_OBJPUSH_SVCLASS_ID }, > + { "pbap", PBAP_SVCLASS_ID }, > + { "pbap-pce", PBAP_PCE_SVCLASS_ID }, > + { "pbap-pse", PBAP_PSE_SVCLASS_ID }, > + { "sap", SAP_SVCLASS_ID }, > + { "spp", SERIAL_PORT_SVCLASS_ID }, > + { "synch", IRMC_SYNC_SVCLASS_ID }, > { } These may need to go in a separate patch. > }; > > -- > 2.39.5 > -- Luiz Augusto von Dentz