blob: cd5fb904d50622f2f0c6cdfea1ed003250ef0c05 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/shell/platform/linux/fl_platform_channel.h"
#include <cstring>
#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_method_codec.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h"
static constexpr char kChannelName[] = "flutter/platform";
static constexpr char kBadArgumentsError[] = "Bad Arguments";
static constexpr char kGetClipboardDataMethod[] = "Clipboard.getData";
static constexpr char kSetClipboardDataMethod[] = "Clipboard.setData";
static constexpr char kClipboardHasStringsMethod[] = "Clipboard.hasStrings";
static constexpr char kExitApplicationMethod[] = "System.exitApplication";
static constexpr char kRequestAppExitMethod[] = "System.requestAppExit";
static constexpr char kInitializationCompleteMethod[] =
"System.initializationComplete";
static constexpr char kPlaySoundMethod[] = "SystemSound.play";
static constexpr char kSystemNavigatorPopMethod[] = "SystemNavigator.pop";
static constexpr char kTextKey[] = "text";
static constexpr char kValueKey[] = "value";
static constexpr char kExitTypeKey[] = "type";
static constexpr char kExitTypeCancelable[] = "cancelable";
static constexpr char kExitTypeRequired[] = "required";
static constexpr char kExitResponseKey[] = "response";
static constexpr char kExitResponseCancel[] = "cancel";
static constexpr char kExitResponseExit[] = "exit";
struct _FlPlatformChannel {
GObject parent_instance;
FlMethodChannel* channel;
// Handlers for incoming method calls.
FlPlatformChannelVTable* vtable;
// User data to pass to method call handlers.
gpointer user_data;
};
G_DEFINE_TYPE(FlPlatformChannel, fl_platform_channel, G_TYPE_OBJECT)
static FlMethodResponse* clipboard_set_data(FlPlatformChannel* self,
FlMethodCall* method_call) {
FlValue* args = fl_method_call_get_args(method_call);
if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kBadArgumentsError, "Argument map missing or malformed", nullptr));
}
FlValue* text_value = fl_value_lookup_string(args, kTextKey);
if (text_value == nullptr ||
fl_value_get_type(text_value) != FL_VALUE_TYPE_STRING) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kBadArgumentsError, "Missing clipboard text", nullptr));
}
const gchar* text = fl_value_get_string(text_value);
return self->vtable->clipboard_set_data(method_call, text, self->user_data);
}
static FlMethodResponse* clipboard_get_data(FlPlatformChannel* self,
FlMethodCall* method_call) {
FlValue* args = fl_method_call_get_args(method_call);
if (fl_value_get_type(args) != FL_VALUE_TYPE_STRING) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kBadArgumentsError, "Expected string", nullptr));
}
const gchar* format = fl_value_get_string(args);
return self->vtable->clipboard_get_data(method_call, format, self->user_data);
}
static FlMethodResponse* clipboard_has_strings(FlPlatformChannel* self,
FlMethodCall* method_call) {
return self->vtable->clipboard_has_strings(method_call, self->user_data);
}
// Get the exit response from a System.requestAppExit method call.
FlPlatformChannelExitResponse get_exit_response(FlMethodResponse* response) {
if (response == nullptr) {
return FL_PLATFORM_CHANNEL_EXIT_RESPONSE_EXIT;
}
g_autoptr(GError) error = nullptr;
FlValue* result = fl_method_response_get_result(response, &error);
if (result == nullptr) {
g_warning("Error returned from System.requestAppExit: %s", error->message);
return FL_PLATFORM_CHANNEL_EXIT_RESPONSE_EXIT;
}
if (fl_value_get_type(result) != FL_VALUE_TYPE_MAP) {
g_warning("System.requestAppExit result argument map missing or malformed");
return FL_PLATFORM_CHANNEL_EXIT_RESPONSE_EXIT;
}
FlValue* response_value = fl_value_lookup_string(result, kExitResponseKey);
if (fl_value_get_type(response_value) != FL_VALUE_TYPE_STRING) {
g_warning("Invalid response from System.requestAppExit");
return FL_PLATFORM_CHANNEL_EXIT_RESPONSE_EXIT;
}
const char* response_string = fl_value_get_string(response_value);
if (strcmp(response_string, kExitResponseCancel) == 0) {
return FL_PLATFORM_CHANNEL_EXIT_RESPONSE_CANCEL;
} else if (strcmp(response_string, kExitResponseExit) == 0) {
return FL_PLATFORM_CHANNEL_EXIT_RESPONSE_EXIT;
}
// If something went wrong, then just exit.
return FL_PLATFORM_CHANNEL_EXIT_RESPONSE_EXIT;
}
static FlMethodResponse* system_exit_application(FlPlatformChannel* self,
FlMethodCall* method_call) {
FlValue* args = fl_method_call_get_args(method_call);
if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kBadArgumentsError, "Argument map missing or malformed", nullptr));
}
FlValue* type_value = fl_value_lookup_string(args, kExitTypeKey);
if (type_value == nullptr ||
fl_value_get_type(type_value) != FL_VALUE_TYPE_STRING) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kBadArgumentsError, "Missing type argument", nullptr));
}
const char* type_string = fl_value_get_string(type_value);
FlPlatformChannelExitType type;
if (strcmp(type_string, kExitTypeCancelable) == 0) {
type = FL_PLATFORM_CHANNEL_EXIT_TYPE_CANCELABLE;
} else if (strcmp(type_string, kExitTypeRequired) == 0) {
type = FL_PLATFORM_CHANNEL_EXIT_TYPE_REQUIRED;
} else {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kBadArgumentsError, "Invalid exit type", nullptr));
}
return self->vtable->system_exit_application(method_call, type,
self->user_data);
}
static FlMethodResponse* system_initialization_complete(
FlPlatformChannel* self) {
self->vtable->system_initialization_complete(self->user_data);
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
}
static FlMethodResponse* system_sound_play(FlPlatformChannel* self,
FlValue* args) {
if (fl_value_get_type(args) != FL_VALUE_TYPE_STRING) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kBadArgumentsError, "Expected string", nullptr));
}
const gchar* type = fl_value_get_string(args);
self->vtable->system_sound_play(type, self->user_data);
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
}
static FlMethodResponse* system_navigator_pop(FlPlatformChannel* self) {
self->vtable->system_navigator_pop(self->user_data);
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
}
static void method_call_cb(FlMethodChannel* channel,
FlMethodCall* method_call,
gpointer user_data) {
FlPlatformChannel* self = FL_PLATFORM_CHANNEL(user_data);
const gchar* method = fl_method_call_get_name(method_call);
FlValue* args = fl_method_call_get_args(method_call);
g_autoptr(FlMethodResponse) response = nullptr;
if (strcmp(method, kSetClipboardDataMethod) == 0) {
response = clipboard_set_data(self, method_call);
} else if (strcmp(method, kGetClipboardDataMethod) == 0) {
response = clipboard_get_data(self, method_call);
} else if (strcmp(method, kClipboardHasStringsMethod) == 0) {
response = clipboard_has_strings(self, method_call);
} else if (strcmp(method, kExitApplicationMethod) == 0) {
response = system_exit_application(self, method_call);
} else if (strcmp(method, kInitializationCompleteMethod) == 0) {
response = system_initialization_complete(self);
} else if (strcmp(method, kPlaySoundMethod) == 0) {
response = system_sound_play(self, args);
} else if (strcmp(method, kSystemNavigatorPopMethod) == 0) {
response = system_navigator_pop(self);
} else {
response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
}
if (response != nullptr) {
g_autoptr(GError) error = nullptr;
if (!fl_method_call_respond(method_call, response, &error)) {
g_warning("Failed to send method call response: %s", error->message);
}
}
}
static void fl_platform_channel_dispose(GObject* object) {
FlPlatformChannel* self = FL_PLATFORM_CHANNEL(object);
g_clear_object(&self->channel);
G_OBJECT_CLASS(fl_platform_channel_parent_class)->dispose(object);
}
static void fl_platform_channel_class_init(FlPlatformChannelClass* klass) {
G_OBJECT_CLASS(klass)->dispose = fl_platform_channel_dispose;
}
static void fl_platform_channel_init(FlPlatformChannel* self) {}
FlPlatformChannel* fl_platform_channel_new(FlBinaryMessenger* messenger,
FlPlatformChannelVTable* vtable,
gpointer user_data) {
g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr);
g_return_val_if_fail(vtable != nullptr, nullptr);
FlPlatformChannel* self = FL_PLATFORM_CHANNEL(
g_object_new(fl_platform_channel_get_type(), nullptr));
self->vtable = vtable;
self->user_data = user_data;
g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new();
self->channel =
fl_method_channel_new(messenger, kChannelName, FL_METHOD_CODEC(codec));
fl_method_channel_set_method_call_handler(self->channel, method_call_cb, self,
nullptr);
return self;
}
void fl_platform_channel_system_request_app_exit(FlPlatformChannel* self,
FlPlatformChannelExitType type,
GCancellable* cancellable,
GAsyncReadyCallback callback,
gpointer user_data) {
g_return_if_fail(FL_IS_PLATFORM_CHANNEL(self));
g_autoptr(FlValue) args = fl_value_new_map();
const gchar* type_string;
switch (type) {
case FL_PLATFORM_CHANNEL_EXIT_TYPE_CANCELABLE:
type_string = kExitTypeCancelable;
break;
case FL_PLATFORM_CHANNEL_EXIT_TYPE_REQUIRED:
type_string = kExitTypeRequired;
break;
default:
g_assert_not_reached();
}
fl_value_set_string_take(args, kExitTypeKey,
fl_value_new_string(type_string));
fl_method_channel_invoke_method(self->channel, kRequestAppExitMethod, args,
cancellable, callback, user_data);
}
gboolean fl_platform_channel_system_request_app_exit_finish(
GObject* object,
GAsyncResult* result,
FlPlatformChannelExitResponse* exit_response,
GError** error) {
g_autoptr(FlMethodResponse) response = fl_method_channel_invoke_method_finish(
FL_METHOD_CHANNEL(object), result, error);
if (response == nullptr) {
return FALSE;
}
*exit_response = get_exit_response(response);
return TRUE;
}
void fl_platform_channel_respond_clipboard_get_data(FlMethodCall* method_call,
const gchar* text) {
g_autoptr(FlValue) result = nullptr;
if (text != nullptr) {
result = fl_value_new_map();
fl_value_set_string_take(result, kTextKey, fl_value_new_string(text));
}
g_autoptr(FlMethodResponse) response =
FL_METHOD_RESPONSE(fl_method_success_response_new(result));
g_autoptr(GError) error = nullptr;
if (!fl_method_call_respond(method_call, response, &error)) {
g_warning("Failed to send response to %s: %s", kGetClipboardDataMethod,
error->message);
}
}
void fl_platform_channel_respond_clipboard_has_strings(
FlMethodCall* method_call,
gboolean has_strings) {
g_autoptr(FlValue) result = fl_value_new_map();
fl_value_set_string_take(result, kValueKey, fl_value_new_bool(has_strings));
g_autoptr(FlMethodResponse) response =
FL_METHOD_RESPONSE(fl_method_success_response_new(result));
g_autoptr(GError) error = nullptr;
if (!fl_method_call_respond(method_call, response, &error)) {
g_warning("Failed to send response to %s: %s", kClipboardHasStringsMethod,
error->message);
}
}
void fl_platform_channel_respond_system_exit_application(
FlMethodCall* method_call,
FlPlatformChannelExitResponse exit_response) {
g_autoptr(FlMethodResponse) response =
fl_platform_channel_make_system_request_app_exit_response(exit_response);
g_autoptr(GError) error = nullptr;
if (!fl_method_call_respond(method_call, response, &error)) {
g_warning("Failed to send response to System.exitApplication: %s",
error->message);
}
}
FlMethodResponse* fl_platform_channel_make_system_request_app_exit_response(
FlPlatformChannelExitResponse exit_response) {
g_autoptr(FlValue) exit_result = fl_value_new_map();
const gchar* exit_response_string;
switch (exit_response) {
case FL_PLATFORM_CHANNEL_EXIT_RESPONSE_CANCEL:
exit_response_string = kExitResponseCancel;
break;
case FL_PLATFORM_CHANNEL_EXIT_RESPONSE_EXIT:
exit_response_string = kExitResponseExit;
break;
default:
g_assert_not_reached();
}
fl_value_set_string_take(exit_result, kExitResponseKey,
fl_value_new_string(exit_response_string));
return FL_METHOD_RESPONSE(fl_method_success_response_new(exit_result));
}