0

Reland "Add wrapper around GDBusConnection."

This is a reland of commit 50d8e75ae0

Fixes GN error when building on Linux with the remoting host disabled.

Original change's description:
> Add wrapper around GDBusConnection.
>
> Allows using standard Chromium sequenced callbacks with weak pointers instead of having to worry about GLib thread contexts and GCancellables. Also supports providing call specifications for type-checked calls.
>
> For unit testing, this reuses the D-Bus test service in //dbus, which is pulled out into its own source set so it can be included independently of dbus_test_server.
>
> Change-Id: Ib4b87ab3aec22b1fb62b32178461e4b9dbd6b70a
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5809854
> Reviewed-by: Ryo Hashimoto <hashimoto@chromium.org>
> Reviewed-by: Lambros Lambrou <lambroslambrou@chromium.org>
> Commit-Queue: Erik Jensen <rkjnsn@chromium.org>
> Reviewed-by: Thomas Anderson <thomasanderson@chromium.org>
> Cr-Commit-Position: refs/heads/main@{#1368809}

Change-Id: I452360f89d1e039687769e8d450dab4340c8c200
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5935954
Reviewed-by: Thomas Anderson <thomasanderson@chromium.org>
Commit-Queue: Erik Jensen <rkjnsn@chromium.org>
Reviewed-by: Lambros Lambrou <lambroslambrou@chromium.org>
Reviewed-by: Ryo Hashimoto <hashimoto@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1369666}
This commit is contained in:
Erik Jensen
2024-10-16 22:20:33 +00:00
committed by Chromium LUCI CQ
parent 3e4d95b8be
commit 0cb26c85ee
15 changed files with 1493 additions and 5 deletions

@ -109,10 +109,10 @@ test("dbus_unittests") {
configs += [ "//build/config/linux/dbus" ]
}
executable("dbus_test_server") {
source_set("dbus_test_service") {
testonly = true
sources = [
"test_server.cc",
"test_service.cc",
"test_service.h",
]
@ -125,3 +125,17 @@ executable("dbus_test_server") {
configs += [ "//build/config/linux/dbus" ]
}
executable("dbus_test_server") {
testonly = true
sources = [ "test_server.cc" ]
deps = [
":dbus",
":dbus_test_service",
"//base",
"//base/test:test_support",
]
configs += [ "//build/config/linux/dbus" ]
}

@ -81,6 +81,10 @@ bool TestService::HasDBusThread() {
return bus_->HasDBusThread();
}
std::string TestService::GetConnectionName() {
return bus_->GetConnectionName();
}
void TestService::ShutdownAndBlockInternal() {
if (HasDBusThread())
bus_->ShutdownOnDBusThreadAndBlock();

@ -65,6 +65,9 @@ class TestService : public base::Thread {
// Returns true if the bus has the D-Bus thread.
bool HasDBusThread();
// Returns the unique name of the connection to D-Bus.
std::string GetConnectionName();
// Sends "Test" signal with the given message from the exported object.
void SendTestSignal(const std::string& message);

@ -47,6 +47,10 @@ group("remoting_all") {
"//remoting/host/linux:remoting_user_session",
]
}
if (use_glib) {
deps += [ "//remoting/host/linux/dbus_interfaces:gen_dbus_interface" ]
}
}
if (enable_me2me_host) {

@ -786,6 +786,7 @@ if (is_linux && (ozone_platform_x11 || remoting_use_x11)) {
"//build/config/linux/gtk",
"//remoting/base:logging",
"//remoting/host/linux:x11",
"//remoting/host/linux/dbus_interfaces:interface_headers",
"//third_party/webrtc_overrides:webrtc_component",
"//ui/base",
"//ui/base/x",

@ -98,7 +98,7 @@ if (enable_me2me_host) {
}
}
source_set("utils") {
source_set("gvariant") {
sources = [
"gvariant_ref.cc",
"gvariant_ref.h",
@ -108,6 +108,19 @@ source_set("utils") {
deps = [ "//base" ]
}
source_set("utils") {
sources = [
"gdbus_connection_ref.cc",
"gdbus_connection_ref.h",
]
public_deps = [ ":gvariant" ]
deps = [
"dbus_interfaces:interface_headers",
"//base",
"//ui/base",
]
}
if (is_linux) {
source_set("wayland") {
sources = [
@ -162,6 +175,7 @@ source_set("unit_tests") {
sources = [
"audio_pipe_reader_unittest.cc",
"certificate_watcher_unittest.cc",
"gdbus_connection_ref_unittest.cc",
"gnome_display_config_dbus_client_unittest.cc",
"gvariant_ref_unittest.cc",
"gvariant_type_unittest.cc",
@ -180,8 +194,13 @@ source_set("unit_tests") {
configs += [ "//remoting/build/config:version" ]
# Needed for the dbus test service
configs += [ "//build/config/linux/dbus" ]
deps = [
":utils",
"dbus_interfaces:interface_headers",
"//dbus:dbus_test_service",
"//remoting/host:common",
"//remoting/host:test_support",
"//remoting/host:x11_display_utils",

@ -1,3 +1,9 @@
include_rules = [
"+third_party/libei/cipd",
]
specific_include_rules = {
"gdbus_connection_ref_unittest\.cc": [
"+dbus",
],
}

@ -0,0 +1,17 @@
# Copyright 2024 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
executable("gen_dbus_interface") {
sources = [ "gen_dbus_interface.cc" ]
deps = [ "//base" ]
configs += [ "//build/linux:gio_config" ]
}
source_set("interface_headers") {
sources = [
"org_chromium_TestInterface.h",
"org_freedesktop_DBus_Properties.h",
]
deps = [ "//remoting/host/linux:gvariant" ]
}

@ -0,0 +1,200 @@
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <gio/gio.h>
#include <glib.h>
#include <fstream>
#include <iostream>
#include <ostream>
#include <string>
#include <string_view>
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/strings/string_util.h"
// This utility handles conversion from an XML-format D-Bus interface definition
// to a header file containing method, property, and signal spec classes for use
// with GDBusConnectionRef. It should be run from the src/ directory with the
// output specified with a relative path. (Otherwise the generated header guard
// will be incorrect.) Sample usage:
//
// gen_dbus_interface --input=/path/to/com.example.Interface.xml \
// --output=remoting/host/linux/dbus_interfaces/com_example_Interface.h
//
// Typically, the XML interface definition will come from one of three sources:
//
// 1. Installed by service: Services providing public D-Bus APIs may install
// interface definitions in /usr/share/dbus-1/interfaces/.
// 2. From the service's source tree: Even if not installed, services may
// include an interface XML file in their source tree.
// 3. Via D-Bus introspection: If the service supports introspection, an XML
// interface definition can be obtained via busctl:
//
// busctl --user introspect --xml-interface org.foo.Bar /org/foo/Bar
//
// The output will include all interfaces implemented by the `/org/foo/Bar`
// object exported by the service at `org.foo.Bar`. Edit it to include only
// the interface of interest and feed the result to this utility.
//
// After generation, the result should be formatted and an appropriate copyright
// header added.
//
// If no XML interface definition is available, a header file can be created
// manually following the style of the existing header files.
// Converts a relative path from src/ to an appropriate header guard.
std::string HeaderGuard(const base::FilePath& header) {
// For now assumes command is being run from src/ and header is a relative
// path from there. Can be made smarter if needed.
std::string result = base::ToUpperASCII(header.MaybeAsASCII());
base::ReplaceChars(result, "/.", "_", &result);
return result + "_";
}
// Converts a D-Bus interface name into a valid C++ namespace identifier.
std::string Namespace(std::string_view interface) {
std::string result;
base::ReplaceChars(interface, ".", "_", &result);
return result;
}
// Writes a tuple of parameters, with each parameter on its own line followed by
// a line comment giving the parameter name.
void WriteParameters(std::ostream& output, GDBusArgInfo** args) {
if (args == nullptr || *args == nullptr) {
output << " \"()\"" << std::endl;
return;
}
output << " \"(\"" << std::endl;
for (GDBusArgInfo** arg = args; *arg != nullptr; ++arg) {
output << " \"" << (**arg).signature << "\" // " << (**arg).name
<< std::endl;
}
output << " \")\"" << std::endl;
}
int main(int argc, char* argv[]) {
base::CommandLine::Init(argc, argv);
base::FilePath input_path =
base::CommandLine::ForCurrentProcess()->GetSwitchValuePath("input");
base::FilePath output_path =
base::CommandLine::ForCurrentProcess()->GetSwitchValuePath("output");
if (input_path.empty() || output_path.empty()) {
std::cerr << "Usage: gen_dbus_interface --input=com.example.Interface.xml "
"--output=remoting/host/linux/dbus_interfaces/"
"com_example_Interface.h"
<< std::endl;
return 1;
}
std::string header_guard = HeaderGuard(output_path);
if (header_guard.empty()) {
std::cerr << "Output name is not ASCII" << std::endl;
return 1;
}
std::string xml;
if (!base::ReadFileToString(input_path, &xml)) {
std::cerr << "Failed to read input file.";
}
GError* error = nullptr;
GDBusNodeInfo* node = g_dbus_node_info_new_for_xml(xml.c_str(), &error);
if (error) {
std::cerr << "Error parsing xml: " << error->message << std::endl;
g_error_free(error);
return 1;
}
std::ofstream output(output_path.value());
if (!output) {
std::cerr << "Failed to open output file for writing." << std::endl;
g_dbus_node_info_unref(node);
return 1;
}
output << "// This file was generated from " << input_path.BaseName().value()
<< std::endl
<< std::endl;
output << "#ifndef " << header_guard << std::endl;
output << "#define " << header_guard << std::endl << std::endl;
output << "#include \"remoting/host/linux/gvariant_type.h\"" << std::endl
<< std::endl;
output << "namespace remoting {" << std::endl << std::endl;
for (GDBusInterfaceInfo** interface = node->interfaces;
interface != nullptr && *interface != nullptr; ++interface) {
std::string namespace_name = Namespace((**interface).name);
output << "namespace " << namespace_name << " {" << std::endl << std::endl;
for (GDBusMethodInfo** method = (**interface).methods;
method != nullptr && *method != nullptr; ++method) {
output << "// method" << std::endl;
output << "struct " << (**method).name << " {" << std::endl;
output << " static constexpr char kInterfaceName[] = \""
<< (**interface).name << "\";" << std::endl;
output << " static constexpr char kMethodName[] = \"" << (**method).name
<< "\";" << std::endl;
output << " static constexpr gvariant::Type kInType{" << std::endl;
WriteParameters(output, (**method).in_args);
output << " };" << std::endl;
output << " static constexpr gvariant::Type kOutType{" << std::endl;
WriteParameters(output, (**method).out_args);
output << " };" << std::endl;
output << "};" << std::endl << std::endl;
}
for (GDBusPropertyInfo** property = (**interface).properties;
property != nullptr && *property != nullptr; ++property) {
output << "// property" << std::endl;
output << "struct " << (**property).name << " {" << std::endl;
output << " static constexpr char kInterfaceName[] = \""
<< (**interface).name << "\";" << std::endl;
output << " static constexpr char kPropertyName[] = \""
<< (**property).name << "\";" << std::endl;
output << " static constexpr gvariant::Type kType{\""
<< (**property).signature << "\"};" << std::endl;
output << " static constexpr bool kReadable = "
<< ((**property).flags & G_DBUS_PROPERTY_INFO_FLAGS_READABLE
? "true;"
: "false;")
<< std::endl;
output << " static constexpr bool kWritable = "
<< ((**property).flags & G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE
? "true;"
: "false;")
<< std::endl;
output << "};" << std::endl << std::endl;
}
for (GDBusSignalInfo** signal = (**interface).signals;
signal != nullptr && *signal != nullptr; ++signal) {
output << "// signal" << std::endl;
output << "struct " << (**signal).name << " {" << std::endl;
output << " static constexpr char kInterfaceName[] = \""
<< (**interface).name << "\";" << std::endl;
output << " static constexpr char kSignalName[] = \"" << (**signal).name
<< "\";" << std::endl;
output << " static constexpr gvariant::Type kType{" << std::endl;
WriteParameters(output, (**signal).args);
output << " };" << std::endl;
output << "};" << std::endl << std::endl;
}
output << "} // namespace " << namespace_name << std::endl << std::endl;
}
output << "} // namespace remoting" << std::endl << std::endl;
output << "#endif // " << header_guard << std::endl;
g_dbus_node_info_unref(node);
}

@ -0,0 +1,132 @@
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_HOST_LINUX_DBUS_INTERFACES_ORG_CHROMIUM_TESTINTERFACE_H_
#define REMOTING_HOST_LINUX_DBUS_INTERFACES_ORG_CHROMIUM_TESTINTERFACE_H_
#include "remoting/host/linux/gvariant_type.h"
namespace remoting::org_chromium_TestInterface {
// method
struct Echo {
static constexpr char kInterfaceName[] = "org.chromium.TestInterface";
static constexpr char kMethodName[] = "Echo";
static constexpr gvariant::Type kInType{
"("
"s" // text_message
")"};
static constexpr gvariant::Type kOutType{
"("
"s" // text_message
")"};
};
// method
struct SlowEcho {
static constexpr char kInterfaceName[] = "org.chromium.TestInterface";
static constexpr char kMethodName[] = "SlowEcho";
static constexpr gvariant::Type kInType{
"("
"s" // text_message
")"};
static constexpr gvariant::Type kOutType{
"("
"s" // text_message
")"};
};
// method
struct AsyncEcho {
static constexpr char kInterfaceName[] = "org.chromium.TestInterface";
static constexpr char kMethodName[] = "AsyncEcho";
static constexpr gvariant::Type kInType{
"("
"s" // text_message
")"};
static constexpr gvariant::Type kOutType{
"("
"s" // text_message
")"};
};
// method
// Always fails
struct BrokenMethod {
static constexpr char kInterfaceName[] = "org.chromium.TestInterface";
static constexpr char kMethodName[] = "BrokenMethod";
static constexpr gvariant::Type kInType{"r"};
static constexpr gvariant::Type kOutType{"()"};
};
// method
struct PerformAction {
static constexpr char kInterfaceName[] = "org.chromium.TestInterface";
static constexpr char kMethodName[] = "PerformAction";
static constexpr gvariant::Type kInType{
"("
"s" // action
"o" // object_path
")"};
static constexpr gvariant::Type kOutType{"()"};
};
// property
struct Name {
static constexpr char kInterfaceName[] = "org.chromium.TestInterface";
static constexpr char kPropertyName[] = "Name";
static constexpr gvariant::Type kType{"s"};
static constexpr bool kReadable = true;
static constexpr bool kWritable = true;
};
// property
struct Version {
static constexpr char kInterfaceName[] = "org.chromium.TestInterface";
static constexpr char kPropertyName[] = "Version";
static constexpr gvariant::Type kType{"n"};
static constexpr bool kReadable = true;
static constexpr bool kWritable = false;
};
// property
struct Methods {
static constexpr char kInterfaceName[] = "org.chromium.TestInterface";
static constexpr char kPropertyName[] = "Methods";
static constexpr gvariant::Type kType{"as"};
static constexpr bool kReadable = true;
static constexpr bool kWritable = false;
};
// property
struct Objects {
static constexpr char kInterfaceName[] = "org.chromium.TestInterface";
static constexpr char kPropertyName[] = "Objects";
static constexpr gvariant::Type kType{"ao"};
static constexpr bool kReadable = true;
static constexpr bool kWritable = false;
};
// property
struct Bytes {
static constexpr char kInterfaceName[] = "org.chromium.TestInterface";
static constexpr char kPropertyName[] = "Bytes";
static constexpr gvariant::Type kType{"ay"};
static constexpr bool kReadable = true;
static constexpr bool kWritable = false;
};
// signal
struct Test {
static constexpr char kInterfaceName[] = "org.chromium.TestInterface";
static constexpr char kSignalName[] = "Test";
static constexpr gvariant::Type kType{
"("
"s" // message
")"};
};
} // namespace remoting::org_chromium_TestInterface
#endif // REMOTING_HOST_LINUX_DBUS_INTERFACES_ORG_CHROMIUM_TESTINTERFACE_H_

@ -0,0 +1,70 @@
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This file was generated from org.freedesktop.DBus.Properties.xml
#ifndef REMOTING_HOST_LINUX_DBUS_INTERFACES_ORG_FREEDESKTOP_DBUS_PROPERTIES_H_
#define REMOTING_HOST_LINUX_DBUS_INTERFACES_ORG_FREEDESKTOP_DBUS_PROPERTIES_H_
#include "remoting/host/linux/gvariant_type.h"
namespace remoting::org_freedesktop_DBus_Properties {
// method
struct Get {
static constexpr char kInterfaceName[] = "org.freedesktop.DBus.Properties";
static constexpr char kMethodName[] = "Get";
static constexpr gvariant::Type kInType{
"("
"s" // interface_name
"s" // property_name
")"};
static constexpr gvariant::Type kOutType{
"("
"v" // value
")"};
};
// method
struct GetAll {
static constexpr char kInterfaceName[] = "org.freedesktop.DBus.Properties";
static constexpr char kMethodName[] = "GetAll";
static constexpr gvariant::Type kInType{
"("
"s" // interface_name
")"};
static constexpr gvariant::Type kOutType{
"("
"a{sv}" // properties
")"};
};
// method
struct Set {
static constexpr char kInterfaceName[] = "org.freedesktop.DBus.Properties";
static constexpr char kMethodName[] = "Set";
static constexpr gvariant::Type kInType{
"("
"s" // interface_name
"s" // property_name
"v" // value
")"};
static constexpr gvariant::Type kOutType{"()"};
};
// signal
struct PropertiesChanged {
static constexpr char kInterfaceName[] = "org.freedesktop.DBus.Properties";
static constexpr char kSignalName[] = "PropertiesChanged";
static constexpr gvariant::Type kType{
"("
"s" // interface_name
"a{sv}" // changed_properties
"as" // invalidated_properties
")"};
};
} // namespace remoting::org_freedesktop_DBus_Properties
#endif // REMOTING_HOST_LINUX_DBUS_INTERFACES_ORG_FREEDESKTOP_DBUS_PROPERTIES_H_

@ -0,0 +1,159 @@
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "remoting/host/linux/gdbus_connection_ref.h"
#include <gio/gio.h>
#include <glib-object.h>
#include <glib.h>
#include <type_traits>
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/types/expected.h"
#include "remoting/host/linux/gvariant_ref.h"
#include "ui/base/glib/scoped_gobject.h"
namespace remoting {
GDBusConnectionRef::GDBusConnectionRef() = default;
GDBusConnectionRef::GDBusConnectionRef(const GDBusConnectionRef& other) =
default;
GDBusConnectionRef::GDBusConnectionRef(GDBusConnectionRef&& other) = default;
GDBusConnectionRef& GDBusConnectionRef::operator=(
const GDBusConnectionRef& other) = default;
GDBusConnectionRef& GDBusConnectionRef::operator=(GDBusConnectionRef&& other) =
default;
GDBusConnectionRef::~GDBusConnectionRef() = default;
GDBusConnectionRef::GDBusConnectionRef(
ScopedGObject<GDBusConnection> connection)
: connection_(connection) {}
bool GDBusConnectionRef::is_initialized() const {
return connection_;
}
// static
void GDBusConnectionRef::CreateForSessionBus(CreateCallback callback) {
return CreateForBus(G_BUS_TYPE_SESSION, std::move(callback));
}
// static
void GDBusConnectionRef::CreateForSystemBus(CreateCallback callback) {
return CreateForBus(G_BUS_TYPE_SYSTEM, std::move(callback));
}
// static
void GDBusConnectionRef::CreateForBus(GBusType bus, CreateCallback callback) {
g_bus_get(
bus, nullptr,
[](GObject* source, GAsyncResult* result, gpointer user_data) {
std::unique_ptr<CreateCallback> callback(
static_cast<CreateCallback*>(user_data));
GError* error = nullptr;
GDBusConnection* connection = g_bus_get_finish(result, &error);
if (connection) {
std::move(*callback).Run(
base::ok(GDBusConnectionRef(TakeGObject(connection))));
} else {
std::move(*callback).Run(base::unexpected(error->message));
g_error_free(error);
}
},
new CreateCallback(
base::BindPostTask(base::SequencedTaskRunner::GetCurrentDefault(),
std::move(callback))));
}
void GDBusConnectionRef::CallInternal(const char* bus_name,
const char* object_path,
const char* interface_name,
const char* method_name,
const GVariantRef<"r">& arguments,
CallCallback<GVariantRef<"r">> callback,
GDBusCallFlags flags,
gint timeout_msec) const {
auto* bound_callback = new CallCallback<GVariantRef<"r">>(base::BindPostTask(
base::SequencedTaskRunner::GetCurrentDefault(), std::move(callback)));
// May run on a different sequence.
auto on_complete = [](GObject* source, GAsyncResult* result,
gpointer user_data) {
auto callback =
base::WrapUnique(static_cast<decltype(bound_callback)>(user_data));
GError* error = nullptr;
GVariant* variant = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source),
result, &error);
// Will post back to the proper sequence thanks to BindPostTask above.
if (variant != nullptr) {
std::move(*callback).Run(GVariantRef<"r">::TakeUnchecked(variant));
} else {
std::move(*callback).Run(base::unexpected(error->message));
g_error_free(error);
}
};
g_dbus_connection_call(connection_, bus_name, object_path, interface_name,
method_name, arguments.raw(), G_VARIANT_TYPE_TUPLE,
flags, timeout_msec, nullptr, on_complete,
bound_callback);
}
GDBusConnectionRef::SignalSubscription::~SignalSubscription() {
g_dbus_connection_signal_unsubscribe(connection_.raw(), subscription_id_);
}
GDBusConnectionRef::SignalSubscription::SignalSubscription(
GDBusConnectionRef connection,
const char* sender,
const char* object_path,
const char* interface_name,
const char* signal_name,
DetailedSignalCallback<GVariantRef<"r">> callback)
: connection_(std::move(connection)),
callback_(std::move(callback)),
weak_factory_(this) {
auto* bound_callback = new DetailedSignalCallback<GVariantRef<"r">>(
base::BindPostTask(base::SequencedTaskRunner::GetCurrentDefault(),
base::BindRepeating(&SignalSubscription::OnSignal,
weak_factory_.GetWeakPtr())));
// May run on a different sequence.
auto on_signal = [](GDBusConnection* connection, const gchar* sender_name,
const gchar* object_path, const gchar* interface_name,
const gchar* signal_name, GVariant* arguments,
gpointer user_data) {
auto* callback = static_cast<decltype(bound_callback)>(user_data);
// Will post back to the proper sequence thanks to BindPostTask above.
callback->Run(sender_name ? std::string(sender_name) : std::string(),
gvariant::ObjectPath::TryFrom(object_path).value(),
interface_name, signal_name,
GVariantRef<"r">::RefSinkUnchecked(arguments));
};
auto free_func = [](gpointer data) {
delete static_cast<decltype(bound_callback)>(data);
};
subscription_id_ = g_dbus_connection_signal_subscribe(
connection_.raw(), sender, interface_name, signal_name, object_path,
nullptr, G_DBUS_SIGNAL_FLAGS_NONE, on_signal, bound_callback, free_func);
}
void GDBusConnectionRef::SignalSubscription::OnSignal(
std::string sender,
gvariant::ObjectPath object_path,
std::string interface_name,
std::string signal_name,
GVariantRef<"r"> arguments) {
callback_.Run(std::move(sender), std::move(object_path),
std::move(interface_name), std::move(signal_name),
std::move(arguments));
}
} // namespace remoting

@ -0,0 +1,612 @@
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_HOST_LINUX_GDBUS_CONNECTION_REF_H_
#define REMOTING_HOST_LINUX_GDBUS_CONNECTION_REF_H_
#include <gio/gio.h>
#include <glib-object.h>
#include <glib.h>
#include <algorithm>
#include <memory>
#include <optional>
#include <string>
#include <tuple>
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback_forward.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/types/expected.h"
#include "remoting/host/linux/dbus_interfaces/org_freedesktop_DBus_Properties.h"
#include "remoting/host/linux/gvariant_ref.h"
#include "remoting/host/linux/gvariant_type.h"
#include "ui/base/glib/scoped_gobject.h"
namespace remoting {
// A wrapper around a GDBusConnection providing Chromium-style async callbacks
// using GVariantRef for type-safe GVariant handling.
//
// Like with GDBusConnection, calls can be made from any thread. The reply
// callback will be invoke via the default task runner of the caller's virtual
// thread, as obtained by base::SequencedTaskRunner::GetCurrentDefault().
class GDBusConnectionRef {
public:
using CreateCallback =
base::OnceCallback<void(base::expected<GDBusConnectionRef, std::string>)>;
template <typename ReturnType>
using CallCallback =
base::OnceCallback<void(base::expected<ReturnType, std::string>)>;
template <typename ArgType>
using SignalCallback = base::RepeatingCallback<void(ArgType arguments)>;
// Can be passed in lieu of SignalCallback when more details of the emitted
// signal are needed (e.g., when registering the same callback for multiple
// signals).
template <typename ArgType>
using DetailedSignalCallback =
base::RepeatingCallback<void(std::string sender,
gvariant::ObjectPath object_path,
std::string interface_name,
std::string signal_name,
ArgType arguments)>;
// Returned from SignalSubscribe. Dropping will free the callback and cancel
// the underlying subscription. Must be dropped on the sequence where it was
// created.
class SignalSubscription;
// The only valid operation for a default-constructed GDBusConnectionRef is
// assigning a connection to it.
GDBusConnectionRef();
GDBusConnectionRef(const GDBusConnectionRef& other);
GDBusConnectionRef(GDBusConnectionRef&& other);
GDBusConnectionRef& operator=(const GDBusConnectionRef& other);
GDBusConnectionRef& operator=(GDBusConnectionRef&& other);
~GDBusConnectionRef();
// Create from an existing connection.
explicit GDBusConnectionRef(ScopedGObject<GDBusConnection> connection);
// Asynchronously tries to create an instance for the session bus and invokes
// callback with the result.
static void CreateForSessionBus(CreateCallback callback);
// Asynchronously tries to create an instance for the system bus and invokes
// callback with the result.
static void CreateForSystemBus(CreateCallback callback);
// Returns whether this instance is initialized for use (not default
// constructed or moved from).
bool is_initialized() const;
// Obtains the underlying GDBusConnection pointer.
inline GDBusConnection* raw() const { return connection_.get(); }
// Dynamically-checked overloads. Type mismatches reported at run time.
// Asynchronously invoke the provided method, checking types at run time.
//
// bus_name - The owner of the object on which to call the method. May be a
// unique or well-known bus name. If this is a direct peer connection
// rather than a bus connection, pass nullptr.
// object_path - The remote object on which to call the method.
// interface_name - The interface the method is a part of.
// method_name - The name of the method to call.
// arguments - The arguments to pass to the method. The method call will fail
// if the arguments don't match the actual parameters expected by the
// method.
// callback - A callback to invoke with the result of the method call, or an
// error if something goes wrong. If the actual return type can't be
// converted to ReturnType, an error will be returned (but note that in
// this case, the method was still executed.) A ReturnType of
// GVariantRef<"r"> can accept any return value.
// flags - See https://docs.gtk.org/gio/flags.DBusCallFlags.html
// timeout_msec - Timeout for the call in milliseconds. Pass -1 to use the
// default, or G_MAXINT for no timeout.
template <typename ArgType, typename ReturnType>
void Call(const char* bus_name,
const char* object_path,
const char* interface_name,
const char* method_name,
const ArgType& arguments,
CallCallback<ReturnType> callback,
GDBusCallFlags flags = G_DBUS_CALL_FLAGS_NONE,
gint timeout_msec = -1) const
requires requires(GVariantRef<"r"> variant) {
GVariantRef<"r">::TryFrom(arguments);
variant.TryInto<ReturnType>();
};
// Asynchronously retrieve the specified property value.
//
// bus_name - The owner of the object from which to retrieve the property. May
// be a unique or well-known bus name. If this is a direct peer connection
// rather than a bus connection, pass nullptr.
// object_path - The remote object from which to retrieve the property.
// interface_name - The interface the property is a part of.
// property_name - The name of the property to retrieve.
// callback - A callback to invoke with the retrieved property value, or an
// error if something goes wrong. If the property value cannot be
// converted to ValueType, an error will be returned. Accept a
// GVariantRef<> to handle any value type.
template <typename ValueType>
void GetProperty(const char* bus_name,
const char* object_path,
const char* interface_name,
const char* property_name,
CallCallback<ValueType> callback,
GDBusCallFlags flags = G_DBUS_CALL_FLAGS_NONE,
gint timeout_msec = -1) const
requires requires(GVariantRef<> variant) { variant.TryInto<ValueType>(); };
// Asynchronously set the specified property value.
//
// bus_name - The owner of the object on which to set the property. May be a
// unique or well-known bus name. If this is a direct peer connection
// rather than a bus connection, pass nullptr.
// object_path - The remote object on which to set the property.
// interface_name - The interface the property is a part of.
// property_name - The name of the property to set.
// value - The new property value. An error will be returned if the value is
// incompatible with the property.
// callback - A callback to invoke when the property has been set, or an error
// if something goes wrong.
template <typename ValueType>
void SetProperty(const char* bus_name,
const char* object_path,
const char* interface_name,
const char* property_name,
const ValueType& value,
CallCallback<void> callback,
GDBusCallFlags flags = G_DBUS_CALL_FLAGS_NONE,
gint timeout_msec = -1) const
requires requires() { GVariantRef<>::TryFrom(value); };
// Subscribe to matching signals from the sender.
//
// bus_name - The sender from which to receive signals. May be a unique or
// well-known bus name. If this is a direct peer connection rather than a
// bus connection, pass nullptr.
// object_path - The remote object from which to receive signals. May be
// nullptr to receive signals from all objects owned by the sender.
// interface_name - The interface from which to receive signals. May be
// nullptr to receive signals from all interfaces.
// signal_name - The name of the signals to receive. May be nullptr to
// receive signals with any name.
// callback - The callback to invoke when a signal is received. Only signals
// convertible to ArgType will be delivered. Accept a GVariantRef<"r"> to
// receive signals of any type.
//
// Returns a subscription object, which must be dropped on the same sequence.
// Dropping the returned object will unsubscribe from the signal and no more
// signals will be sent.
template <typename ArgType>
std::unique_ptr<SignalSubscription> SignalSubscribe(
const char* bus_name,
const char* object_path,
const char* interface_name,
const char* signal_name,
SignalCallback<ArgType> callback)
requires requires(GVariantRef<"r"> variant) { variant.TryInto<ArgType>(); };
// Variant of subscribe that provides sender information for the signal. In
// addition to the signal data, provides the callback with the following
// information:
//
// sender - The unique bus name of the sender of the signal. Note that this
// will be the unique name of the sender even if the subscription was
// created using the well-known name. If this is a direct peer connection,
// sender will be an empty string.
// object_path - The remote object that was the source of the signal.
// interface_name - The interface that was the source of the signal.
// signal_name - The name of the signal.
//
// This additional information is mostly useful when the same callback is used
// for multiple signals.
template <typename ArgType>
std::unique_ptr<SignalSubscription> SignalSubscribe(
const char* bus_name,
const char* object_path,
const char* interface_name,
const char* signal_name,
DetailedSignalCallback<ArgType> callback)
requires requires(GVariantRef<"r"> variant) { variant.TryInto<ArgType>(); };
// Statically-checked overloads. Types checked against provided spec at
// compile time. (The call may still fail for a variety of other reasons,
// including the spec not matching the actual implementation on the bus.)
// Asynchronously invoke the method declared by the provided MethodSpec.
//
// bus_name - The owner of the object on which to call the method. May be a
// unique or well-known bus name. If this is a direct peer connection
// rather than a bus connection, pass nullptr.
// object_path - The remote object on which to call the method.
// arguments - The arguments to pass to the method. Must be infallibly
// convertible to the input type declared by MethodSpec.
// callback - A callback to invoke with the result of the method call, or an
// error if something goes wrong. The output type declared by MethodSpec
// must be infallibly convertible to ReturnType.
// flags - See https://docs.gtk.org/gio/flags.DBusCallFlags.html
// timeout_msec - Timeout for the call in milliseconds. Pass -1 to use the
// default, or G_MAXINT for no timeout.
template <typename MethodSpec, typename ArgType, typename ReturnType>
void Call(const char* bus_name,
const char* object_path,
const ArgType& arguments,
CallCallback<ReturnType> callback,
GDBusCallFlags flags = G_DBUS_CALL_FLAGS_NONE,
gint timeout_msec = -1) const
requires requires(GVariantRef<MethodSpec::kOutType> variant) {
GVariantRef<MethodSpec::kInType>::From(arguments);
variant.template Into<ReturnType>();
};
// Asynchronously retrieve the property declared by the provided PropertySpec.
//
// bus_name - The owner of the object from which to retrieve the property. May
// be a unique or well-known bus name. If this is a direct peer connection
// rather than a bus connection, pass nullptr.
// object_path - The remote object from which to retrieve the property.
// callback - A callback to invoke with the retrieved property value, or an
// error if something goes wrong. The property type declared by
// PropertySpec must be infallibly convertible to ValueType.
template <typename PropertySpec, typename ValueType>
void GetProperty(const char* bus_name,
const char* object_path,
CallCallback<ValueType> callback,
GDBusCallFlags flags = G_DBUS_CALL_FLAGS_NONE,
gint timeout_msec = -1) const
requires(PropertySpec::kReadable &&
requires(GVariantRef<PropertySpec::kType> variant) {
variant.template Into<ValueType>();
});
// Asynchronously set the property declared by the provided PropertySpec.
//
// bus_name - The owner of the object on which to set the property. May be a
// unique or well-known bus name. If this is a direct peer connection
// rather than a bus connection, pass nullptr.
// object_path - The remote object on which to set the property.
// interface_name - The interface the property is a part of.
// property_name - The name of the property to set.
// value - The new property value. Must be infallibly convertible to the type
// declared by PropertySpec.
// callback - A callback to invoke when the property has been set, or an error
// if something goes wrong.
template <typename PropertySpec, typename ValueType>
void SetProperty(const char* bus_name,
const char* object_path,
const ValueType& value,
CallCallback<void> callback,
GDBusCallFlags flags = G_DBUS_CALL_FLAGS_NONE,
gint timeout_msec = -1) const
requires(PropertySpec::kWritable &&
requires() { GVariantRef<PropertySpec::kType>::From(value); });
// Subscribe to signals matching the provided SignalSpec.
//
// bus_name - The sender from which to receive signals. May be a unique or
// well-known bus name. If this is a direct peer connection rather than a
// bus connection, pass nullptr.
// object_path - The remote object from which to receive signals. May be
// nullptr to receive signals from all objects owned by the sender.
// callback - The callback to invoke when a signal is received. The signal
// type declared by SignalSpec must be infallibly convertible to ArgType.
//
// Returns a subscription object, which must be dropped on the same sequence.
// Dropping the returned object will unsubscribe from the signal and no more
// signals will be sent.
template <typename SignalSpec, typename ArgType>
std::unique_ptr<SignalSubscription> SignalSubscribe(
const char* bus_name,
const char* object_path,
SignalCallback<ArgType> callback)
requires requires(GVariantRef<SignalSpec::kType> variant) {
variant.template Into<ArgType>();
};
// Variant of subscribe that provides sender information for the signal. In
// addition to the signal data, provides the callback with the following
// information:
//
// sender - The unique bus name of the sender of the signal. Note that this
// will be the unique name of the sender even if the subscription was
// created using the well-known name. If this is a direct peer connection,
// sender will be an empty string.
// object_path - The remote object that was the source of the signal.
// interface_name - The interface that was the source of the signal.
// signal_name - The name of the signal.
//
// This additional information is mostly useful when the same callback is used
// for multiple signals.
template <typename SignalSpec, typename ArgType>
std::unique_ptr<SignalSubscription> SignalSubscribe(
const char* bus_name,
const char* object_path,
DetailedSignalCallback<ArgType> callback)
requires requires(GVariantRef<SignalSpec::kType> variant) {
variant.template Into<ArgType>();
};
private:
static void CreateForBus(GBusType bus, CreateCallback callback);
// Common logic for all Calls.
void CallInternal(const char* bus_name,
const char* object_path,
const char* interface_name,
const char* method_name,
const GVariantRef<"r">& arguments,
CallCallback<GVariantRef<"r">> callback,
GDBusCallFlags flags = G_DBUS_CALL_FLAGS_NONE,
gint timeout_msec = -1) const;
ScopedGObject<GDBusConnection> connection_;
};
// Represents an active signal subscription.
class GDBusConnectionRef::SignalSubscription {
public:
// Unsubscribes from the signal.
~SignalSubscription();
private:
// Subscribes to the signal with the given callback.
SignalSubscription(GDBusConnectionRef connection,
const char* sender,
const char* object_path,
const char* interface_name,
const char* signal_name,
DetailedSignalCallback<GVariantRef<"r">> callback);
// Called when a signal arrives. Invokes the provided callback.
void OnSignal(std::string sender,
gvariant::ObjectPath object_path,
std::string interface_name,
std::string signal_name,
GVariantRef<"r"> arguments);
GDBusConnectionRef connection_;
DetailedSignalCallback<GVariantRef<"r">> callback_;
guint subscription_id_;
base::WeakPtrFactory<SignalSubscription> weak_factory_;
friend class GDBusConnectionRef;
};
template <typename ArgType, typename ReturnType>
void GDBusConnectionRef::Call(const char* bus_name,
const char* object_path,
const char* interface_name,
const char* method_name,
const ArgType& arguments,
CallCallback<ReturnType> callback,
GDBusCallFlags flags,
gint timeout_msec) const
requires requires(GVariantRef<"r"> variant) {
GVariantRef<"r">::TryFrom(arguments);
variant.TryInto<ReturnType>();
}
{
// First check that the provided arguments can be converted to a GVariant
// tuple.
auto arg_variant = GVariantRef<"r">::TryFrom(arguments);
if (!arg_variant.has_value()) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback),
base::unexpected(std::move(arg_variant).error())));
return;
}
// Attempt to convert return value into the target type.
auto convert_result =
base::BindOnce([](base::expected<GVariantRef<"r">, std::string> result) {
return std::move(result).and_then([](GVariantRef<"r"> variant) {
return variant.TryInto<ReturnType>();
});
});
CallInternal(bus_name, object_path, interface_name, method_name,
arg_variant.value(),
std::move(convert_result).Then(std::move(callback)));
}
template <typename ValueType>
void GDBusConnectionRef::GetProperty(const char* bus_name,
const char* object_path,
const char* interface_name,
const char* property_name,
CallCallback<ValueType> callback,
GDBusCallFlags flags,
gint timeout_msec) const
requires requires(GVariantRef<> variant) { variant.TryInto<ValueType>(); }
{
// Ensure interface and property names are valid UTF-8.
auto args =
GVariantRef<"(ss)">::TryFrom(std::tuple(interface_name, property_name));
if (!args.has_value()) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback),
base::unexpected(std::move(args).error())));
return;
}
// Unboxes the returned "v" value and attempts to convert it to the expected
// type.
auto convert_result = base::BindOnce(
[](base::expected<GVariantRef<"(v)">, std::string> result) {
return std::move(result).and_then([](GVariantRef<"(v)"> variant) {
return variant.get<0>().get<0>().TryInto<ValueType>();
});
});
Call<remoting::org_freedesktop_DBus_Properties::Get>(
bus_name, object_path, args.value(),
std::move(convert_result).Then(std::move(callback)), flags, timeout_msec);
}
template <typename ValueType>
void GDBusConnectionRef::SetProperty(const char* bus_name,
const char* object_path,
const char* interface_name,
const char* property_name,
const ValueType& value,
CallCallback<void> callback,
GDBusCallFlags flags,
gint timeout_msec) const
requires requires() { GVariantRef<>::TryFrom(value); }
{
// Ensure interface and property names are valid UTF-8 and the value can be
// converted to a GVariant.
auto args = GVariantRef<"(ssv)">::TryFrom(std::tuple(
interface_name, property_name, gvariant::Boxed<const ValueType&>{value}));
if (!args.has_value()) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback),
base::unexpected(std::move(args).error())));
return;
}
// Ignores the returned std::tuple() and returns void instead.
auto convert_result =
base::BindOnce([](base::expected<std::tuple<>, std::string> result) {
return std::move(result).transform([](std::tuple<>) {});
});
Call<remoting::org_freedesktop_DBus_Properties::Set>(
bus_name, object_path, args.value(),
std::move(convert_result).Then(std::move(callback)), flags, timeout_msec);
}
template <typename ArgType>
std::unique_ptr<GDBusConnectionRef::SignalSubscription>
GDBusConnectionRef::SignalSubscribe(const char* bus_name,
const char* object_path,
const char* interface_name,
const char* signal_name,
SignalCallback<ArgType> callback)
requires requires(GVariantRef<"r"> variant) { variant.TryInto<ArgType>(); }
{
return SignalSubscribe(
bus_name, object_path, interface_name, signal_name,
base::IgnoreArgs<std::string, gvariant::ObjectPath, std::string,
std::string>(std::move(callback)));
}
template <typename ArgType>
std::unique_ptr<GDBusConnectionRef::SignalSubscription>
GDBusConnectionRef::SignalSubscribe(const char* bus_name,
const char* object_path,
const char* interface_name,
const char* signal_name,
DetailedSignalCallback<ArgType> callback)
requires requires(GVariantRef<"r"> variant) { variant.TryInto<ArgType>(); }
{
// Attempts to convert return value into the target type and invokes the
// provided callback if it matches.
auto callback_wrapper = base::BindRepeating(
[](const DetailedSignalCallback<ArgType>& callback, std::string sender,
gvariant::ObjectPath object_path, std::string interface_name,
std::string signal_name, GVariantRef<"r"> arguments) {
base::expected<ArgType, std::string> try_result =
arguments.TryInto<ArgType>();
if (try_result.has_value()) {
callback.Run(std::move(sender), std::move(object_path),
std::move(interface_name), std::move(signal_name),
std::move(try_result).value());
}
},
std::move(callback));
return std::unique_ptr<SignalSubscription>(
new SignalSubscription(*this, bus_name, object_path, interface_name,
signal_name, std::move(callback_wrapper)));
}
template <typename MethodSpec, typename ArgType, typename ReturnType>
void GDBusConnectionRef::Call(const char* bus_name,
const char* object_path,
const ArgType& arguments,
CallCallback<ReturnType> callback,
GDBusCallFlags flags,
gint timeout_msec) const
requires requires(GVariantRef<MethodSpec::kOutType> variant) {
GVariantRef<MethodSpec::kInType>::From(arguments);
variant.template Into<ReturnType>();
}
{
Call(bus_name, object_path, MethodSpec::kInterfaceName,
MethodSpec::kMethodName, arguments, std::move(callback), flags,
timeout_msec);
}
template <typename PropertySpec, typename ValueType>
void GDBusConnectionRef::GetProperty(const char* bus_name,
const char* object_path,
CallCallback<ValueType> callback,
GDBusCallFlags flags,
gint timeout_msec) const
requires(PropertySpec::kReadable &&
requires(GVariantRef<PropertySpec::kType> variant) {
variant.template Into<ValueType>();
})
{
GetProperty(bus_name, object_path, PropertySpec::kInterfaceName,
PropertySpec::kPropertyName, std::move(callback), flags,
timeout_msec);
}
template <typename PropertySpec, typename ValueType>
void GDBusConnectionRef::SetProperty(const char* bus_name,
const char* object_path,
const ValueType& value,
CallCallback<void> callback,
GDBusCallFlags flags,
gint timeout_msec) const
requires(PropertySpec::kWritable &&
requires() { GVariantRef<PropertySpec::kType>::From(value); })
{
SetProperty(bus_name, object_path, PropertySpec::kInterfaceName,
PropertySpec::kPropertyName, value, std::move(callback), flags,
timeout_msec);
}
template <typename SignalSpec, typename ArgType>
std::unique_ptr<GDBusConnectionRef::SignalSubscription>
GDBusConnectionRef::SignalSubscribe(const char* bus_name,
const char* object_path,
SignalCallback<ArgType> callback)
requires requires(GVariantRef<SignalSpec::kType> variant) {
variant.template Into<ArgType>();
}
{
return SignalSubscribe(bus_name, object_path, SignalSpec::kInterfaceName,
SignalSpec::kSignalName, std::move(callback));
}
template <typename SignalSpec, typename ArgType>
std::unique_ptr<GDBusConnectionRef::SignalSubscription>
GDBusConnectionRef::SignalSubscribe(const char* bus_name,
const char* object_path,
DetailedSignalCallback<ArgType> callback)
requires requires(GVariantRef<SignalSpec::kType> variant) {
variant.template Into<ArgType>();
}
{
return SignalSubscribe(bus_name, object_path, SignalSpec::kInterfaceName,
SignalSpec::kSignalName, std::move(callback));
}
} // namespace remoting
#endif // REMOTING_HOST_LINUX_GDBUS_CONNECTION_REF_H_

@ -0,0 +1,247 @@
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "remoting/host/linux/gdbus_connection_ref.h"
#include <cstddef>
#include <memory>
#include <string>
#include <tuple>
#include <vector>
#include "base/check.h"
#include "base/check_op.h"
#include "base/functional/callback_helpers.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "base/threading/thread.h"
#include "base/types/expected.h"
#include "dbus/test_service.h"
#include "remoting/host/linux/dbus_interfaces/org_chromium_TestInterface.h"
#include "remoting/host/linux/dbus_interfaces/org_freedesktop_DBus_Properties.h"
#include "remoting/host/linux/gvariant_ref.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace remoting {
namespace test_interface = org_chromium_TestInterface;
class GDBusConnectionRefTest : public testing::Test {
public:
void SetUp() override {
// Start the test service (runs on its own thread).
ASSERT_TRUE(test_service_.StartService());
test_service_.WaitUntilServiceIsStarted();
base::test::TestFuture<base::expected<GDBusConnectionRef, std::string>>
connection;
GDBusConnectionRef::CreateForSessionBus(connection.GetCallback());
ASSERT_TRUE(connection.Get().has_value());
connection_ = connection.Take().value();
}
void TearDown() override { test_service_.ShutdownAndBlock(); }
protected:
static constexpr char kObjectPath[] = "/org/chromium/TestObject";
void PingBus() {
base::test::TestFuture<base::expected<gvariant::Ignored, std::string>>
response;
connection_.Call("org.freedesktop.DBus", "/", "org.freedesktop.DBus.Peer",
"Ping", std::tuple(), response.GetCallback());
EXPECT_TRUE(response.Get().has_value());
}
// Need a UI thread to get a GLib main loop.
base::test::SingleThreadTaskEnvironment task_environment_{
base::test::SingleThreadTaskEnvironment::MainThreadType::UI};
dbus::TestService test_service_{dbus::TestService::Options()};
// Service name is randomly generated each run.
std::string service_name_{test_service_.service_name()};
GDBusConnectionRef connection_;
};
TEST_F(GDBusConnectionRefTest, MethodCall) {
const char* kMessages[] = {"one", "two", "three"};
base::test::TestFuture<base::expected<std::tuple<std::string>, std::string>>
futures[3] = {};
for (std::size_t i = 0; i < 3; ++i) {
connection_.Call<test_interface::AsyncEcho>(
service_name_.c_str(), kObjectPath, std::tuple(kMessages[i]),
futures[i].GetCallback());
}
for (std::size_t i = 0; i < 3; ++i) {
EXPECT_TRUE(futures[i].Get().has_value());
EXPECT_EQ(kMessages[i], get<0>(futures[i].Get().value()));
}
}
TEST_F(GDBusConnectionRefTest, MethodCallError) {
base::test::TestFuture<base::expected<std::tuple<>, std::string>> result;
connection_.Call<test_interface::BrokenMethod>(
service_name_.c_str(), kObjectPath, std::tuple(), result.GetCallback());
EXPECT_FALSE(result.Get().has_value());
}
TEST_F(GDBusConnectionRefTest, GetProperty) {
base::test::TestFuture<base::expected<std::string, std::string>> name_value;
base::test::TestFuture<base::expected<std::vector<std::string>, std::string>>
methods_value;
connection_.GetProperty<test_interface::Name>(
service_name_.c_str(), kObjectPath, name_value.GetCallback());
connection_.GetProperty<test_interface::Methods>(
service_name_.c_str(), kObjectPath, methods_value.GetCallback());
ASSERT_TRUE(name_value.Get().has_value());
EXPECT_EQ("TestService", name_value.Get().value());
ASSERT_TRUE(methods_value.Get().has_value());
EXPECT_EQ((std::vector<std::string>{"Echo", "SlowEcho", "AsyncEcho",
"BrokenMethod"}),
methods_value.Get().value());
}
TEST_F(GDBusConnectionRefTest, SetProperty) {
// TestService doesn't actually remember the set value of name, so instead one
// must listen for the PropertiesChanged signal to make sure Set was called
// properly.
base::test::TestFuture<
GVariantRef<org_freedesktop_DBus_Properties::PropertiesChanged::kType>>
change_signal;
auto signal_subscription =
connection_
.SignalSubscribe<org_freedesktop_DBus_Properties::PropertiesChanged>(
service_name_.c_str(), kObjectPath,
change_signal.GetRepeatingCallback());
base::test::TestFuture<base::expected<void, std::string>> set_complete;
const char* value = "new value";
connection_.SetProperty<test_interface::Name>(
service_name_.c_str(), kObjectPath, value, set_complete.GetCallback());
EXPECT_TRUE(set_complete.Get().has_value());
auto [interface_name, changed_properties, invalidated_properties] =
change_signal.Take();
EXPECT_EQ(test_interface::Name::kInterfaceName, interface_name.string_view());
EXPECT_EQ(GVariantRef<"v">::From(gvariant::Boxed(value)),
changed_properties.LookUp(test_interface::Name::kPropertyName));
}
TEST_F(GDBusConnectionRefTest, SignalSubscribe) {
base::test::TestFuture<std::tuple<std::string>> signal_future;
auto signal_subscription = connection_.SignalSubscribe<test_interface::Test>(
service_name_.c_str(), kObjectPath, signal_future.GetRepeatingCallback());
// Subscribing to a signal involves calling a bus method to send the match
// rule to the bus. Unfortunately, Gio does not provide a way to inform the
// caller of when the bus method has completed, as normally one is
// communicating with a different process and the subscription creation only
// has to be ordered with respect to other DBus messages (like in the
// SetProperty test above). Here, since the test-service signal is triggered
// directly, the test needs to wait for the match rule to be set before
// triggering the signal. As a workaround, the test pings the bus and waits
// for a response. Since the Ping message is sent after the match-rule message
// we know the match-rule message has been received by the time we received
// the ping response.
PingBus();
test_service_.SendTestSignal("message1");
EXPECT_EQ("message1", get<0>(signal_future.Take()));
test_service_.SendTestSignal("message2");
EXPECT_EQ("message2", get<0>(signal_future.Take()));
}
TEST_F(GDBusConnectionRefTest, DropSubscription) {
base::test::TestFuture<std::tuple<std::string>> signal_future;
auto signal_subscription = connection_.SignalSubscribe<test_interface::Test>(
service_name_.c_str(), kObjectPath, signal_future.GetRepeatingCallback());
PingBus();
test_service_.SendTestSignal("message1");
EXPECT_EQ("message1", get<0>(signal_future.Take()));
// Create a new subscription at root and drop original subscription.
signal_subscription = connection_.SignalSubscribe<test_interface::Test>(
service_name_.c_str(), "/", signal_future.GetRepeatingCallback());
PingBus();
// The next signal on TestObject should be ignored, but the following one on
// the root object will match the new subscription.
test_service_.SendTestSignal("message2");
test_service_.SendTestSignalFromRoot("message3");
EXPECT_EQ("message3", get<0>(signal_future.Take()));
}
TEST_F(GDBusConnectionRefTest, SubscribeAll) {
std::string connection_name = test_service_.GetConnectionName();
base::test::TestFuture<std::string, gvariant::ObjectPath, std::string,
std::string, GVariantRef<"r">>
signal_future;
auto signal_subscription = connection_.SignalSubscribe(
service_name_.c_str(), nullptr, nullptr, nullptr,
signal_future.GetRepeatingCallback());
PingBus();
test_service_.SendTestSignal("message1");
{
auto [sender, object_path, interface_name, signal_name, arguments] =
signal_future.Take();
EXPECT_EQ(connection_name, sender);
EXPECT_EQ(kObjectPath, object_path.value());
EXPECT_EQ(test_interface::Test::kInterfaceName, interface_name);
EXPECT_EQ(test_interface::Test::kSignalName, signal_name);
EXPECT_EQ(GVariantRef<>::From(std::tuple("message1")), arguments);
}
test_service_.SendTestSignalFromRoot("message2");
{
auto [sender, object_path, interface_name, signal_name, arguments] =
signal_future.Take();
EXPECT_EQ(connection_name, sender);
EXPECT_EQ("/", object_path.value());
EXPECT_EQ(test_interface::Test::kInterfaceName, interface_name);
EXPECT_EQ(test_interface::Test::kSignalName, signal_name);
EXPECT_EQ(GVariantRef<>::From(std::tuple("message2")), arguments);
}
const char* prop_value = "value3";
connection_.SetProperty<test_interface::Name>(
service_name_.c_str(), kObjectPath, prop_value, base::DoNothing());
{
auto [sender, object_path, interface_name, signal_name, arguments] =
signal_future.Take();
EXPECT_EQ(connection_name, sender);
EXPECT_EQ(kObjectPath, object_path.value());
EXPECT_EQ(
org_freedesktop_DBus_Properties::PropertiesChanged::kInterfaceName,
interface_name);
EXPECT_EQ(org_freedesktop_DBus_Properties::PropertiesChanged::kSignalName,
signal_name);
auto typed_args =
GVariantRef<org_freedesktop_DBus_Properties::PropertiesChanged::kType>::
TryFrom(arguments);
ASSERT_TRUE(typed_args.has_value());
EXPECT_EQ(GVariantRef<"v">::From(gvariant::Boxed(prop_value)),
typed_args->get<1>().LookUp(test_interface::Name::kPropertyName));
}
}
} // namespace remoting

@ -50,11 +50,11 @@ class ScopedGObject {
}
}
T* get() { return obj_; }
T* get() const { return obj_; }
// Deliberately implicit to allow easier interaction with C APIs.
// NOLINTNEXTLINE(google-explicit-constructor)
operator T*() { return obj_; }
operator T*() const { return obj_; }
private:
template <typename U>