blob: 79cad6f774b9605214d666c059415e9fd13c3bde [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_mouse_cursor_plugin.h"
#include <gtk/gtk.h>
#include <cstring>
#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h"
static constexpr char kChannelName[] = "flutter/mousecursor";
static constexpr char kBadArgumentsError[] = "Bad Arguments";
static constexpr char kActivateSystemCursorMethod[] = "activateSystemCursor";
static constexpr char kKindKey[] = "kind";
static constexpr char kFallbackCursor[] = "default";
struct _FlMouseCursorPlugin {
GObject parent_instance;
FlMethodChannel* channel;
FlView* view;
GHashTable* system_cursor_table;
};
G_DEFINE_TYPE(FlMouseCursorPlugin, fl_mouse_cursor_plugin, G_TYPE_OBJECT)
// Insert a new entry into a hashtable from strings to strings.
//
// Returns whether the newly added value was already in the hash table or not.
static bool define_system_cursor(GHashTable* table,
const gchar* key,
const gchar* value) {
return g_hash_table_insert(
table, reinterpret_cast<gpointer>(const_cast<gchar*>(key)),
reinterpret_cast<gpointer>(const_cast<gchar*>(value)));
}
// Populate the hash table so that it maps from Flutter's cursor kinds to GTK's
// cursor values.
//
// The table must have been created as a hashtable from strings to strings.
static void populate_system_cursor_table(GHashTable* table) {
// The following mapping must be kept in sync with Flutter framework's
// mouse_cursor.dart.
define_system_cursor(table, "alias", "alias");
define_system_cursor(table, "allScroll", "all-scroll");
define_system_cursor(table, "basic", "default");
define_system_cursor(table, "cell", "cell");
define_system_cursor(table, "click", "pointer");
define_system_cursor(table, "contextMenu", "context-menu");
define_system_cursor(table, "copy", "copy");
define_system_cursor(table, "forbidden", "not-allowed");
define_system_cursor(table, "grab", "grab");
define_system_cursor(table, "grabbing", "grabbing");
define_system_cursor(table, "help", "help");
define_system_cursor(table, "move", "move");
define_system_cursor(table, "none", "none");
define_system_cursor(table, "noDrop", "no-drop");
define_system_cursor(table, "precise", "crosshair");
define_system_cursor(table, "progress", "progress");
define_system_cursor(table, "text", "text");
define_system_cursor(table, "resizeColumn", "col-resize");
define_system_cursor(table, "resizeDown", "s-resize");
define_system_cursor(table, "resizeDownLeft", "sw-resize");
define_system_cursor(table, "resizeDownRight", "se-resize");
define_system_cursor(table, "resizeLeft", "w-resize");
define_system_cursor(table, "resizeLeftRight", "ew-resize");
define_system_cursor(table, "resizeRight", "e-resize");
define_system_cursor(table, "resizeRow", "row-resize");
define_system_cursor(table, "resizeUp", "n-resize");
define_system_cursor(table, "resizeUpDown", "ns-resize");
define_system_cursor(table, "resizeUpLeft", "nw-resize");
define_system_cursor(table, "resizeUpRight", "ne-resize");
define_system_cursor(table, "resizeUpLeftDownRight", "nwse-resize");
define_system_cursor(table, "resizeUpRightDownLeft", "nesw-resize");
define_system_cursor(table, "verticalText", "vertical-text");
define_system_cursor(table, "wait", "wait");
define_system_cursor(table, "zoomIn", "zoom-in");
define_system_cursor(table, "zoomOut", "zoom-out");
}
// Sets the mouse cursor.
FlMethodResponse* activate_system_cursor(FlMouseCursorPlugin* self,
FlValue* args) {
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* kind_value = fl_value_lookup_string(args, kKindKey);
const gchar* kind = nullptr;
if (fl_value_get_type(kind_value) == FL_VALUE_TYPE_STRING) {
kind = fl_value_get_string(kind_value);
}
if (self->system_cursor_table == nullptr) {
self->system_cursor_table = g_hash_table_new(g_str_hash, g_str_equal);
populate_system_cursor_table(self->system_cursor_table);
}
const gchar* cursor_name = reinterpret_cast<const gchar*>(
g_hash_table_lookup(self->system_cursor_table, kind));
if (cursor_name == nullptr) {
cursor_name = kFallbackCursor;
}
GdkWindow* window =
gtk_widget_get_window(gtk_widget_get_toplevel(GTK_WIDGET(self->view)));
g_autoptr(GdkCursor) cursor =
gdk_cursor_new_from_name(gdk_window_get_display(window), cursor_name);
gdk_window_set_cursor(window, cursor);
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
}
// Called when a method call is received from Flutter.
static void method_call_cb(FlMethodChannel* channel,
FlMethodCall* method_call,
gpointer user_data) {
FlMouseCursorPlugin* self = FL_MOUSE_CURSOR_PLUGIN(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, kActivateSystemCursorMethod) == 0) {
response = activate_system_cursor(self, args);
} else {
response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
}
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_mouse_cursor_plugin_dispose(GObject* object) {
FlMouseCursorPlugin* self = FL_MOUSE_CURSOR_PLUGIN(object);
g_clear_object(&self->channel);
if (self->view != nullptr) {
g_object_remove_weak_pointer(G_OBJECT(self->view),
reinterpret_cast<gpointer*>(&(self->view)));
self->view = nullptr;
}
g_clear_pointer(&self->system_cursor_table, g_hash_table_unref);
G_OBJECT_CLASS(fl_mouse_cursor_plugin_parent_class)->dispose(object);
}
static void fl_mouse_cursor_plugin_class_init(FlMouseCursorPluginClass* klass) {
G_OBJECT_CLASS(klass)->dispose = fl_mouse_cursor_plugin_dispose;
}
static void fl_mouse_cursor_plugin_init(FlMouseCursorPlugin* self) {}
FlMouseCursorPlugin* fl_mouse_cursor_plugin_new(FlBinaryMessenger* messenger,
FlView* view) {
g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr);
FlMouseCursorPlugin* self = FL_MOUSE_CURSOR_PLUGIN(
g_object_new(fl_mouse_cursor_plugin_get_type(), nullptr));
g_autoptr(FlStandardMethodCodec) codec = fl_standard_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);
self->view = view;
if (view != nullptr) {
g_object_add_weak_pointer(G_OBJECT(view),
reinterpret_cast<gpointer*>(&(self->view)));
}
return self;
}