1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/ui/gtk/apps/native_app_window_gtk.h"
6
7#include <gdk/gdkx.h>
8#include <vector>
9
10#include "base/message_loop/message_pump_gtk.h"
11#include "base/strings/utf_string_conversions.h"
12#include "chrome/browser/profiles/profile.h"
13#include "chrome/browser/ui/gtk/extensions/extension_keybinding_registry_gtk.h"
14#include "chrome/browser/ui/gtk/gtk_util.h"
15#include "chrome/browser/ui/gtk/gtk_window_util.h"
16#include "chrome/browser/web_applications/web_app.h"
17#include "chrome/common/extensions/extension.h"
18#include "content/public/browser/render_view_host.h"
19#include "content/public/browser/render_widget_host_view.h"
20#include "content/public/browser/web_contents.h"
21#include "content/public/browser/web_contents_view.h"
22#include "ui/base/x/active_window_watcher_x.h"
23#include "ui/gfx/gtk_util.h"
24#include "ui/gfx/image/image.h"
25#include "ui/gfx/rect.h"
26
27using apps::ShellWindow;
28
29namespace {
30
31// The timeout in milliseconds before we'll get the true window position with
32// gtk_window_get_position() after the last GTK configure-event signal.
33const int kDebounceTimeoutMilliseconds = 100;
34
35const char* kAtomsToCache[] = {
36  "_NET_WM_STATE",
37  "_NET_WM_STATE_HIDDEN",
38  NULL
39};
40
41} // namespace
42
43NativeAppWindowGtk::NativeAppWindowGtk(ShellWindow* shell_window,
44                                       const ShellWindow::CreateParams& params)
45    : shell_window_(shell_window),
46      window_(NULL),
47      state_(GDK_WINDOW_STATE_WITHDRAWN),
48      is_active_(false),
49      content_thinks_its_fullscreen_(false),
50      frameless_(params.frame == ShellWindow::FRAME_NONE),
51      frame_cursor_(NULL),
52      atom_cache_(base::MessagePumpGtk::GetDefaultXDisplay(), kAtomsToCache),
53      is_x_event_listened_(false) {
54  window_ = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
55
56  gfx::NativeView native_view =
57      web_contents()->GetView()->GetNativeView();
58  gtk_container_add(GTK_CONTAINER(window_), native_view);
59
60  if (params.bounds.x() != INT_MIN && params.bounds.y() != INT_MIN)
61    gtk_window_move(window_, params.bounds.x(), params.bounds.y());
62
63  // This is done to avoid a WM "feature" where setting the window size to
64  // the monitor size causes the WM to set the EWMH for full screen mode.
65  int win_height = params.bounds.height();
66  if (frameless_ &&
67      gtk_window_util::BoundsMatchMonitorSize(window_, params.bounds)) {
68    win_height -= 1;
69  }
70  gtk_window_set_default_size(window_, params.bounds.width(), win_height);
71
72  resizable_ = params.resizable;
73  if (!resizable_) {
74    // If the window doesn't have a size request when we set resizable to
75    // false, GTK will shrink the window to 1x1px.
76    gtk_widget_set_size_request(GTK_WIDGET(window_),
77        params.bounds.width(), win_height);
78    gtk_window_set_resizable(window_, FALSE);
79  }
80
81  // make sure bounds_ and restored_bounds_ have correct values until we
82  // get our first configure-event
83  bounds_ = restored_bounds_ = params.bounds;
84  gint x, y;
85  gtk_window_get_position(window_, &x, &y);
86  bounds_.set_origin(gfx::Point(x, y));
87
88  // Hide titlebar when {frame: 'none'} specified on ShellWindow.
89  if (frameless_)
90    gtk_window_set_decorated(window_, false);
91
92  int min_width = params.minimum_size.width();
93  int min_height = params.minimum_size.height();
94  int max_width = params.maximum_size.width();
95  int max_height = params.maximum_size.height();
96  GdkGeometry hints;
97  int hints_mask = 0;
98  if (min_width || min_height) {
99    hints.min_height = min_height;
100    hints.min_width = min_width;
101    hints_mask |= GDK_HINT_MIN_SIZE;
102  }
103  if (max_width || max_height) {
104    hints.max_height = max_height ? max_height : G_MAXINT;
105    hints.max_width = max_width ? max_width : G_MAXINT;
106    hints_mask |= GDK_HINT_MAX_SIZE;
107  }
108  if (hints_mask) {
109    gtk_window_set_geometry_hints(
110        window_,
111        GTK_WIDGET(window_),
112        &hints,
113        static_cast<GdkWindowHints>(hints_mask));
114  }
115
116  // In some (older) versions of compiz, raising top-level windows when they
117  // are partially off-screen causes them to get snapped back on screen, not
118  // always even on the current virtual desktop.  If we are running under
119  // compiz, suppress such raises, as they are not necessary in compiz anyway.
120  if (ui::GuessWindowManager() == ui::WM_COMPIZ)
121    suppress_window_raise_ = true;
122
123  gtk_window_set_title(window_, extension()->name().c_str());
124
125  std::string app_name = web_app::GenerateApplicationNameFromExtensionId(
126      extension()->id());
127  gtk_window_util::SetWindowCustomClass(window_,
128      web_app::GetWMClassFromAppName(app_name));
129
130  g_signal_connect(window_, "delete-event",
131                   G_CALLBACK(OnMainWindowDeleteEventThunk), this);
132  g_signal_connect(window_, "configure-event",
133                   G_CALLBACK(OnConfigureThunk), this);
134  g_signal_connect(window_, "window-state-event",
135                   G_CALLBACK(OnWindowStateThunk), this);
136  if (frameless_) {
137    g_signal_connect(window_, "button-press-event",
138                     G_CALLBACK(OnButtonPressThunk), this);
139    g_signal_connect(window_, "motion-notify-event",
140                     G_CALLBACK(OnMouseMoveEventThunk), this);
141  }
142
143  // If _NET_WM_STATE_HIDDEN is in _NET_SUPPORTED, listen for XEvent to work
144  // around GTK+ not reporting minimization state changes. See comment in the
145  // |OnXEvent|.
146  std::vector< ::Atom> supported_atoms;
147  if (ui::GetAtomArrayProperty(ui::GetX11RootWindow(),
148                               "_NET_SUPPORTED",
149                               &supported_atoms)) {
150    if (std::find(supported_atoms.begin(),
151                  supported_atoms.end(),
152                  atom_cache_.GetAtom("_NET_WM_STATE_HIDDEN")) !=
153        supported_atoms.end()) {
154      GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(window_));
155      gdk_window_add_filter(window,
156                            &NativeAppWindowGtk::OnXEventThunk,
157                            this);
158      is_x_event_listened_ = true;
159    }
160  }
161
162  // Add the keybinding registry.
163  extension_keybinding_registry_.reset(new ExtensionKeybindingRegistryGtk(
164      shell_window_->profile(),
165      window_,
166      extensions::ExtensionKeybindingRegistry::PLATFORM_APPS_ONLY,
167      shell_window_));
168
169  ui::ActiveWindowWatcherX::AddObserver(this);
170}
171
172NativeAppWindowGtk::~NativeAppWindowGtk() {
173  ui::ActiveWindowWatcherX::RemoveObserver(this);
174  if (is_x_event_listened_) {
175    gdk_window_remove_filter(NULL,
176                             &NativeAppWindowGtk::OnXEventThunk,
177                             this);
178  }
179}
180
181bool NativeAppWindowGtk::IsActive() const {
182  if (ui::ActiveWindowWatcherX::WMSupportsActivation())
183    return is_active_;
184
185  // This still works even though we don't get the activation notification.
186  return gtk_window_is_active(window_);
187}
188
189bool NativeAppWindowGtk::IsMaximized() const {
190  return (state_ & GDK_WINDOW_STATE_MAXIMIZED);
191}
192
193bool NativeAppWindowGtk::IsMinimized() const {
194  return (state_ & GDK_WINDOW_STATE_ICONIFIED);
195}
196
197bool NativeAppWindowGtk::IsFullscreen() const {
198  return (state_ & GDK_WINDOW_STATE_FULLSCREEN);
199}
200
201gfx::NativeWindow NativeAppWindowGtk::GetNativeWindow() {
202  return window_;
203}
204
205gfx::Rect NativeAppWindowGtk::GetRestoredBounds() const {
206  gfx::Rect window_bounds = restored_bounds_;
207  window_bounds.Inset(-GetFrameInsets());
208  return window_bounds;
209}
210
211ui::WindowShowState NativeAppWindowGtk::GetRestoredState() const {
212  if (IsMaximized())
213    return ui::SHOW_STATE_MAXIMIZED;
214  return ui::SHOW_STATE_NORMAL;
215}
216
217gfx::Rect NativeAppWindowGtk::GetBounds() const {
218  gfx::Rect window_bounds = bounds_;
219  window_bounds.Inset(-GetFrameInsets());
220  return window_bounds;
221}
222
223void NativeAppWindowGtk::Show() {
224  gtk_window_present(window_);
225}
226
227void NativeAppWindowGtk::ShowInactive() {
228  gtk_window_set_focus_on_map(window_, false);
229  gtk_widget_show(GTK_WIDGET(window_));
230}
231
232void NativeAppWindowGtk::Hide() {
233  gtk_widget_hide(GTK_WIDGET(window_));
234}
235
236void NativeAppWindowGtk::Close() {
237  shell_window_->OnNativeWindowChanged();
238
239  // Cancel any pending callback from the window configure debounce timer.
240  window_configure_debounce_timer_.Stop();
241
242  GtkWidget* window = GTK_WIDGET(window_);
243  // To help catch bugs in any event handlers that might get fired during the
244  // destruction, set window_ to NULL before any handlers will run.
245  window_ = NULL;
246
247  // OnNativeClose does a delete this so no other members should
248  // be accessed after. gtk_widget_destroy is safe (and must
249  // be last).
250  shell_window_->OnNativeClose();
251  gtk_widget_destroy(window);
252}
253
254void NativeAppWindowGtk::Activate() {
255  gtk_window_present(window_);
256}
257
258void NativeAppWindowGtk::Deactivate() {
259  gdk_window_lower(gtk_widget_get_window(GTK_WIDGET(window_)));
260}
261
262void NativeAppWindowGtk::Maximize() {
263  // Represent the window first in order to keep the maximization behavior
264  // consistency with Windows platform. Otherwise the window will be hidden if
265  // it has been minimized.
266  gtk_window_present(window_);
267  gtk_window_maximize(window_);
268}
269
270void NativeAppWindowGtk::Minimize() {
271  gtk_window_iconify(window_);
272}
273
274void NativeAppWindowGtk::Restore() {
275  if (IsMaximized())
276    gtk_window_unmaximize(window_);
277  else if (IsMinimized())
278    gtk_window_deiconify(window_);
279
280  // Represent the window to keep restoration behavior consistency with Windows
281  // platform.
282  // TODO(zhchbin): verify whether we need this until http://crbug.com/261013 is
283  // fixed.
284  gtk_window_present(window_);
285}
286
287void NativeAppWindowGtk::SetBounds(const gfx::Rect& bounds) {
288  gfx::Rect content_bounds = bounds;
289  content_bounds.Inset(GetFrameInsets());
290  gtk_window_move(window_, content_bounds.x(), content_bounds.y());
291  if (!resizable_) {
292    if (frameless_ &&
293        gtk_window_util::BoundsMatchMonitorSize(window_, content_bounds)) {
294      content_bounds.set_height(content_bounds.height() - 1);
295    }
296    // TODO(jeremya): set_size_request doesn't honor min/max size, so the
297    // bounds should be constrained manually.
298    gtk_widget_set_size_request(GTK_WIDGET(window_),
299        content_bounds.width(), content_bounds.height());
300  } else {
301    gtk_window_util::SetWindowSize(window_,
302        gfx::Size(bounds.width(), bounds.height()));
303  }
304}
305
306GdkFilterReturn NativeAppWindowGtk::OnXEvent(GdkXEvent* gdk_x_event,
307                                             GdkEvent* gdk_event) {
308  // Work around GTK+ not reporting minimization state changes. Listen
309  // for _NET_WM_STATE property changes and use _NET_WM_STATE_HIDDEN's
310  // presence to set or clear the iconified bit if _NET_WM_STATE_HIDDEN
311  // is supported. http://crbug.com/162794.
312  XEvent* x_event = static_cast<XEvent*>(gdk_x_event);
313  std::vector< ::Atom> atom_list;
314
315  if (x_event->type == PropertyNotify &&
316      x_event->xproperty.atom == atom_cache_.GetAtom("_NET_WM_STATE") &&
317      ui::GetAtomArrayProperty(GDK_WINDOW_XWINDOW(GTK_WIDGET(window_)->window),
318                               "_NET_WM_STATE",
319                               &atom_list)) {
320    std::vector< ::Atom>::iterator it =
321        std::find(atom_list.begin(),
322                  atom_list.end(),
323                  atom_cache_.GetAtom("_NET_WM_STATE_HIDDEN"));
324    state_ = (it != atom_list.end()) ? GDK_WINDOW_STATE_ICONIFIED :
325        static_cast<GdkWindowState>(state_ & ~GDK_WINDOW_STATE_ICONIFIED);
326
327    shell_window_->OnNativeWindowChanged();
328  }
329
330  return GDK_FILTER_CONTINUE;
331}
332
333void NativeAppWindowGtk::FlashFrame(bool flash) {
334  gtk_window_set_urgency_hint(window_, flash);
335}
336
337bool NativeAppWindowGtk::IsAlwaysOnTop() const {
338  return false;
339}
340
341void NativeAppWindowGtk::RenderViewHostChanged() {
342  web_contents()->GetView()->Focus();
343}
344
345gfx::Insets NativeAppWindowGtk::GetFrameInsets() const {
346  if (frameless_)
347    return gfx::Insets();
348  GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window_));
349  if (!gdk_window)
350    return gfx::Insets();
351
352  gint current_width = 0;
353  gint current_height = 0;
354  gtk_window_get_size(window_, &current_width, &current_height);
355  gint current_x = 0;
356  gint current_y = 0;
357  gdk_window_get_position(gdk_window, &current_x, &current_y);
358  GdkRectangle rect_with_decorations = {0};
359  gdk_window_get_frame_extents(gdk_window,
360                               &rect_with_decorations);
361
362  int left_inset = current_x - rect_with_decorations.x;
363  int top_inset = current_y - rect_with_decorations.y;
364  return gfx::Insets(
365      top_inset,
366      left_inset,
367      rect_with_decorations.height - current_height - top_inset,
368      rect_with_decorations.width - current_width - left_inset);
369}
370
371gfx::NativeView NativeAppWindowGtk::GetHostView() const {
372  NOTIMPLEMENTED();
373  return NULL;
374}
375
376gfx::Point NativeAppWindowGtk::GetDialogPosition(const gfx::Size& size) {
377  gint current_width = 0;
378  gint current_height = 0;
379  gtk_window_get_size(window_, &current_width, &current_height);
380  return gfx::Point(current_width / 2 - size.width() / 2,
381                    current_height / 2 - size.height() / 2);
382}
383
384void NativeAppWindowGtk::AddObserver(
385    web_modal::WebContentsModalDialogHostObserver* observer) {
386  observer_list_.AddObserver(observer);
387}
388
389void NativeAppWindowGtk::RemoveObserver(
390    web_modal::WebContentsModalDialogHostObserver* observer) {
391  observer_list_.RemoveObserver(observer);
392}
393
394void NativeAppWindowGtk::ActiveWindowChanged(GdkWindow* active_window) {
395  // Do nothing if we're in the process of closing the browser window.
396  if (!window_)
397    return;
398
399  is_active_ = gtk_widget_get_window(GTK_WIDGET(window_)) == active_window;
400  if (is_active_)
401    shell_window_->OnNativeWindowActivated();
402}
403
404// Callback for the delete event.  This event is fired when the user tries to
405// close the window (e.g., clicking on the X in the window manager title bar).
406gboolean NativeAppWindowGtk::OnMainWindowDeleteEvent(GtkWidget* widget,
407                                                     GdkEvent* event) {
408  Close();
409
410  // Return true to prevent the GTK window from being destroyed.  Close will
411  // destroy it for us.
412  return TRUE;
413}
414
415gboolean NativeAppWindowGtk::OnConfigure(GtkWidget* widget,
416                                         GdkEventConfigure* event) {
417  // We update |bounds_| but not |restored_bounds_| here.  The latter needs
418  // to be updated conditionally when the window is non-maximized and non-
419  // fullscreen, but whether those state updates have been processed yet is
420  // window-manager specific.  We update |restored_bounds_| in the debounced
421  // handler below, after the window state has been updated.
422  bounds_.SetRect(event->x, event->y, event->width, event->height);
423
424  // The GdkEventConfigure* we get here doesn't have quite the right
425  // coordinates though (they're relative to the drawable window area, rather
426  // than any window manager decorations, if enabled), so we need to call
427  // gtk_window_get_position() to get the right values. (Otherwise session
428  // restore, if enabled, will restore windows to incorrect positions.) That's
429  // a round trip to the X server though, so we set a debounce timer and only
430  // call it (in OnConfigureDebounced() below) after we haven't seen a
431  // reconfigure event in a short while.
432  // We don't use Reset() because the timer may not yet be running.
433  // (In that case Stop() is a no-op.)
434  window_configure_debounce_timer_.Stop();
435  window_configure_debounce_timer_.Start(FROM_HERE,
436      base::TimeDelta::FromMilliseconds(kDebounceTimeoutMilliseconds), this,
437      &NativeAppWindowGtk::OnConfigureDebounced);
438
439  return FALSE;
440}
441
442void NativeAppWindowGtk::OnConfigureDebounced() {
443  gtk_window_util::UpdateWindowPosition(this, &bounds_, &restored_bounds_);
444  shell_window_->OnNativeWindowChanged();
445
446  FOR_EACH_OBSERVER(web_modal::WebContentsModalDialogHostObserver,
447                    observer_list_,
448                    OnPositionRequiresUpdate());
449
450  // Fullscreen of non-resizable windows requires them to be made resizable
451  // first. After that takes effect and OnConfigure is called we transition
452  // to fullscreen.
453  if (!IsFullscreen() && IsFullscreenOrPending()) {
454    gtk_window_fullscreen(window_);
455  }
456}
457
458gboolean NativeAppWindowGtk::OnWindowState(GtkWidget* sender,
459                                           GdkEventWindowState* event) {
460  state_ = event->new_window_state;
461
462  if (content_thinks_its_fullscreen_ &&
463      !(state_ & GDK_WINDOW_STATE_FULLSCREEN)) {
464    content_thinks_its_fullscreen_ = false;
465    content::RenderViewHost* rvh = web_contents()->GetRenderViewHost();
466    if (rvh)
467      rvh->ExitFullscreen();
468  }
469
470  return FALSE;
471}
472
473bool NativeAppWindowGtk::GetWindowEdge(int x, int y, GdkWindowEdge* edge) {
474  if (!frameless_)
475    return false;
476
477  if (IsMaximized() || IsFullscreen())
478    return false;
479
480  return gtk_window_util::GetWindowEdge(bounds_.size(), 0, x, y, edge);
481}
482
483gboolean NativeAppWindowGtk::OnMouseMoveEvent(GtkWidget* widget,
484                                              GdkEventMotion* event) {
485  if (!frameless_) {
486    // Reset the cursor.
487    if (frame_cursor_) {
488      frame_cursor_ = NULL;
489      gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(window_)), NULL);
490    }
491    return FALSE;
492  }
493
494  if (!resizable_)
495    return FALSE;
496
497  // Update the cursor if we're on the custom frame border.
498  GdkWindowEdge edge;
499  bool has_hit_edge = GetWindowEdge(static_cast<int>(event->x),
500                                    static_cast<int>(event->y), &edge);
501  GdkCursorType new_cursor = GDK_LAST_CURSOR;
502  if (has_hit_edge)
503    new_cursor = gtk_window_util::GdkWindowEdgeToGdkCursorType(edge);
504
505  GdkCursorType last_cursor = GDK_LAST_CURSOR;
506  if (frame_cursor_)
507    last_cursor = frame_cursor_->type;
508
509  if (last_cursor != new_cursor) {
510    frame_cursor_ = has_hit_edge ? gfx::GetCursor(new_cursor) : NULL;
511    gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(window_)),
512                          frame_cursor_);
513  }
514  return FALSE;
515}
516
517gboolean NativeAppWindowGtk::OnButtonPress(GtkWidget* widget,
518                                           GdkEventButton* event) {
519  DCHECK(frameless_);
520  // Make the button press coordinate relative to the browser window.
521  int win_x, win_y;
522  GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window_));
523  gdk_window_get_origin(gdk_window, &win_x, &win_y);
524
525  GdkWindowEdge edge;
526  gfx::Point point(static_cast<int>(event->x_root - win_x),
527                   static_cast<int>(event->y_root - win_y));
528  bool has_hit_edge = resizable_ && GetWindowEdge(point.x(), point.y(), &edge);
529  bool has_hit_titlebar =
530      draggable_region_ && draggable_region_->contains(event->x, event->y);
531
532  if (event->button == 1) {
533    if (GDK_BUTTON_PRESS == event->type) {
534      // Raise the window after a click on either the titlebar or the border to
535      // match the behavior of most window managers, unless that behavior has
536      // been suppressed.
537      if ((has_hit_titlebar || has_hit_edge) && !suppress_window_raise_)
538        gdk_window_raise(GTK_WIDGET(widget)->window);
539
540      if (has_hit_edge) {
541        gtk_window_begin_resize_drag(window_, edge, event->button,
542                                     static_cast<gint>(event->x_root),
543                                     static_cast<gint>(event->y_root),
544                                     event->time);
545        return TRUE;
546      } else if (has_hit_titlebar) {
547        return gtk_window_util::HandleTitleBarLeftMousePress(
548            window_, bounds_, event);
549      }
550    } else if (GDK_2BUTTON_PRESS == event->type) {
551      if (has_hit_titlebar && resizable_) {
552        // Maximize/restore on double click.
553        if (IsMaximized()) {
554          gtk_window_util::UnMaximize(GTK_WINDOW(widget),
555              bounds_, restored_bounds_);
556        } else {
557          gtk_window_maximize(window_);
558        }
559        return TRUE;
560      }
561    }
562  } else if (event->button == 2) {
563    if (has_hit_titlebar || has_hit_edge)
564      gdk_window_lower(gdk_window);
565    return TRUE;
566  }
567
568  return FALSE;
569}
570
571void NativeAppWindowGtk::SetFullscreen(bool fullscreen) {
572  content_thinks_its_fullscreen_ = fullscreen;
573  if (fullscreen){
574    if (resizable_) {
575      gtk_window_fullscreen(window_);
576    } else {
577      // We must first make the window resizable. That won't take effect
578      // immediately, so OnConfigureDebounced completes the fullscreen call.
579      gtk_window_set_resizable(window_, TRUE);
580    }
581  } else {
582    gtk_window_unfullscreen(window_);
583    if (!resizable_)
584      gtk_window_set_resizable(window_, FALSE);
585  }
586}
587
588bool NativeAppWindowGtk::IsFullscreenOrPending() const {
589  return content_thinks_its_fullscreen_;
590}
591
592bool NativeAppWindowGtk::IsDetached() const {
593  return false;
594}
595
596void NativeAppWindowGtk::UpdateWindowIcon() {
597  Profile* profile = shell_window_->profile();
598  gfx::Image app_icon = shell_window_->app_icon();
599  if (!app_icon.IsEmpty())
600    gtk_util::SetWindowIcon(window_, profile, app_icon.ToGdkPixbuf());
601  else
602    gtk_util::SetWindowIcon(window_, profile);
603}
604
605void NativeAppWindowGtk::UpdateWindowTitle() {
606  string16 title = shell_window_->GetTitle();
607  gtk_window_set_title(window_, UTF16ToUTF8(title).c_str());
608}
609
610void NativeAppWindowGtk::HandleKeyboardEvent(
611    const content::NativeWebKeyboardEvent& event) {
612  // No-op.
613}
614
615void NativeAppWindowGtk::UpdateDraggableRegions(
616    const std::vector<extensions::DraggableRegion>& regions) {
617  // Draggable region is not supported for non-frameless window.
618  if (!frameless_)
619    return;
620
621  draggable_region_.reset(ShellWindow::RawDraggableRegionsToSkRegion(regions));
622}
623