diff --git a/library/linux/include/flutter_desktop_embedding/binary_messenger.h b/library/linux/include/flutter_desktop_embedding/binary_messenger.h index 06baf5038..d08bd6a3b 100644 --- a/library/linux/include/flutter_desktop_embedding/binary_messenger.h +++ b/library/linux/include/flutter_desktop_embedding/binary_messenger.h @@ -14,10 +14,26 @@ #ifndef LIBRARY_LINUX_INCLUDE_FLUTTER_DESKTOP_EMBEDDING_BINARY_MESSENGER_H_ #define LIBRARY_LINUX_INCLUDE_FLUTTER_DESKTOP_EMBEDDING_BINARY_MESSENGER_H_ +#include #include +// TODO: Consider adding absl as a dependency and using absl::Span for all of +// the message/message_size pairs. namespace flutter_desktop_embedding { +// A message reply callback. +// +// Used for submitting a reply back to a Flutter message sender. +typedef std::function + BinaryReply; + +// A message handler callback. +// +// Used for receiving messages from Flutter and providing an asynchronous reply. +typedef std::function + BinaryMessageHandler; + // A protocol for a class that handles communication of binary data on named // channels to and from the Flutter engine. class BinaryMessenger { @@ -32,7 +48,13 @@ class BinaryMessenger { // TODO: Add support for a version of Send expecting a reply once // https://github.com/flutter/flutter/issues/18852 is fixed. - // TODO: Add SetMessageHandler. See Issue #102. + // Registers a message handler for incoming binary messages from the Flutter + // side on the specified channel. + // + // Replaces any existing handler. Provide a null handler to unregister the + // existing handler. + virtual void SetMessageHandler(const std::string &channel, + BinaryMessageHandler handler) = 0; }; } // namespace flutter_desktop_embedding diff --git a/library/linux/include/flutter_desktop_embedding/json_plugin.h b/library/linux/include/flutter_desktop_embedding/json_plugin.h index 06bf1e322..0eaefde2e 100644 --- a/library/linux/include/flutter_desktop_embedding/json_plugin.h +++ b/library/linux/include/flutter_desktop_embedding/json_plugin.h @@ -14,7 +14,11 @@ #ifndef LIBRARY_LINUX_INCLUDE_FLUTTER_DESKTOP_EMBEDDING_JSON_PLUGIN_H_ #define LIBRARY_LINUX_INCLUDE_FLUTTER_DESKTOP_EMBEDDING_JSON_PLUGIN_H_ +#include +#include + #include "json_method_call.h" +#include "method_channel.h" #include "plugin.h" namespace flutter_desktop_embedding { @@ -38,16 +42,24 @@ class JsonPlugin : public Plugin { std::unique_ptr result) override; protected: + // Plugin implementation: + void RegisterMethodChannels(BinaryMessenger *messenger) override; + // Identical to HandleMethodCall, except that the call has been cast to the // more specific type. Subclasses must implement this instead of // HandleMethodCall. virtual void HandleJsonMethodCall(const JsonMethodCall &method_call, std::unique_ptr result) = 0; - // Calls InvokeMethodCall with a JsonMethodCall constructed from the given - // values. + // Calls InvokeMethodCall on |method_channel_| with a JsonMethodCall + // constructed from the given values. void InvokeMethod(const std::string &method, const Json::Value &arguments = Json::Value()); + + private: + // The MethodChannel used by this plugin for communication with the Flutter + // engine. + std::unique_ptr method_channel_; }; } // namespace flutter_desktop_embedding diff --git a/library/linux/include/flutter_desktop_embedding/method_channel.h b/library/linux/include/flutter_desktop_embedding/method_channel.h new file mode 100644 index 000000000..6f4f0f604 --- /dev/null +++ b/library/linux/include/flutter_desktop_embedding/method_channel.h @@ -0,0 +1,72 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef LIBRARY_LINUX_INCLUDE_FLUTTER_DESKTOP_EMBEDDING_METHOD_CHANNEL_H_ +#define LIBRARY_LINUX_INCLUDE_FLUTTER_DESKTOP_EMBEDDING_METHOD_CHANNEL_H_ + +#include + +#include "binary_messenger.h" +#include "method_call.h" +#include "method_codec.h" +#include "method_result.h" + +namespace flutter_desktop_embedding { + +// A handler for receiving a method call from the Flutter engine. +// +// Implementations must asynchronously call exactly one of the methods on +// |result| to indicate the resust of the method call. +typedef std::function result)> + MethodCallHandler; + +// A channel for communicating with the Flutter engine using invocation of +// asynchronous methods. +class MethodChannel { + public: + // Creates an instance that sends and receives method calls on the channel + // named |name|, encoded with |codec| and dispatched via |messenger|. + // + // TODO: Make codec optional once the standard codec is supported (Issue #67). + MethodChannel(BinaryMessenger *messenger, const std::string &name, + const MethodCodec *codec); + ~MethodChannel(); + + // Prevent copying. + MethodChannel(MethodChannel const &) = delete; + MethodChannel &operator=(MethodChannel const &) = delete; + + // Sends |method_call| to the Flutter engine on this channel. + // + // TODO: Implement InovkeMethod and remove this. This is a temporary + // implementation, since supporting InvokeMethod involves significant changes + // to other classes. + void InvokeMethodCall(const MethodCall &method_call) const; + + // TODO: Add support for a version expecting a reply once + // https://github.com/flutter/flutter/issues/18852 is fixed. + + // Registers a handler that should be called any time a method call is + // received on this channel. + void SetMethodCallHandler(MethodCallHandler handler) const; + + private: + BinaryMessenger *messenger_; + std::string name_; + const MethodCodec *codec_; +}; + +} // namespace flutter_desktop_embedding + +#endif // LIBRARY_LINUX_INCLUDE_FLUTTER_DESKTOP_EMBEDDING_METHOD_CHANNEL_H_ diff --git a/library/linux/include/flutter_desktop_embedding/plugin.h b/library/linux/include/flutter_desktop_embedding/plugin.h index b9dfe86b1..02affff86 100644 --- a/library/linux/include/flutter_desktop_embedding/plugin.h +++ b/library/linux/include/flutter_desktop_embedding/plugin.h @@ -59,21 +59,26 @@ class Plugin { // while waiting for this plugin to handle its platform message. virtual bool input_blocking() const { return input_blocking_; } - // Sets the pointer to the caller-owned binary messenger. + // Binds this plugin to the given caller-owned binary messenger. It must + // remain valid for the life of the plugin. // // The embedder typically sets this pointer rather than the client. - virtual void set_binary_messenger(BinaryMessenger *messenger) { - messenger_ = messenger; - } + void SetBinaryMessenger(BinaryMessenger *messenger); protected: + // Implementers should register any MethodChannels that should receive + // messages from Flutter with |messenger| when this is called. + virtual void RegisterMethodChannels(BinaryMessenger *messenger) = 0; + // Calls a method in the Flutter engine on this Plugin's channel. + // + // Deprecated. Use MethodChannel's InvokeMethodCall instead. void InvokeMethodCall(const MethodCall &method_call); private: std::string channel_; // Caller-owned instance of the binary messenger used to message the engine. - BinaryMessenger *messenger_; + const BinaryMessenger *messenger_; bool input_blocking_; }; diff --git a/library/linux/src/embedder.cc b/library/linux/src/embedder.cc index c7b0acc6b..41e36ef97 100644 --- a/library/linux/src/embedder.cc +++ b/library/linux/src/embedder.cc @@ -242,7 +242,6 @@ namespace flutter_desktop_embedding { bool AddPlugin(GLFWwindow *flutter_window, std::unique_ptr plugin) { auto state = GetSavedEmbedderState(flutter_window); - plugin->set_binary_messenger(state->plugin_handler.get()); return state->plugin_handler->AddPlugin(std::move(plugin)); } diff --git a/library/linux/src/internal/engine_method_result.cc b/library/linux/src/internal/engine_method_result.cc index 86d39e463..46850f3f2 100644 --- a/library/linux/src/internal/engine_method_result.cc +++ b/library/linux/src/internal/engine_method_result.cc @@ -17,19 +17,17 @@ namespace flutter_desktop_embedding { -EngineMethodResult::EngineMethodResult( - FlutterEngine engine, - const FlutterPlatformMessageResponseHandle *response_handle, - const MethodCodec *codec) - : engine_(engine), response_handle_(response_handle), codec_(codec) { - if (!response_handle_) { - std::cerr << "Error: Response handle must be provided for a response." +EngineMethodResult::EngineMethodResult(BinaryReply reply_handler, + const MethodCodec *codec) + : reply_handler_(std::move(reply_handler)), codec_(codec) { + if (!reply_handler_) { + std::cerr << "Error: Reply handler must be provided for a response." << std::endl; } } EngineMethodResult::~EngineMethodResult() { - if (response_handle_) { + if (reply_handler_) { // Warn, rather than send a not-implemented response, since the engine may // no longer be valid at this point. std::cerr @@ -55,20 +53,18 @@ void EngineMethodResult::ErrorInternal(const std::string &error_code, void EngineMethodResult::NotImplementedInternal() { SendResponseData(nullptr); } void EngineMethodResult::SendResponseData(const std::vector *data) { - if (!response_handle_) { + if (!reply_handler_) { std::cerr - << "Error: Response can be set only once. Ignoring duplicate response." + << "Error: Only one of Success, Error, or NotImplemented can be called," + << " and it can be called exactyl once. Ignoring duplicate result." << std::endl; return; } const uint8_t *message = data && !data->empty() ? data->data() : nullptr; size_t message_size = data ? data->size() : 0; - FlutterEngineSendPlatformMessageResponse(engine_, response_handle_, message, - message_size); - // The engine frees the response handle once - // FlutterEngineSendPlatformMessageResponse is called. - response_handle_ = nullptr; + reply_handler_(message, message_size); + reply_handler_ = nullptr; } } // namespace flutter_desktop_embedding diff --git a/library/linux/src/internal/engine_method_result.h b/library/linux/src/internal/engine_method_result.h index 467fec11a..23d9427d8 100644 --- a/library/linux/src/internal/engine_method_result.h +++ b/library/linux/src/internal/engine_method_result.h @@ -17,8 +17,7 @@ #include #include -#include - +#include "library/linux/include/flutter_desktop_embedding/binary_messenger.h" #include "library/linux/include/flutter_desktop_embedding/method_codec.h" #include "library/linux/include/flutter_desktop_embedding/method_result.h" @@ -27,15 +26,12 @@ namespace flutter_desktop_embedding { // Implemention of MethodResult that sends responses to the Flutter egnine. class EngineMethodResult : public MethodResult { public: - // Creates a result object that will send results to |engine|, tagged as - // associated with |response_handle|, encoded using |codec|. The |engine| - // and |codec| pointers must remain valid for as long as this object exists. + // Creates a result object that will send results to |reply_handler|, encoded + // using |codec|. The |codec| pointer must remain valid for as long as this + // object exists. // // If the codec is null, only NotImplemented() may be called. - EngineMethodResult( - FlutterEngine engine, - const FlutterPlatformMessageResponseHandle *response_handle, - const MethodCodec *codec); + EngineMethodResult(BinaryReply reply_handler, const MethodCodec *codec); ~EngineMethodResult(); protected: @@ -52,8 +48,7 @@ class EngineMethodResult : public MethodResult { // the engine. void SendResponseData(const std::vector *data); - FlutterEngine engine_; - const FlutterPlatformMessageResponseHandle *response_handle_; + BinaryReply reply_handler_; const MethodCodec *codec_; }; diff --git a/library/linux/src/internal/plugin_handler.cc b/library/linux/src/internal/plugin_handler.cc index 601a11f0c..4acb94e5f 100644 --- a/library/linux/src/internal/plugin_handler.cc +++ b/library/linux/src/internal/plugin_handler.cc @@ -13,6 +13,7 @@ // limitations under the License. #include "library/linux/src/internal/plugin_handler.h" +#include "library/linux/include/flutter_desktop_embedding/method_channel.h" #include "library/linux/src/internal/engine_method_result.h" #include @@ -27,6 +28,7 @@ bool PluginHandler::AddPlugin(std::unique_ptr plugin) { if (plugins_.find(plugin->channel()) != plugins_.end()) { return false; } + plugin->SetBinaryMessenger(this); plugins_.insert(std::make_pair(plugin->channel(), std::move(plugin))); return true; } @@ -36,36 +38,42 @@ void PluginHandler::HandleMethodCallMessage( std::function input_block_cb, std::function input_unblock_cb) { std::string channel(message->channel); + auto *response_handle = message->response_handle; + auto *response_engine = engine_; + BinaryReply reply_handler = [response_engine, response_handle]( + const uint8_t *reply, + const size_t reply_size) mutable { + if (!response_handle) { + std::cerr << "Error: Response can be set only once. Ignoring " + "duplicate response." + << std::endl; + return; + } + FlutterEngineSendPlatformMessageResponse(response_engine, response_handle, + reply, reply_size); + // The engine frees the response handle once + // FlutterEngineSendPlatformMessageResponse is called. + response_handle = nullptr; + }; - // Find the plugin for the channel; if there isn't one, report the failure. - if (plugins_.find(channel) == plugins_.end()) { + // Find the handler for the channel; if there isn't one, report the failure. + if (handlers_.find(channel) == handlers_.end()) { auto result = std::make_unique( - engine_, message->response_handle, nullptr); + std::move(reply_handler), nullptr); result->NotImplemented(); return; } + const BinaryMessageHandler &message_handler = handlers_[channel]; const std::unique_ptr &plugin = plugins_[channel]; - // Use the plugin's codec to decode the call and build a result handler. - const flutter_desktop_embedding::MethodCodec &codec = plugin->GetCodec(); - auto result = std::make_unique( - engine_, message->response_handle, &codec); - std::unique_ptr method_call = - codec.DecodeMethodCall(message->message, message->message_size); - if (!method_call) { - std::cerr << "Unable to construct method call from message on channel " - << message->channel << std::endl; - result->NotImplemented(); - return; - } - // Process the call, handling input blocking if requested by the plugin. - if (plugin->input_blocking()) { + if (plugin && plugin->input_blocking()) { input_block_cb(); } - plugin->HandleMethodCall(*method_call, std::move(result)); - if (plugin->input_blocking()) { + message_handler(message->message, message->message_size, + std::move(reply_handler)); + if (plugin && plugin->input_blocking()) { input_unblock_cb(); } } @@ -81,4 +89,9 @@ void PluginHandler::Send(const std::string &channel, const uint8_t *message, FlutterEngineSendPlatformMessage(engine_, &platform_message); } +void PluginHandler::SetMessageHandler(const std::string &channel, + BinaryMessageHandler handler) { + handlers_[channel] = std::move(handler); +} + } // namespace flutter_desktop_embedding diff --git a/library/linux/src/internal/plugin_handler.h b/library/linux/src/internal/plugin_handler.h index e924c28a7..46c1e06c1 100644 --- a/library/linux/src/internal/plugin_handler.h +++ b/library/linux/src/internal/plugin_handler.h @@ -63,10 +63,13 @@ class PluginHandler : public BinaryMessenger { // BinaryMessenger implementation: void Send(const std::string &channel, const uint8_t *message, const size_t message_size) const override; + void SetMessageHandler(const std::string &channel, + BinaryMessageHandler handler) override; private: FlutterEngine engine_; std::map> plugins_; + std::map handlers_; }; } // namespace flutter_desktop_embedding diff --git a/library/linux/src/json_plugin.cc b/library/linux/src/json_plugin.cc index ec2779595..2cb7b56d9 100644 --- a/library/linux/src/json_plugin.cc +++ b/library/linux/src/json_plugin.cc @@ -32,9 +32,20 @@ void JsonPlugin::HandleMethodCall(const MethodCall &method_call, std::move(result)); } +void JsonPlugin::RegisterMethodChannels(BinaryMessenger *messenger) { + method_channel_ = + std::make_unique(messenger, channel(), &GetCodec()); + + MethodCallHandler handler = [this](const MethodCall &call, + std::unique_ptr result) { + HandleMethodCall(call, std::move(result)); + }; + method_channel_->SetMethodCallHandler(std::move(handler)); +} + void JsonPlugin::InvokeMethod(const std::string &method, const Json::Value &arguments) { - InvokeMethodCall(JsonMethodCall(method, arguments)); + method_channel_->InvokeMethodCall(JsonMethodCall(method, arguments)); } } // namespace flutter_desktop_embedding diff --git a/library/linux/src/method_channel.cc b/library/linux/src/method_channel.cc new file mode 100644 index 000000000..b4201310f --- /dev/null +++ b/library/linux/src/method_channel.cc @@ -0,0 +1,56 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "library/linux/include/flutter_desktop_embedding/method_channel.h" + +#include + +#include "library/linux/src/internal/engine_method_result.h" + +namespace flutter_desktop_embedding { + +MethodChannel::MethodChannel(BinaryMessenger *messenger, + const std::string &name, const MethodCodec *codec) + : messenger_(messenger), name_(name), codec_(codec) {} + +MethodChannel::~MethodChannel() {} + +void MethodChannel::InvokeMethodCall(const MethodCall &method_call) const { + std::unique_ptr> message = + codec_->EncodeMethodCall(method_call); + messenger_->Send(name_, message->data(), message->size()); +} + +void MethodChannel::SetMethodCallHandler(MethodCallHandler handler) const { + const auto *codec = codec_; + std::string channel_name = name_; + BinaryMessageHandler binary_handler = [handler, codec, channel_name]( + const uint8_t *message, + const size_t message_size, + BinaryReply reply) { + // Use this channel's codec to decode the call and build a result handler. + auto result = std::make_unique(std::move(reply), codec); + std::unique_ptr method_call = + codec->DecodeMethodCall(message, message_size); + if (!method_call) { + std::cerr << "Unable to construct method call from message on channel " + << channel_name << std::endl; + result->NotImplemented(); + return; + } + handler(*method_call, std::move(result)); + }; + messenger_->SetMessageHandler(name_, std::move(binary_handler)); +} + +} // namespace flutter_desktop_embedding diff --git a/library/linux/src/plugin.cc b/library/linux/src/plugin.cc index 041f6c651..aaf63642e 100644 --- a/library/linux/src/plugin.cc +++ b/library/linux/src/plugin.cc @@ -22,6 +22,11 @@ Plugin::Plugin(const std::string &channel, bool input_blocking) Plugin::~Plugin() {} +void Plugin::SetBinaryMessenger(BinaryMessenger *messenger) { + messenger_ = messenger; + RegisterMethodChannels(messenger); +} + void Plugin::InvokeMethodCall(const MethodCall &method_call) { if (!messenger_) { return;