fl_view.cc 12.7 KB
Newer Older
1 2 3 4 5 6
// 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/public/flutter_linux/fl_view.h"

7
#include "flutter/shell/platform/linux/fl_engine_private.h"
R
Robert Ancell 已提交
8
#include "flutter/shell/platform/linux/fl_key_event_plugin.h"
9
#include "flutter/shell/platform/linux/fl_plugin_registrar_private.h"
10
#include "flutter/shell/platform/linux/fl_renderer_x11.h"
R
Robert Ancell 已提交
11
#include "flutter/shell/platform/linux/fl_text_input_plugin.h"
12
#include "flutter/shell/platform/linux/public/flutter_linux/fl_engine.h"
13
#include "flutter/shell/platform/linux/public/flutter_linux/fl_plugin_registry.h"
14

15 16
#include <gdk/gdkx.h>

17
static constexpr int kMicrosecondsPerMillisecond = 1000;
18 19 20 21

struct _FlView {
  GtkWidget parent_instance;

22
  // Project being run.
23
  FlDartProject* project;
24

25
  // Rendering output.
26
  FlRendererX11* renderer;
27

28
  // Engine running @project.
29
  FlEngine* engine;
30

31
  // Pointer button state recorded for sending status updates.
32
  int64_t button_state;
R
Robert Ancell 已提交
33 34 35

  // Flutter system channel handlers.
  FlKeyEventPlugin* key_event_plugin;
R
Robert Ancell 已提交
36
  FlTextInputPlugin* text_input_plugin;
37 38 39 40
};

enum { PROP_FLUTTER_PROJECT = 1, PROP_LAST };

41 42 43 44 45 46 47 48 49
static void fl_view_plugin_registry_iface_init(
    FlPluginRegistryInterface* iface);

G_DEFINE_TYPE_WITH_CODE(
    FlView,
    fl_view,
    GTK_TYPE_WIDGET,
    G_IMPLEMENT_INTERFACE(fl_plugin_registry_get_type(),
                          fl_view_plugin_registry_iface_init))
50

R
Robert Ancell 已提交
51
// Converts a GDK button event into a Flutter event and sends it to the engine.
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
static gboolean fl_view_send_pointer_button_event(FlView* self,
                                                  GdkEventButton* event) {
  int64_t button;
  switch (event->button) {
    case 1:
      button = kFlutterPointerButtonMousePrimary;
      break;
    case 2:
      button = kFlutterPointerButtonMouseMiddle;
      break;
    case 3:
      button = kFlutterPointerButtonMouseSecondary;
      break;
    default:
      return FALSE;
  }
  int old_button_state = self->button_state;
69
  FlutterPointerPhase phase;
70
  if (event->type == GDK_BUTTON_PRESS) {
71
    // Drop the event if Flutter already thinks the button is down.
72 73 74 75
    if ((self->button_state & button) != 0)
      return FALSE;
    self->button_state ^= button;

76
    phase = old_button_state == 0 ? kDown : kMove;
77
  } else if (event->type == GDK_BUTTON_RELEASE) {
78
    // Drop the event if Flutter already thinks the button is up.
79 80 81 82
    if ((self->button_state & button) == 0)
      return FALSE;
    self->button_state ^= button;

83
    phase = self->button_state == 0 ? kUp : kMove;
84 85
  }

86
  if (self->engine == nullptr)
87 88
    return FALSE;

R
Robert Ancell 已提交
89 90 91
  gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(self));
  fl_engine_send_mouse_pointer_event(
      self->engine, phase, event->time * kMicrosecondsPerMillisecond,
92 93 94
      event->x * scale_factor, event->y * scale_factor, 0, 0,
      self->button_state);

95 96 97
  return TRUE;
}

R
Robert Ancell 已提交
98 99 100 101 102 103 104 105 106 107
// Updates the engine with the current window metrics.
static void fl_view_send_window_metrics(FlView* self) {
  GtkAllocation allocation;
  gtk_widget_get_allocation(GTK_WIDGET(self), &allocation);
  gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(self));
  fl_engine_send_window_metrics_event(
      self->engine, allocation.width * scale_factor,
      allocation.height * scale_factor, scale_factor);
}

108
// Implements FlPluginRegistry::get_registrar_for_plugin.
109 110 111 112 113 114 115 116 117 118 119 120 121 122
static FlPluginRegistrar* fl_view_get_registrar_for_plugin(
    FlPluginRegistry* registry,
    const gchar* name) {
  FlView* self = FL_VIEW(registry);

  return fl_plugin_registrar_new(self,
                                 fl_engine_get_binary_messenger(self->engine));
}

static void fl_view_plugin_registry_iface_init(
    FlPluginRegistryInterface* iface) {
  iface->get_registrar_for_plugin = fl_view_get_registrar_for_plugin;
}

123 124 125 126 127
static void fl_view_constructed(GObject* object) {
  FlView* self = FL_VIEW(object);

  self->renderer = fl_renderer_x11_new();
  self->engine = fl_engine_new(self->project, FL_RENDERER(self->renderer));
R
Robert Ancell 已提交
128

129
  // Create system channel handlers.
R
Robert Ancell 已提交
130 131
  FlBinaryMessenger* messenger = fl_engine_get_binary_messenger(self->engine);
  self->key_event_plugin = fl_key_event_plugin_new(messenger);
R
Robert Ancell 已提交
132
  self->text_input_plugin = fl_text_input_plugin_new(messenger);
133 134
}

135 136 137 138 139 140 141 142
static void fl_view_set_property(GObject* object,
                                 guint prop_id,
                                 const GValue* value,
                                 GParamSpec* pspec) {
  FlView* self = FL_VIEW(object);

  switch (prop_id) {
    case PROP_FLUTTER_PROJECT:
143
      g_set_object(&self->project,
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
                   static_cast<FlDartProject*>(g_value_get_object(value)));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
      break;
  }
}

static void fl_view_get_property(GObject* object,
                                 guint prop_id,
                                 GValue* value,
                                 GParamSpec* pspec) {
  FlView* self = FL_VIEW(object);

  switch (prop_id) {
    case PROP_FLUTTER_PROJECT:
160
      g_value_set_object(value, self->project);
161 162 163 164 165 166 167
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
      break;
  }
}

R
Robert Ancell 已提交
168 169 170 171 172 173 174 175 176 177 178
static void fl_view_notify(GObject* object, GParamSpec* pspec) {
  FlView* self = FL_VIEW(object);

  if (strcmp(pspec->name, "scale-factor") == 0) {
    fl_view_send_window_metrics(self);
  }

  if (G_OBJECT_CLASS(fl_view_parent_class)->notify != nullptr)
    G_OBJECT_CLASS(fl_view_parent_class)->notify(object, pspec);
}

179 180 181
static void fl_view_dispose(GObject* object) {
  FlView* self = FL_VIEW(object);

182
  g_clear_object(&self->project);
183
  g_clear_object(&self->renderer);
184
  g_clear_object(&self->engine);
R
Robert Ancell 已提交
185
  g_clear_object(&self->key_event_plugin);
R
Robert Ancell 已提交
186
  g_clear_object(&self->text_input_plugin);
187 188 189 190

  G_OBJECT_CLASS(fl_view_parent_class)->dispose(object);
}

191
// Implements GtkWidget::realize.
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
static void fl_view_realize(GtkWidget* widget) {
  FlView* self = FL_VIEW(widget);

  gtk_widget_set_realized(widget, TRUE);

  GtkAllocation allocation;
  gtk_widget_get_allocation(widget, &allocation);

  GdkWindowAttr window_attributes;
  window_attributes.window_type = GDK_WINDOW_CHILD;
  window_attributes.x = allocation.x;
  window_attributes.y = allocation.y;
  window_attributes.width = allocation.width;
  window_attributes.height = allocation.height;
  window_attributes.wclass = GDK_INPUT_OUTPUT;
  window_attributes.visual = gtk_widget_get_visual(widget);
  window_attributes.event_mask =
209
      gtk_widget_get_events(widget) | GDK_EXPOSURE_MASK |
R
Robert Ancell 已提交
210
      GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK |
211 212
      GDK_BUTTON_RELEASE_MASK | GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK |
      GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK;
213 214 215 216 217 218 219 220 221

  gint window_attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;

  GdkWindow* window =
      gdk_window_new(gtk_widget_get_parent_window(widget), &window_attributes,
                     window_attributes_mask);
  gtk_widget_register_window(widget, window);
  gtk_widget_set_window(widget, window);

222
  Window xid = gdk_x11_window_get_xid(gtk_widget_get_window(GTK_WIDGET(self)));
223 224
  fl_renderer_x11_set_xid(self->renderer, xid);

225 226
  g_autoptr(GError) error = nullptr;
  if (!fl_engine_start(self->engine, &error))
227
    g_warning("Failed to start Flutter engine: %s", error->message);
228 229
}

230
// Implements GtkWidget::size-allocate.
231 232 233 234 235 236
static void fl_view_size_allocate(GtkWidget* widget,
                                  GtkAllocation* allocation) {
  FlView* self = FL_VIEW(widget);

  gtk_widget_set_allocation(widget, allocation);

237
  if (gtk_widget_get_realized(widget) && gtk_widget_get_has_window(widget)) {
238 239 240
    gdk_window_move_resize(gtk_widget_get_window(widget), allocation->x,
                           allocation->y, allocation->width,
                           allocation->height);
241
  }
242

R
Robert Ancell 已提交
243
  fl_view_send_window_metrics(self);
244 245
}

246
// Implements GtkWidget::button_press_event.
247 248 249 250
static gboolean fl_view_button_press_event(GtkWidget* widget,
                                           GdkEventButton* event) {
  FlView* self = FL_VIEW(widget);

251
  // Flutter doesn't handle double and triple click events.
252 253 254 255 256 257 258
  if (event->type == GDK_DOUBLE_BUTTON_PRESS ||
      event->type == GDK_TRIPLE_BUTTON_PRESS)
    return FALSE;

  return fl_view_send_pointer_button_event(self, event);
}

259
// Implements GtkWidget::button_release_event.
260 261 262 263 264 265 266
static gboolean fl_view_button_release_event(GtkWidget* widget,
                                             GdkEventButton* event) {
  FlView* self = FL_VIEW(widget);

  return fl_view_send_pointer_button_event(self, event);
}

267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
// Implements GtkWidget::scroll_event.
static gboolean fl_view_scroll_event(GtkWidget* widget, GdkEventScroll* event) {
  FlView* self = FL_VIEW(widget);

  // TODO(robert-ancell): Update to use GtkEventControllerScroll when we can
  // depend on GTK 3.24.

  gdouble scroll_delta_x = 0.0, scroll_delta_y = 0.0;
  if (event->direction == GDK_SCROLL_SMOOTH) {
    scroll_delta_x = event->delta_x;
    scroll_delta_y = event->delta_y;
  } else if (event->direction == GDK_SCROLL_UP) {
    scroll_delta_y = -1;
  } else if (event->direction == GDK_SCROLL_DOWN) {
    scroll_delta_y = 1;
  } else if (event->direction == GDK_SCROLL_LEFT) {
    scroll_delta_x = -1;
  } else if (event->direction == GDK_SCROLL_RIGHT) {
    scroll_delta_x = 1;
  }

  // TODO: See if this can be queried from the OS; this value is chosen
  // arbitrarily to get something that feels reasonable.
  const int kScrollOffsetMultiplier = 20;
  scroll_delta_x *= kScrollOffsetMultiplier;
  scroll_delta_y *= kScrollOffsetMultiplier;

  gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(self));
  fl_engine_send_mouse_pointer_event(
      self->engine, self->button_state != 0 ? kMove : kHover,
      event->time * kMicrosecondsPerMillisecond, event->x * scale_factor,
      event->y * scale_factor, scroll_delta_x, scroll_delta_y,
      self->button_state);

  return TRUE;
}

304
// Implements GtkWidget::motion_notify_event.
305 306 307 308
static gboolean fl_view_motion_notify_event(GtkWidget* widget,
                                            GdkEventMotion* event) {
  FlView* self = FL_VIEW(widget);

309
  if (self->engine == nullptr)
310 311
    return FALSE;

R
Robert Ancell 已提交
312 313 314 315
  gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(self));
  fl_engine_send_mouse_pointer_event(
      self->engine, self->button_state != 0 ? kMove : kHover,
      event->time * kMicrosecondsPerMillisecond, event->x * scale_factor,
316
      event->y * scale_factor, 0, 0, self->button_state);
317 318 319 320

  return TRUE;
}

321
// Implements GtkWidget::key_press_event.
R
Robert Ancell 已提交
322 323 324
static gboolean fl_view_key_press_event(GtkWidget* widget, GdkEventKey* event) {
  FlView* self = FL_VIEW(widget);

R
Robert Ancell 已提交
325 326 327
  if (fl_text_input_plugin_filter_keypress(self->text_input_plugin, event))
    return TRUE;

R
Robert Ancell 已提交
328 329 330 331 332
  fl_key_event_plugin_send_key_event(self->key_event_plugin, event);

  return TRUE;
}

333
// Implements GtkWidget::key_release_event.
R
Robert Ancell 已提交
334 335 336 337
static gboolean fl_view_key_release_event(GtkWidget* widget,
                                          GdkEventKey* event) {
  FlView* self = FL_VIEW(widget);

R
Robert Ancell 已提交
338 339 340
  if (fl_text_input_plugin_filter_keypress(self->text_input_plugin, event))
    return TRUE;

R
Robert Ancell 已提交
341 342 343 344 345
  fl_key_event_plugin_send_key_event(self->key_event_plugin, event);

  return TRUE;
}

346
static void fl_view_class_init(FlViewClass* klass) {
347
  G_OBJECT_CLASS(klass)->constructed = fl_view_constructed;
348 349
  G_OBJECT_CLASS(klass)->set_property = fl_view_set_property;
  G_OBJECT_CLASS(klass)->get_property = fl_view_get_property;
R
Robert Ancell 已提交
350
  G_OBJECT_CLASS(klass)->notify = fl_view_notify;
351 352 353
  G_OBJECT_CLASS(klass)->dispose = fl_view_dispose;
  GTK_WIDGET_CLASS(klass)->realize = fl_view_realize;
  GTK_WIDGET_CLASS(klass)->size_allocate = fl_view_size_allocate;
354 355
  GTK_WIDGET_CLASS(klass)->button_press_event = fl_view_button_press_event;
  GTK_WIDGET_CLASS(klass)->button_release_event = fl_view_button_release_event;
356
  GTK_WIDGET_CLASS(klass)->scroll_event = fl_view_scroll_event;
357
  GTK_WIDGET_CLASS(klass)->motion_notify_event = fl_view_motion_notify_event;
R
Robert Ancell 已提交
358 359
  GTK_WIDGET_CLASS(klass)->key_press_event = fl_view_key_press_event;
  GTK_WIDGET_CLASS(klass)->key_release_event = fl_view_key_release_event;
360 361 362 363 364 365 366 367 368 369

  g_object_class_install_property(
      G_OBJECT_CLASS(klass), PROP_FLUTTER_PROJECT,
      g_param_spec_object(
          "flutter-project", "flutter-project", "Flutter project in use",
          fl_dart_project_get_type(),
          static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
                                   G_PARAM_STATIC_STRINGS)));
}

R
Robert Ancell 已提交
370 371 372
static void fl_view_init(FlView* self) {
  gtk_widget_set_can_focus(GTK_WIDGET(self), TRUE);
}
373 374 375 376 377

G_MODULE_EXPORT FlView* fl_view_new(FlDartProject* project) {
  return static_cast<FlView*>(
      g_object_new(fl_view_get_type(), "flutter-project", project, nullptr));
}
378 379 380 381 382

G_MODULE_EXPORT FlEngine* fl_view_get_engine(FlView* view) {
  g_return_val_if_fail(FL_IS_VIEW(view), nullptr);
  return view->engine;
}