Skip to content
Commits on Source (11)
ver 42.3:
This version adds a new API for more precise adapter power state, and fixes
a number of small UI problems in bluetooth-sendto.
ver 42.2:
This version fixes duplicate devices appearing when bluetoothd restarts, as well
as the discovery not being updated correctly in that same situation.
......
......@@ -54,11 +54,19 @@
#define BLUEZ_ADAPTER_INTERFACE "org.bluez.Adapter1"
#define BLUEZ_DEVICE_INTERFACE "org.bluez.Device1"
/* Subset of BluetoothAdapterState */
typedef enum {
POWER_STATE_REQUEST_NONE = 0,
POWER_STATE_REQUEST_ON,
POWER_STATE_REQUEST_OFF,
} PowerStateRequest;
struct _BluetoothClient {
GObject parent;
GListStore *list_store;
Adapter1 *default_adapter;
PowerStateRequest power_req;
GDBusObjectManager *manager;
GCancellable *cancellable;
guint num_adapters;
......@@ -75,6 +83,7 @@ enum {
PROP_NUM_ADAPTERS,
PROP_DEFAULT_ADAPTER,
PROP_DEFAULT_ADAPTER_POWERED,
PROP_DEFAULT_ADAPTER_STATE,
PROP_DEFAULT_ADAPTER_SETUP_MODE,
PROP_DEFAULT_ADAPTER_NAME,
PROP_DEFAULT_ADAPTER_ADDRESS
......@@ -409,15 +418,31 @@ adapter_set_powered_cb (GDBusProxy *proxy,
GAsyncResult *res,
gpointer user_data)
{
BluetoothClient *client;
g_autoptr(GError) error = NULL;
g_autoptr(GVariant) ret = NULL;
ret = g_dbus_proxy_call_finish (proxy, res, &error);
if (!ret) {
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
g_debug ("Error setting property 'Powered' on %s: %s (%s, %d)",
g_dbus_proxy_get_object_path (proxy),
error->message, g_quark_to_string (error->domain), error->code);
}
client = user_data;
if (client->default_adapter) {
gboolean powered;
powered = adapter1_get_powered (client->default_adapter);
if ((powered && client->power_req == POWER_STATE_REQUEST_ON) ||
(!powered && client->power_req == POWER_STATE_REQUEST_OFF)) {
/* Only reset if we don't have a conflicting state in progress */
client->power_req = POWER_STATE_REQUEST_NONE;
}
}
g_object_notify (G_OBJECT (client), "default-adapter-state");
}
static void
......@@ -433,6 +458,12 @@ adapter_set_powered (BluetoothClient *client,
return;
}
if ((powered && client->power_req == POWER_STATE_REQUEST_ON) ||
(!powered && client->power_req == POWER_STATE_REQUEST_OFF)) {
g_debug ("Default adapter is already being powered %s", powered ? "on" : "off");
return;
}
if (powered == adapter1_get_powered (client->default_adapter)) {
g_debug ("Default adapter is already %spowered", powered ? "" : "un");
return;
......@@ -442,6 +473,9 @@ adapter_set_powered (BluetoothClient *client,
powered ? "up" : "down",
g_dbus_proxy_get_object_path (G_DBUS_PROXY (client->default_adapter)));
variant = g_variant_new_boolean (powered);
client->power_req = powered ?
POWER_STATE_REQUEST_ON : POWER_STATE_REQUEST_OFF;
g_object_notify (G_OBJECT (client), "default-adapter-state");
g_dbus_proxy_call (G_DBUS_PROXY (client->default_adapter),
"org.freedesktop.DBus.Properties.Set",
g_variant_new ("(ssv)", "org.bluez.Adapter1", "Powered", variant),
......@@ -554,6 +588,7 @@ adapter_notify_cb (Adapter1 *adapter,
g_object_notify (G_OBJECT (client), "default-adapter-setup-mode");
} else if (g_strcmp0 (property, "powered") == 0) {
g_object_notify (G_OBJECT (client), "default-adapter-powered");
g_object_notify (G_OBJECT (client), "default-adapter-state");
}
}
......@@ -570,6 +605,7 @@ notify_default_adapter_props (BluetoothClient *client)
g_object_notify (G_OBJECT (client), "default-adapter");
g_object_notify (G_OBJECT (client), "default-adapter-address");
g_object_notify (G_OBJECT (client), "default-adapter-powered");
g_object_notify (G_OBJECT (client), "default-adapter-state");
g_object_notify (G_OBJECT (client), "default-adapter-setup-mode");
g_object_notify (G_OBJECT (client), "default-adapter-name");
}
......@@ -603,7 +639,6 @@ default_adapter_changed (GDBusObjectManager *manager,
g_list_store_remove_all (client->list_store);
g_debug ("Disabling discovery on old default adapter");
_bluetooth_client_set_default_adapter_discovering (client, FALSE);
g_clear_object (&client->default_adapter);
}
client->default_adapter = ADAPTER1 (g_object_ref (G_OBJECT (adapter)));
......@@ -1210,6 +1245,19 @@ _bluetooth_client_set_default_adapter_discovering (BluetoothClient *client,
}
}
static BluetoothAdapterState
adapter_get_state (BluetoothClient *client)
{
if (!client->default_adapter)
return BLUETOOTH_ADAPTER_STATE_ABSENT;
if (client->power_req == POWER_STATE_REQUEST_NONE)
return adapter1_get_powered (client->default_adapter) ?
BLUETOOTH_ADAPTER_STATE_ON : BLUETOOTH_ADAPTER_STATE_OFF;
return client->power_req == POWER_STATE_REQUEST_ON ?
BLUETOOTH_ADAPTER_STATE_TURNING_ON :
BLUETOOTH_ADAPTER_STATE_TURNING_OFF;
}
static void
bluetooth_client_get_property (GObject *object,
guint property_id,
......@@ -1230,6 +1278,9 @@ bluetooth_client_get_property (GObject *object,
g_value_set_boolean (value, client->default_adapter ?
adapter1_get_powered (client->default_adapter) : FALSE);
break;
case PROP_DEFAULT_ADAPTER_STATE:
g_value_set_enum (value, adapter_get_state (client));
break;
case PROP_DEFAULT_ADAPTER_NAME:
g_value_set_string (value, client->default_adapter ?
adapter1_get_alias (client->default_adapter) : NULL);
......@@ -1363,6 +1414,18 @@ static void bluetooth_client_class_init(BluetoothClientClass *klass)
g_param_spec_boolean ("default-adapter-powered", NULL,
"Whether the default adapter is powered",
FALSE, G_PARAM_READWRITE));
/**
* BluetoothClient:default-adapter-state:
*
* The #BluetoothAdapterState of the default Bluetooth adapter. More precise than
* #BluetoothClient:default-adapter-powered.
*/
g_object_class_install_property (object_class, PROP_DEFAULT_ADAPTER_STATE,
g_param_spec_enum ("default-adapter-state", NULL,
"State of the default adapter",
BLUETOOTH_TYPE_ADAPTER_STATE,
BLUETOOTH_ADAPTER_STATE_ABSENT,
G_PARAM_READABLE));
/**
* BluetoothClient:default-adapter-setup-mode:
*
......@@ -1696,6 +1759,14 @@ disconnect_callback (GDBusProxy *proxy,
* @callback: (scope async): a #GAsyncReadyCallback to call when the connection is complete
* @user_data: the data to pass to callback function
*
* This will start the process of connecting to one of the known-connectable
* services on the device. This means that it could connect to all the audio
* services on a headset, but just to the input service on a keyboard.
*
* Broadly speaking, this will only have an effect on devices with audio and HID
* services, and do nothing if the device doesn't have the "connectable"
* property set.
*
* When the connection operation is finished, @callback will be called. You can
* then call bluetooth_client_connect_service_finish() to get the result of the
* operation.
......
......@@ -112,3 +112,21 @@ typedef enum {
BLUETOOTH_BATTERY_TYPE_PERCENTAGE,
BLUETOOTH_BATTERY_TYPE_COARSE
} BluetoothBatteryType;
/**
* BluetoothAdapterState:
* @BLUETOOTH_ADAPTER_STATE_ABSENT: Bluetooth adapter is missing.
* @BLUETOOTH_ADAPTER_STATE_ON: Bluetooth adapter is on.
* @BLUETOOTH_ADAPTER_STATE_TURNING_ON: Bluetooth adapter is being turned on.
* @BLUETOOTH_ADAPTER_STATE_TURNING_OFF: Bluetooth adapter is being turned off.
* @BLUETOOTH_ADAPTER_STATE_OFF: Bluetooth adapter is off.
*
* A more precise power state for a Bluetooth adapter.
**/
typedef enum {
BLUETOOTH_ADAPTER_STATE_ABSENT = 0,
BLUETOOTH_ADAPTER_STATE_ON,
BLUETOOTH_ADAPTER_STATE_TURNING_ON,
BLUETOOTH_ADAPTER_STATE_TURNING_OFF,
BLUETOOTH_ADAPTER_STATE_OFF,
} BluetoothAdapterState;
......@@ -21,6 +21,8 @@ global:
bluetooth_device_dump;
bluetooth_device_get_object_path;
bluetooth_device_to_string;
bluetooth_battery_type_get_type;
bluetooth_adapter_state_get_type;
bluetooth_agent_get_type;
bluetooth_agent_error_get_type;
bluetooth_agent_new;
......
......@@ -122,7 +122,7 @@ pkg.generate(
)
if enable_gir
gir_sources = sources + headers
gir_sources = sources + headers + enum_sources
gir_incs = [
'Gio-2.0',
......
project(
'gnome-bluetooth', 'c',
version: '42.2',
version: '42.3',
license: 'GPL2+',
default_options: 'buildtype=debugoptimized',
meson_version: '>= 0.58.0',
......@@ -34,9 +34,9 @@ enable_gir = get_option('introspection')
# - If binary compatibility has been broken (eg removed or changed interfaces)
# change to C+1:0:0
# - If the interface is the same as the previous version, change to C:R+1:A
current = 14
current = 15
revision = 0
age = 1
age = 2
lt_soversion = current - age
libversion = '@0@.@1@.@2@'.format(lt_soversion, age, revision)
......
# please keep this list sorted alphabetically
ab
af
an
ar
......
msgid ""
msgstr ""
"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-bluetooth/issues\n"
"POT-Creation-Date: 2022-07-15 20:11+0000\n"
"Last-Translator: Нанба Наала <naala-nanba@rambler.ru>\n"
"Language-Team: Abkhazian <ab@li.org>\n"
"Language: ab\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: lib/bluetooth-pairing-dialog.c:81 lib/bluetooth-pairing-dialog.c:88
#: lib/bluetooth-pairing-dialog.c:102
msgid "Confirm Bluetooth PIN"
msgstr ""
#: lib/bluetooth-pairing-dialog.c:82
#, c-format
msgid "Please confirm the PIN that was entered on “%s”."
msgstr ""
#: lib/bluetooth-pairing-dialog.c:86 lib/bluetooth-pairing-dialog.c:99
#: lib/bluetooth-pairing-dialog.c:147
msgid "Confirm"
msgstr ""
#: lib/bluetooth-pairing-dialog.c:89
#, c-format
msgid ""
"Confirm the Bluetooth PIN for “%s”. This can usually be found in the "
"device’s manual."
msgstr ""
#: lib/bluetooth-pairing-dialog.c:95
#, c-format
msgid "Pairing “%s”"
msgstr ""
#: lib/bluetooth-pairing-dialog.c:103
#, c-format
msgid ""
"Please confirm that the following PIN matches the one displayed on “%s”."
msgstr ""
#: lib/bluetooth-pairing-dialog.c:108
msgid "Bluetooth Pairing Request"
msgstr ""
#: lib/bluetooth-pairing-dialog.c:109
#, c-format
msgid "“%s” wants to pair with this device. Do you want to allow pairing?"
msgstr ""
#: lib/bluetooth-pairing-dialog.c:114
msgid "Confirm Bluetooth Connection"
msgstr ""
#: lib/bluetooth-pairing-dialog.c:115
#, c-format
msgid "“%s” wants to connect with this device. Do you want to allow it?"
msgstr ""
#: lib/bluetooth-pairing-dialog.c:123
#, c-format
msgid "Please enter the following PIN on “%s”."
msgstr ""
#: lib/bluetooth-pairing-dialog.c:126
#, c-format
msgid ""
"Please enter the following PIN on “%s”. Then press “Return” on the keyboard."
msgstr ""
#: lib/bluetooth-pairing-dialog.c:129
msgid ""
"Please move the joystick of your iCade in the following directions. Then "
"press any of the white buttons."
msgstr ""
#: lib/bluetooth-pairing-dialog.c:137
msgid "Allow"
msgstr ""
#: lib/bluetooth-pairing-dialog.c:141
msgid "Dismiss"
msgstr ""
#. Cancel button
#: lib/bluetooth-pairing-dialog.c:151 lib/bluetooth-pairing-dialog.c:304
msgid "Cancel"
msgstr ""
#. OK button
#: lib/bluetooth-pairing-dialog.c:286 lib/bluetooth-settings-obexpush.c:247
msgid "Accept"
msgstr ""
#: lib/bluetooth-settings-row.c:78 lib/bluetooth-settings-row.ui:14
msgid "Not Set Up"
msgstr ""
#: lib/bluetooth-settings-row.c:80
msgid "Connected"
msgstr ""
#: lib/bluetooth-settings-row.c:82
msgid "Disconnected"
msgstr ""
#: lib/bluetooth-settings-widget.c:1138
msgid "Yes"
msgstr "Ааи"
#: lib/bluetooth-settings-widget.c:1138
msgid "No"
msgstr ""
#. translators: first %s is the name of the computer, for example:
#. * Visible as “Bastien Nocera’s Computer” followed by the
#. * location of the Downloads folder.
#: lib/bluetooth-settings-widget.c:1237
#, c-format
msgid ""
"Visible as “%s” and available for Bluetooth file transfers. Transferred "
"files are placed in the <a href=\"%s\">Downloads</a> folder."
msgstr ""
#: lib/bluetooth-settings-widget.c:1313
#, c-format
msgid "Remove “%s” from the list of devices?"
msgstr ""
#: lib/bluetooth-settings-widget.c:1315
msgid ""
"If you remove the device, you will have to set it up again before next use."
msgstr ""
#: lib/bluetooth-settings-widget.c:1318 sendto/main.c:447 sendto/main.c:699
msgid "_Cancel"
msgstr ""
#: lib/bluetooth-settings-widget.c:1319
msgid "_Remove"
msgstr ""
#. Translators: %s is the name of the filename received
#: lib/bluetooth-settings-obexpush.c:145
#, c-format
msgid "You received “%s” via Bluetooth"
msgstr ""
#: lib/bluetooth-settings-obexpush.c:147
msgid "You received a file"
msgstr ""
#: lib/bluetooth-settings-obexpush.c:158
msgid "Open File"
msgstr ""
#: lib/bluetooth-settings-obexpush.c:162
msgid "Open Containing Folder"
msgstr ""
#: lib/bluetooth-settings-obexpush.c:179
msgid "File reception complete"
msgstr ""
#: lib/bluetooth-settings-obexpush.c:234
#, c-format
msgid "Bluetooth file transfer from %s"
msgstr ""
#: lib/bluetooth-settings-obexpush.c:244
msgid "Decline"
msgstr ""
#: lib/bluetooth-utils.c:56
msgid "Phone"
msgstr ""
#: lib/bluetooth-utils.c:58
msgid "Modem"
msgstr ""
#: lib/bluetooth-utils.c:60
msgid "Computer"
msgstr ""
#: lib/bluetooth-utils.c:62
msgid "Network"
msgstr ""
#. translators: a hands-free headset, a combination of a single speaker with a microphone
#: lib/bluetooth-utils.c:65
msgid "Headset"
msgstr ""
#: lib/bluetooth-utils.c:67
msgid "Headphones"
msgstr ""
#: lib/bluetooth-utils.c:69
msgid "Audio device"
msgstr ""
#: lib/bluetooth-utils.c:71
msgid "Keyboard"
msgstr ""
#: lib/bluetooth-utils.c:73
msgid "Mouse"
msgstr ""
#: lib/bluetooth-utils.c:75
msgid "Camera"
msgstr ""
#: lib/bluetooth-utils.c:77
msgid "Printer"
msgstr ""
#: lib/bluetooth-utils.c:79
msgid "Joypad"
msgstr ""
#: lib/bluetooth-utils.c:81
msgid "Tablet"
msgstr ""
#: lib/bluetooth-utils.c:83
msgid "Video device"
msgstr ""
#: lib/bluetooth-utils.c:85
msgid "Remote control"
msgstr ""
#: lib/bluetooth-utils.c:87
msgid "Scanner"
msgstr ""
#: lib/bluetooth-utils.c:89
msgid "Display"
msgstr ""
#: lib/bluetooth-utils.c:91
msgid "Wearable"
msgstr ""
#: lib/bluetooth-utils.c:93
msgid "Toy"
msgstr ""
#: lib/bluetooth-utils.c:95
msgid "Speakers"
msgstr ""
#: lib/bluetooth-utils.c:97
msgid "Unknown"
msgstr ""
#: lib/settings.ui:29
msgid "Connection"
msgstr ""
#: lib/settings.ui:71
msgid "Paired"
msgstr ""
#: lib/settings.ui:94
msgid "Type"
msgstr ""
#: lib/settings.ui:117
msgid "Address"
msgstr "Аҭыӡҭыԥ"
#: lib/settings.ui:143
msgid "_Mouse & Touchpad Settings"
msgstr ""
#: lib/settings.ui:150
msgid "_Sound Settings"
msgstr ""
#: lib/settings.ui:157
msgid "_Keyboard Settings"
msgstr ""
#: lib/settings.ui:164
msgid "Send _Files…"
msgstr ""
#: lib/settings.ui:171
msgid "_Remove Device"
msgstr ""
#: lib/settings.ui:185 lib/settings.ui:200
msgid "Devices"
msgstr ""
#: lib/settings.ui:214
msgid "Searching for devices…"
msgstr ""
#: sendto/bluetooth-sendto.desktop.in.in:3
msgid "Bluetooth Transfer"
msgstr ""
#: sendto/bluetooth-sendto.desktop.in.in:4
msgid "Send files via Bluetooth"
msgstr ""
#: sendto/main.c:118
msgid "An unknown error occurred"
msgstr ""
#: sendto/main.c:131
msgid ""
"Make sure that the remote device is switched on and that it accepts "
"Bluetooth connections"
msgstr ""
#: sendto/main.c:364
#, c-format
msgid "%'d second"
msgid_plural "%'d seconds"
msgstr[0] ""
msgstr[1] ""
#: sendto/main.c:369 sendto/main.c:382
#, c-format
msgid "%'d minute"
msgid_plural "%'d minutes"
msgstr[0] ""
msgstr[1] ""
#: sendto/main.c:380
#, c-format
msgid "%'d hour"
msgid_plural "%'d hours"
msgstr[0] ""
msgstr[1] ""
#: sendto/main.c:390
#, c-format
msgid "approximately %'d hour"
msgid_plural "approximately %'d hours"
msgstr[0] ""
msgstr[1] ""
#: sendto/main.c:403 sendto/main.c:503
msgid "Connecting…"
msgstr ""
#: sendto/main.c:444
msgid "Bluetooth File Transfer"
msgstr ""
#: sendto/main.c:448
msgid "_Retry"
msgstr ""
#: sendto/main.c:469
msgid "From:"
msgstr ""
#: sendto/main.c:485
msgid "To:"
msgstr "аҟны:"
#: sendto/main.c:567
#, c-format
msgid "Sending %s"
msgstr ""
#: sendto/main.c:574 sendto/main.c:623
#, c-format
msgid "Sending file %d of %d"
msgstr ""
#: sendto/main.c:619
#, c-format
msgid "%d kB/s"
msgstr ""
#: sendto/main.c:621
#, c-format
msgid "%d B/s"
msgstr "%d Б/с"
#: sendto/main.c:652
#, c-format
msgid "%u transfer complete"
msgid_plural "%u transfers complete"
msgstr[0] ""
msgstr[1] ""
#: sendto/main.c:659
msgid "_Close"
msgstr ""
#: sendto/main.c:669
msgid "There was an error"
msgstr ""
#: sendto/main.c:693
msgid "Choose files to send"
msgstr ""
#: sendto/main.c:700
msgid "Select"
msgstr ""
#: sendto/main.c:736
msgid "Remote device to use"
msgstr ""
#: sendto/main.c:736
msgid "ADDRESS"
msgstr "Аҭыӡҭыԥ"
#: sendto/main.c:738
msgid "Remote device’s name"
msgstr ""
#: sendto/main.c:738
msgid "NAME"
msgstr ""
......@@ -28,6 +28,7 @@
#include <sys/time.h>
#include <adwaita.h>
#include <glib/gi18n.h>
#include <glib/gstdio.h>
#include <gio/gio.h>
......@@ -450,6 +451,10 @@ static void create_window(void)
gtk_window_set_default_size(GTK_WINDOW(dialog), 400, -1);
vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_widget_set_margin_end(vbox, 12);
gtk_widget_set_margin_start(vbox, 12);
gtk_widget_set_margin_top(vbox, 12);
gtk_widget_set_margin_bottom(vbox, 12);
gtk_box_set_spacing(GTK_BOX(vbox), 6);
gtk_window_set_child (GTK_WINDOW(dialog), vbox);
......@@ -751,6 +756,7 @@ int main(int argc, char *argv[])
error = NULL;
gtk_init();
adw_init();
option_context = g_option_context_new(NULL);
g_option_context_add_main_entries(option_context, options, GETTEXT_PACKAGE);
......
......@@ -4,7 +4,7 @@ executable(
name,
'main.c',
include_directories: top_inc,
dependencies: [libgnome_bluetooth_dep, gtk_dep],
dependencies: [libgnome_bluetooth_dep, gtk_dep, libadwaita_dep],
install: true,
)
......
......@@ -265,26 +265,34 @@ class OopTests(dbusmock.DBusTestCase):
self.assertEqual(dbusprops_bluez.Get('org.bluez.Adapter1', 'Powered'), False)
self.wait_for_mainloop()
self.assertEqual(dbusprops_bluez.Get('org.bluez.Adapter1', 'Powered'), False)
self.assertEqual(self.client.props.default_adapter_state, GnomeBluetoothPriv.AdapterState.OFF)
self.client.props.default_adapter_powered = True
self.assertEqual(self.client.props.default_adapter_state, GnomeBluetoothPriv.AdapterState.TURNING_ON)
self.wait_for_condition(lambda: dbusprops_bluez.Get('org.bluez.Adapter1', 'Powered') == True)
self.assertEqual(self.client.props.num_adapters, 1)
self.assertEqual(dbusprops_bluez.Get('org.bluez.Adapter1', 'Powered'), True)
self.wait_for_mainloop()
self.assertEqual(self.client.props.default_adapter_powered, True)
self.assertEqual(self.client.props.default_adapter_state, GnomeBluetoothPriv.AdapterState.ON)
self.client.props.default_adapter_powered = False
self.wait_for_condition(lambda: dbusprops_bluez.Get('org.bluez.Adapter1', 'Powered') == False)
self.assertEqual(dbusprops_bluez.Get('org.bluez.Adapter1', 'Powered'), False)
self.assertEqual(self.client.props.default_adapter_state, GnomeBluetoothPriv.AdapterState.TURNING_OFF)
self.wait_for_mainloop()
self.assertEqual(self.client.props.default_adapter_powered, False)
self.assertEqual(self.client.props.default_adapter_state, GnomeBluetoothPriv.AdapterState.OFF)
dbusmock_bluez.UpdateProperties('org.bluez.Adapter1', {
'Powered': True,
})
# NOTE: this should be "turning on" when we have bluez API to keep track of it
self.assertEqual(self.client.props.default_adapter_state, GnomeBluetoothPriv.AdapterState.OFF)
self.wait_for_condition(lambda: dbusprops_bluez.Get('org.bluez.Adapter1', 'Powered') == True)
self.assertEqual(dbusprops_bluez.Get('org.bluez.Adapter1', 'Powered'), True)
self.wait_for_mainloop()
self.assertEqual(self.client.props.default_adapter_powered, True)
self.assertEqual(self.client.props.default_adapter_state, GnomeBluetoothPriv.AdapterState.ON)
def _pair_cb(self, client, result, user_data=None):
success, path = client.setup_device_finish(result)
......@@ -426,6 +434,7 @@ class OopTests(dbusmock.DBusTestCase):
self.wait_for_mainloop()
list_store = client.get_devices()
self.wait_for_condition(lambda: list_store.get_n_items() == 3)
self.assertEqual(list_store.get_n_items(), 3)
device = list_store.get_item(0)
......