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:

committed by
Chromium LUCI CQ

parent
3e4d95b8be
commit
0cb26c85ee
@ -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",
|
||||
],
|
||||
}
|
||||
|
17
remoting/host/linux/dbus_interfaces/BUILD.gn
Normal file
17
remoting/host/linux/dbus_interfaces/BUILD.gn
Normal file
@ -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" ]
|
||||
}
|
200
remoting/host/linux/dbus_interfaces/gen_dbus_interface.cc
Normal file
200
remoting/host/linux/dbus_interfaces/gen_dbus_interface.cc
Normal file
@ -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);
|
||||
}
|
132
remoting/host/linux/dbus_interfaces/org_chromium_TestInterface.h
Normal file
132
remoting/host/linux/dbus_interfaces/org_chromium_TestInterface.h
Normal file
@ -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_
|
159
remoting/host/linux/gdbus_connection_ref.cc
Normal file
159
remoting/host/linux/gdbus_connection_ref.cc
Normal file
@ -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
|
612
remoting/host/linux/gdbus_connection_ref.h
Normal file
612
remoting/host/linux/gdbus_connection_ref.h
Normal file
@ -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_
|
247
remoting/host/linux/gdbus_connection_ref_unittest.cc
Normal file
247
remoting/host/linux/gdbus_connection_ref_unittest.cc
Normal file
@ -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>
|
||||
|
Reference in New Issue
Block a user