gecko/widget/gtk2/nsWindow.cpp

6212 lines
194 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim:expandtab:shiftwidth=4:tabstop=4:
*/
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/Util.h"
#include <algorithm>
#ifdef MOZ_PLATFORM_MAEMO
// needed to include hildon parts in gtk.h
#define MAEMO_CHANGES
#endif
#include "prlink.h"
#include "nsWindow.h"
#include "nsGTKToolkit.h"
#include "nsIRollupListener.h"
#include "nsIDOMNode.h"
#include "nsWidgetsCID.h"
#include "nsDragService.h"
#include "nsIWidgetListener.h"
#include "nsGtkKeyUtils.h"
#include "nsGtkCursors.h"
#include <gtk/gtk.h>
#if (MOZ_WIDGET_GTK == 3)
#include <gtk/gtkx.h>
#endif
#ifdef MOZ_X11
#include <gdk/gdkx.h>
#include <X11/Xatom.h>
#include <X11/extensions/XShm.h>
#include <X11/extensions/shape.h>
#if (MOZ_WIDGET_GTK == 3)
#include <gdk/gdkkeysyms-compat.h>
#endif
#ifdef AIX
#include <X11/keysym.h>
#else
#include <X11/XF86keysym.h>
#endif
#if (MOZ_WIDGET_GTK == 2)
#include "gtk2xtbin.h"
#endif
#endif /* MOZ_X11 */
#include <gdk/gdkkeysyms.h>
#if defined(MOZ_WIDGET_GTK2)
#include <gtk/gtkprivate.h>
#endif
#include "nsGkAtoms.h"
#ifdef MOZ_ENABLE_STARTUP_NOTIFICATION
#define SN_API_NOT_YET_FROZEN
#include <startup-notification-1.0/libsn/sn.h>
#endif
#include "mozilla/Likely.h"
#include "mozilla/Preferences.h"
#include "nsIPrefService.h"
#include "nsIGConfService.h"
#include "nsIServiceManager.h"
#include "nsIStringBundle.h"
#include "nsGfxCIID.h"
#include "nsIObserverService.h"
#include "LayersTypes.h"
#include "nsIIdleServiceInternal.h"
#include "nsIPropertyBag2.h"
#ifdef ACCESSIBILITY
#include "mozilla/a11y/Accessible.h"
#include "mozilla/a11y/Platform.h"
#include "nsAccessibilityService.h"
using namespace mozilla;
using namespace mozilla::widget;
#endif
/* For SetIcon */
#include "nsAppDirectoryServiceDefs.h"
#include "nsXPIDLString.h"
#include "nsIFile.h"
/* SetCursor(imgIContainer*) */
#include <gdk/gdk.h>
#include <wchar.h>
#include "imgIContainer.h"
#include "nsGfxCIID.h"
#include "nsImageToPixbuf.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsAutoPtr.h"
#include "BasicLayers.h"
extern "C" {
#define PIXMAN_DONT_DEFINE_STDINT
#include "pixman.h"
}
#include "gfxPlatformGtk.h"
#include "gfxContext.h"
#include "gfxImageSurface.h"
#include "gfxUtils.h"
#include "Layers.h"
#include "LayerManagerOGL.h"
#include "GLContextProvider.h"
#ifdef MOZ_X11
#include "gfxXlibSurface.h"
#include "cairo-xlib.h"
#endif
#include "nsShmImage.h"
#include "nsIDOMWheelEvent.h"
using namespace mozilla;
using namespace mozilla::widget;
using mozilla::gl::GLContext;
using mozilla::layers::LayerManagerOGL;
// Don't put more than this many rects in the dirty region, just fluff
// out to the bounding-box if there are more
#define MAX_RECTS_IN_REGION 100
const gint kEvents = GDK_EXPOSURE_MASK | GDK_STRUCTURE_MASK |
GDK_VISIBILITY_NOTIFY_MASK |
GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
#ifdef MOZ_PLATFORM_MAEMO
GDK_POINTER_MOTION_HINT_MASK |
#endif
GDK_POINTER_MOTION_MASK;
/* utility functions */
static bool is_mouse_in_window(GdkWindow* aWindow,
gdouble aMouseX, gdouble aMouseY);
static nsWindow *get_window_for_gtk_widget(GtkWidget *widget);
static nsWindow *get_window_for_gdk_window(GdkWindow *window);
static GtkWidget *get_gtk_widget_for_gdk_window(GdkWindow *window);
static GdkCursor *get_gtk_cursor(nsCursor aCursor);
static GdkWindow *get_inner_gdk_window (GdkWindow *aWindow,
gint x, gint y,
gint *retx, gint *rety);
static inline bool is_context_menu_key(const nsKeyEvent& inKeyEvent);
static int is_parent_ungrab_enter(GdkEventCrossing *aEvent);
static int is_parent_grab_leave(GdkEventCrossing *aEvent);
static void GetBrandName(nsXPIDLString& brandName);
/* callbacks from widgets */
#if defined(MOZ_WIDGET_GTK2)
static gboolean expose_event_cb (GtkWidget *widget,
GdkEventExpose *event);
#else
static gboolean expose_event_cb (GtkWidget *widget,
cairo_t *rect);
#endif
static gboolean configure_event_cb (GtkWidget *widget,
GdkEventConfigure *event);
static void container_unrealize_cb (GtkWidget *widget);
static void size_allocate_cb (GtkWidget *widget,
GtkAllocation *allocation);
static gboolean delete_event_cb (GtkWidget *widget,
GdkEventAny *event);
static gboolean enter_notify_event_cb (GtkWidget *widget,
GdkEventCrossing *event);
static gboolean leave_notify_event_cb (GtkWidget *widget,
GdkEventCrossing *event);
static gboolean motion_notify_event_cb (GtkWidget *widget,
GdkEventMotion *event);
static gboolean button_press_event_cb (GtkWidget *widget,
GdkEventButton *event);
static gboolean button_release_event_cb (GtkWidget *widget,
GdkEventButton *event);
static gboolean focus_in_event_cb (GtkWidget *widget,
GdkEventFocus *event);
static gboolean focus_out_event_cb (GtkWidget *widget,
GdkEventFocus *event);
static gboolean key_press_event_cb (GtkWidget *widget,
GdkEventKey *event);
static gboolean key_release_event_cb (GtkWidget *widget,
GdkEventKey *event);
static gboolean scroll_event_cb (GtkWidget *widget,
GdkEventScroll *event);
static gboolean visibility_notify_event_cb(GtkWidget *widget,
GdkEventVisibility *event);
static void hierarchy_changed_cb (GtkWidget *widget,
GtkWidget *previous_toplevel);
static gboolean window_state_event_cb (GtkWidget *widget,
GdkEventWindowState *event);
static void theme_changed_cb (GtkSettings *settings,
GParamSpec *pspec,
nsWindow *data);
static nsWindow* GetFirstNSWindowForGDKWindow (GdkWindow *aGdkWindow);
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#ifdef MOZ_X11
static GdkFilterReturn popup_take_focus_filter (GdkXEvent *gdk_xevent,
GdkEvent *event,
gpointer data);
static GdkFilterReturn plugin_window_filter_func (GdkXEvent *gdk_xevent,
GdkEvent *event,
gpointer data);
static GdkFilterReturn plugin_client_message_filter (GdkXEvent *xevent,
GdkEvent *event,
gpointer data);
#endif /* MOZ_X11 */
#ifdef __cplusplus
}
#endif /* __cplusplus */
static gboolean drag_motion_event_cb (GtkWidget *aWidget,
GdkDragContext *aDragContext,
gint aX,
gint aY,
guint aTime,
gpointer aData);
static void drag_leave_event_cb (GtkWidget *aWidget,
GdkDragContext *aDragContext,
guint aTime,
gpointer aData);
static gboolean drag_drop_event_cb (GtkWidget *aWidget,
GdkDragContext *aDragContext,
gint aX,
gint aY,
guint aTime,
gpointer aData);
static void drag_data_received_event_cb(GtkWidget *aWidget,
GdkDragContext *aDragContext,
gint aX,
gint aY,
GtkSelectionData *aSelectionData,
guint aInfo,
guint32 aTime,
gpointer aData);
/* initialization static functions */
static nsresult initialize_prefs (void);
static guint32 sLastUserInputTime = GDK_CURRENT_TIME;
static guint32 sRetryGrabTime;
static NS_DEFINE_IID(kCDragServiceCID, NS_DRAGSERVICE_CID);
// The window from which the focus manager asks us to dispatch key events.
static nsWindow *gFocusWindow = NULL;
static bool gBlockActivateEvent = false;
static bool gGlobalsInitialized = false;
static bool gRaiseWindows = true;
static nsWindow *gPluginFocusWindow = NULL;
#define NS_WINDOW_TITLE_MAX_LENGTH 4095
// If after selecting profile window, the startup fail, please refer to
// http://bugzilla.gnome.org/show_bug.cgi?id=88940
// needed for imgIContainer cursors
// GdkDisplay* was added in 2.2
typedef struct _GdkDisplay GdkDisplay;
#define kWindowPositionSlop 20
// cursor cache
static GdkCursor *gCursorCache[eCursorCount];
static GtkWidget *gInvisibleContainer = NULL;
// Sometimes this actually also includes the state of the modifier keys, but
// only the button state bits are used.
static guint gButtonState;
// Some gobject functions expect functions for gpointer arguments.
// gpointer is void* but C++ doesn't like casting functions to void*.
template<class T> static inline gpointer
FuncToGpointer(T aFunction)
{
return reinterpret_cast<gpointer>
(reinterpret_cast<uintptr_t>
// This cast just provides a warning if T is not a function.
(reinterpret_cast<void (*)()>(aFunction)));
}
// nsAutoRef<pixman_region32> uses nsSimpleRef<> to know how to automatically
// destroy regions.
template <>
class nsSimpleRef<pixman_region32> : public pixman_region32 {
protected:
typedef pixman_region32 RawRef;
nsSimpleRef() { data = nullptr; }
nsSimpleRef(const RawRef &aRawRef) : pixman_region32(aRawRef) { }
static void Release(pixman_region32& region) {
pixman_region32_fini(&region);
}
// Whether this needs to be released:
bool HaveResource() const { return data != nullptr; }
pixman_region32& get() { return *this; }
};
static inline int32_t
GetBitmapStride(int32_t width)
{
#if defined(MOZ_X11) || defined(MOZ_WIDGET_GTK2)
return (width+7)/8;
#else
return cairo_format_stride_for_width(CAIRO_FORMAT_A1, width);
#endif
}
static inline bool TimestampIsNewerThan(guint32 a, guint32 b)
{
// Timestamps are just the least significant bits of a monotonically
// increasing function, and so the use of unsigned overflow arithmetic.
return a - b <= G_MAXUINT32/2;
}
static void
UpdateLastInputEventTime(void *aGdkEvent)
{
nsCOMPtr<nsIIdleServiceInternal> idleService =
do_GetService("@mozilla.org/widget/idleservice;1");
if (idleService) {
idleService->ResetIdleTimeOut(0);
}
guint timestamp = gdk_event_get_time(static_cast<GdkEvent*>(aGdkEvent));
if (timestamp == GDK_CURRENT_TIME)
return;
sLastUserInputTime = timestamp;
}
nsWindow::nsWindow()
{
mIsTopLevel = false;
mIsDestroyed = false;
mNeedsResize = false;
mNeedsMove = false;
mListenForResizes = false;
mIsShown = false;
mNeedsShow = false;
mEnabled = true;
mCreated = false;
mContainer = nullptr;
mGdkWindow = nullptr;
mShell = nullptr;
mHasMappedToplevel = false;
mIsFullyObscured = false;
mRetryPointerGrab = false;
mWindowType = eWindowType_child;
mSizeState = nsSizeMode_Normal;
mLastSizeMode = nsSizeMode_Normal;
mSizeConstraints.mMaxSize = GetSafeWindowSize(mSizeConstraints.mMaxSize);
#ifdef MOZ_X11
mOldFocusWindow = 0;
#endif /* MOZ_X11 */
mPluginType = PluginType_NONE;
if (!gGlobalsInitialized) {
gGlobalsInitialized = true;
// It's OK if either of these fail, but it may not be one day.
initialize_prefs();
}
mLastMotionPressure = 0;
#ifdef ACCESSIBILITY
mRootAccessible = nullptr;
#endif
mIsTransparent = false;
mTransparencyBitmap = nullptr;
mTransparencyBitmapWidth = 0;
mTransparencyBitmapHeight = 0;
}
nsWindow::~nsWindow()
{
LOG(("nsWindow::~nsWindow() [%p]\n", (void *)this));
delete[] mTransparencyBitmap;
mTransparencyBitmap = nullptr;
Destroy();
}
/* static */ void
nsWindow::ReleaseGlobals()
{
for (uint32_t i = 0; i < ArrayLength(gCursorCache); ++i) {
if (gCursorCache[i]) {
gdk_cursor_unref(gCursorCache[i]);
gCursorCache[i] = nullptr;
}
}
}
NS_IMPL_ISUPPORTS_INHERITED1(nsWindow, nsBaseWidget,
nsISupportsWeakReference)
void
nsWindow::CommonCreate(nsIWidget *aParent, bool aListenForResizes)
{
mParent = aParent;
mListenForResizes = aListenForResizes;
mCreated = true;
}
void
nsWindow::DispatchActivateEvent(void)
{
NS_ASSERTION(mContainer || mIsDestroyed,
"DispatchActivateEvent only intended for container windows");
#ifdef ACCESSIBILITY
DispatchActivateEventAccessible();
#endif //ACCESSIBILITY
if (mWidgetListener)
mWidgetListener->WindowActivated();
}
void
nsWindow::DispatchDeactivateEvent(void)
{
if (mWidgetListener)
mWidgetListener->WindowDeactivated();
#ifdef ACCESSIBILITY
DispatchDeactivateEventAccessible();
#endif //ACCESSIBILITY
}
void
nsWindow::DispatchResized(int32_t aWidth, int32_t aHeight)
{
nsIWidgetListener *listeners[] =
{ mWidgetListener, mAttachedWidgetListener };
for (size_t i = 0; i < ArrayLength(listeners); ++i) {
if (listeners[i]) {
listeners[i]->WindowResized(this, aWidth, aHeight);
}
}
}
nsresult
nsWindow::DispatchEvent(nsGUIEvent *aEvent, nsEventStatus &aStatus)
{
#ifdef DEBUG
debug_DumpEvent(stdout, aEvent->widget, aEvent,
nsAutoCString("something"), 0);
#endif
aStatus = nsEventStatus_eIgnore;
nsIWidgetListener* listener =
mAttachedWidgetListener ? mAttachedWidgetListener : mWidgetListener;
if (listener) {
aStatus = listener->HandleEvent(aEvent, mUseAttachedEvents);
}
return NS_OK;
}
void
nsWindow::OnDestroy(void)
{
if (mOnDestroyCalled)
return;
mOnDestroyCalled = true;
// Prevent deletion.
nsCOMPtr<nsIWidget> kungFuDeathGrip = this;
// release references to children, device context, toolkit + app shell
nsBaseWidget::OnDestroy();
// Remove association between this object and its parent and siblings.
nsBaseWidget::Destroy();
mParent = nullptr;
NotifyWindowDestroyed();
}
bool
nsWindow::AreBoundsSane(void)
{
if (mBounds.width > 0 && mBounds.height > 0)
return true;
return false;
}
static GtkWidget*
EnsureInvisibleContainer()
{
if (!gInvisibleContainer) {
// GtkWidgets need to be anchored to a GtkWindow to be realized (to
// have a window). Using GTK_WINDOW_POPUP rather than
// GTK_WINDOW_TOPLEVEL in the hope that POPUP results in less
// initialization and window manager interaction.
GtkWidget* window = gtk_window_new(GTK_WINDOW_POPUP);
gInvisibleContainer = moz_container_new();
gtk_container_add(GTK_CONTAINER(window), gInvisibleContainer);
gtk_widget_realize(gInvisibleContainer);
}
return gInvisibleContainer;
}
static void
CheckDestroyInvisibleContainer()
{
NS_PRECONDITION(gInvisibleContainer, "oh, no");
if (!gdk_window_peek_children(gtk_widget_get_window(gInvisibleContainer))) {
// No children, so not in use.
// Make sure to destroy the GtkWindow also.
gtk_widget_destroy(gtk_widget_get_parent(gInvisibleContainer));
gInvisibleContainer = NULL;
}
}
// Change the containing GtkWidget on a sub-hierarchy of GdkWindows belonging
// to aOldWidget and rooted at aWindow, and reparent any child GtkWidgets of
// the GdkWindow hierarchy to aNewWidget.
static void
SetWidgetForHierarchy(GdkWindow *aWindow,
GtkWidget *aOldWidget,
GtkWidget *aNewWidget)
{
gpointer data;
gdk_window_get_user_data(aWindow, &data);
if (data != aOldWidget) {
if (!GTK_IS_WIDGET(data))
return;
GtkWidget* widget = static_cast<GtkWidget*>(data);
if (gtk_widget_get_parent(widget) != aOldWidget)
return;
// This window belongs to a child widget, which will no longer be a
// child of aOldWidget.
gtk_widget_reparent(widget, aNewWidget);
return;
}
GList *children = gdk_window_get_children(aWindow);
for(GList *list = children; list; list = list->next) {
SetWidgetForHierarchy(GDK_WINDOW(list->data), aOldWidget, aNewWidget);
}
g_list_free(children);
gdk_window_set_user_data(aWindow, aNewWidget);
}
// Walk the list of child windows and call destroy on them.
void
nsWindow::DestroyChildWindows()
{
if (!mGdkWindow)
return;
while (GList *children = gdk_window_peek_children(mGdkWindow)) {
GdkWindow *child = GDK_WINDOW(children->data);
nsWindow *kid = get_window_for_gdk_window(child);
if (kid) {
kid->Destroy();
} else {
// This child is not an nsWindow.
// Destroy the child GtkWidget.
gpointer data;
gdk_window_get_user_data(child, &data);
if (GTK_IS_WIDGET(data)) {
gtk_widget_destroy(static_cast<GtkWidget*>(data));
}
}
}
}
NS_IMETHODIMP
nsWindow::Destroy(void)
{
if (mIsDestroyed || !mCreated)
return NS_OK;
LOG(("nsWindow::Destroy [%p]\n", (void *)this));
mIsDestroyed = true;
mCreated = false;
/** Need to clean our LayerManager up while still alive */
if (mLayerManager) {
nsRefPtr<GLContext> gl = nullptr;
if (mLayerManager->GetBackendType() == mozilla::layers::LAYERS_OPENGL) {
LayerManagerOGL *ogllm = static_cast<LayerManagerOGL*>(mLayerManager.get());
gl = ogllm->gl();
}
mLayerManager->Destroy();
if (gl) {
gl->MarkDestroyed();
}
}
mLayerManager = nullptr;
// It is safe to call DestroyeCompositor several times (here and
// in the parent class) since it will take effect only once.
// The reason we call it here is because on gtk platforms we need
// to destroy the compositor before we destroy the gdk window (which
// destroys the the gl context attached to it).
DestroyCompositor();
ClearCachedResources();
g_signal_handlers_disconnect_by_func(gtk_settings_get_default(),
FuncToGpointer(theme_changed_cb),
this);
nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
if (rollupListener) {
nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
if (static_cast<nsIWidget *>(this) == rollupWidget) {
rollupListener->Rollup(0, nullptr);
}
}
// dragService will be null after shutdown of the service manager.
nsDragService *dragService = nsDragService::GetInstance();
if (dragService && this == dragService->GetMostRecentDestWindow()) {
dragService->ScheduleLeaveEvent();
}
NativeShow(false);
if (mIMModule) {
mIMModule->OnDestroyWindow(this);
}
// make sure that we remove ourself as the focus window
if (gFocusWindow == this) {
LOGFOCUS(("automatically losing focus...\n"));
gFocusWindow = nullptr;
}
#if defined(MOZ_WIDGET_GTK2) && defined(MOZ_X11)
// make sure that we remove ourself as the plugin focus window
if (gPluginFocusWindow == this) {
gPluginFocusWindow->LoseNonXEmbedPluginFocus();
}
#endif /* MOZ_X11 && MOZ_WIDGET_GTK2 */
// Destroy thebes surface now. Badness can happen if we destroy
// the surface after its X Window.
mThebesSurface = nullptr;
GtkWidget *owningWidget = GetMozContainerWidget();
if (mShell) {
gtk_widget_destroy(mShell);
mShell = nullptr;
mContainer = nullptr;
NS_ABORT_IF_FALSE(!mGdkWindow,
"mGdkWindow should be NULL when mContainer is destroyed");
}
else if (mContainer) {
gtk_widget_destroy(GTK_WIDGET(mContainer));
mContainer = nullptr;
NS_ABORT_IF_FALSE(!mGdkWindow,
"mGdkWindow should be NULL when mContainer is destroyed");
}
else if (mGdkWindow) {
// Destroy child windows to ensure that their mThebesSurfaces are
// released and to remove references from GdkWindows back to their
// container widget. (OnContainerUnrealize() does this when the
// MozContainer widget is destroyed.)
DestroyChildWindows();
gdk_window_set_user_data(mGdkWindow, NULL);
g_object_set_data(G_OBJECT(mGdkWindow), "nsWindow", NULL);
gdk_window_destroy(mGdkWindow);
mGdkWindow = nullptr;
}
if (gInvisibleContainer && owningWidget == gInvisibleContainer) {
CheckDestroyInvisibleContainer();
}
#ifdef ACCESSIBILITY
if (mRootAccessible) {
mRootAccessible = nullptr;
}
#endif
// Save until last because OnDestroy() may cause us to be deleted.
OnDestroy();
return NS_OK;
}
nsIWidget *
nsWindow::GetParent(void)
{
return mParent;
}
float
nsWindow::GetDPI()
{
#ifdef MOZ_PLATFORM_MAEMO
static float sDPI = 0;
if (!sDPI) {
// X on Maemo does not report true DPI: https://bugs.maemo.org/show_bug.cgi?id=4825
nsCOMPtr<nsIPropertyBag2> infoService = do_GetService("@mozilla.org/system-info;1");
NS_ASSERTION(infoService, "Could not find a system info service");
nsCString deviceType;
infoService->GetPropertyAsACString(NS_LITERAL_STRING("device"), deviceType);
if (deviceType.EqualsLiteral("Nokia N900")) {
sDPI = 265.0f;
} else if (deviceType.EqualsLiteral("Nokia N8xx")) {
sDPI = 225.0f;
} else {
// Fall back to something sane.
NS_WARNING("Unknown device - using default DPI");
sDPI = 96.0f;
}
}
return sDPI;
#else
Display *dpy = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
int defaultScreen = DefaultScreen(dpy);
double heightInches = DisplayHeightMM(dpy, defaultScreen)/MM_PER_INCH_FLOAT;
if (heightInches < 0.25) {
// Something's broken, but we'd better not crash.
return 96.0f;
}
return float(DisplayHeight(dpy, defaultScreen)/heightInches);
#endif
}
NS_IMETHODIMP
nsWindow::SetParent(nsIWidget *aNewParent)
{
if (mContainer || !mGdkWindow) {
NS_NOTREACHED("nsWindow::SetParent called illegally");
return NS_ERROR_NOT_IMPLEMENTED;
}
nsCOMPtr<nsIWidget> kungFuDeathGrip = this;
if (mParent) {
mParent->RemoveChild(this);
}
mParent = aNewParent;
GtkWidget* oldContainer = GetMozContainerWidget();
if (!oldContainer) {
// The GdkWindows have been destroyed so there is nothing else to
// reparent.
NS_ABORT_IF_FALSE(gdk_window_is_destroyed(mGdkWindow),
"live GdkWindow with no widget");
return NS_OK;
}
if (aNewParent) {
aNewParent->AddChild(this);
ReparentNativeWidget(aNewParent);
} else {
// aNewParent is NULL, but reparent to a hidden window to avoid
// destroying the GdkWindow and its descendants.
// An invisible container widget is needed to hold descendant
// GtkWidgets.
GtkWidget* newContainer = EnsureInvisibleContainer();
GdkWindow* newParentWindow = gtk_widget_get_window(newContainer);
ReparentNativeWidgetInternal(aNewParent, newContainer, newParentWindow,
oldContainer);
}
return NS_OK;
}
NS_IMETHODIMP
nsWindow::ReparentNativeWidget(nsIWidget* aNewParent)
{
NS_PRECONDITION(aNewParent, "");
NS_ASSERTION(!mIsDestroyed, "");
NS_ASSERTION(!static_cast<nsWindow*>(aNewParent)->mIsDestroyed, "");
GtkWidget* oldContainer = GetMozContainerWidget();
if (!oldContainer) {
// The GdkWindows have been destroyed so there is nothing else to
// reparent.
NS_ABORT_IF_FALSE(gdk_window_is_destroyed(mGdkWindow),
"live GdkWindow with no widget");
return NS_OK;
}
NS_ABORT_IF_FALSE(!gdk_window_is_destroyed(mGdkWindow),
"destroyed GdkWindow with widget");
nsWindow* newParent = static_cast<nsWindow*>(aNewParent);
GdkWindow* newParentWindow = newParent->mGdkWindow;
GtkWidget* newContainer = newParent->GetMozContainerWidget();
GtkWindow* shell = GTK_WINDOW(mShell);
if (shell && gtk_window_get_transient_for(shell)) {
GtkWindow* topLevelParent =
GTK_WINDOW(gtk_widget_get_toplevel(newContainer));
gtk_window_set_transient_for(shell, topLevelParent);
}
ReparentNativeWidgetInternal(aNewParent, newContainer, newParentWindow,
oldContainer);
return NS_OK;
}
void
nsWindow::ReparentNativeWidgetInternal(nsIWidget* aNewParent,
GtkWidget* aNewContainer,
GdkWindow* aNewParentWindow,
GtkWidget* aOldContainer)
{
if (!aNewContainer) {
// The new parent GdkWindow has been destroyed.
NS_ABORT_IF_FALSE(!aNewParentWindow ||
gdk_window_is_destroyed(aNewParentWindow),
"live GdkWindow with no widget");
Destroy();
} else {
if (aNewContainer != aOldContainer) {
NS_ABORT_IF_FALSE(!gdk_window_is_destroyed(aNewParentWindow),
"destroyed GdkWindow with widget");
SetWidgetForHierarchy(mGdkWindow, aOldContainer, aNewContainer);
if (aOldContainer == gInvisibleContainer) {
CheckDestroyInvisibleContainer();
}
}
if (!mIsTopLevel) {
gdk_window_reparent(mGdkWindow, aNewParentWindow, mBounds.x,
mBounds.y);
}
}
nsWindow* newParent = static_cast<nsWindow*>(aNewParent);
bool parentHasMappedToplevel =
newParent && newParent->mHasMappedToplevel;
if (mHasMappedToplevel != parentHasMappedToplevel) {
SetHasMappedToplevel(parentHasMappedToplevel);
}
}
NS_IMETHODIMP
nsWindow::SetModal(bool aModal)
{
LOG(("nsWindow::SetModal [%p] %d\n", (void *)this, aModal));
if (mIsDestroyed)
return aModal ? NS_ERROR_NOT_AVAILABLE : NS_OK;
if (!mIsTopLevel || !mShell)
return NS_ERROR_FAILURE;
gtk_window_set_modal(GTK_WINDOW(mShell), aModal ? TRUE : FALSE);
return NS_OK;
}
// nsIWidget method, which means IsShown.
bool
nsWindow::IsVisible() const
{
return mIsShown;
}
NS_IMETHODIMP
nsWindow::ConstrainPosition(bool aAllowSlop, int32_t *aX, int32_t *aY)
{
if (mIsTopLevel && mShell) {
int32_t screenWidth = gdk_screen_width();
int32_t screenHeight = gdk_screen_height();
if (aAllowSlop) {
if (*aX < (kWindowPositionSlop - mBounds.width))
*aX = kWindowPositionSlop - mBounds.width;
if (*aX > (screenWidth - kWindowPositionSlop))
*aX = screenWidth - kWindowPositionSlop;
if (*aY < (kWindowPositionSlop - mBounds.height))
*aY = kWindowPositionSlop - mBounds.height;
if (*aY > (screenHeight - kWindowPositionSlop))
*aY = screenHeight - kWindowPositionSlop;
} else {
if (*aX < 0)
*aX = 0;
if (*aX > (screenWidth - mBounds.width))
*aX = screenWidth - mBounds.width;
if (*aY < 0)
*aY = 0;
if (*aY > (screenHeight - mBounds.height))
*aY = screenHeight - mBounds.height;
}
}
return NS_OK;
}
void nsWindow::SetSizeConstraints(const SizeConstraints& aConstraints)
{
mSizeConstraints.mMinSize = GetSafeWindowSize(aConstraints.mMinSize);
mSizeConstraints.mMaxSize = GetSafeWindowSize(aConstraints.mMaxSize);
if (mShell) {
GdkGeometry geometry;
geometry.min_width = mSizeConstraints.mMinSize.width;
geometry.min_height = mSizeConstraints.mMinSize.height;
geometry.max_width = mSizeConstraints.mMaxSize.width;
geometry.max_height = mSizeConstraints.mMaxSize.height;
uint32_t hints = GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE;
gtk_window_set_geometry_hints(GTK_WINDOW(mShell), nullptr,
&geometry, GdkWindowHints(hints));
}
}
NS_IMETHODIMP
nsWindow::Show(bool aState)
{
if (aState == mIsShown)
return NS_OK;
// Clear our cached resources when the window is hidden.
if (mIsShown && !aState) {
ClearCachedResources();
}
mIsShown = aState;
LOG(("nsWindow::Show [%p] state %d\n", (void *)this, aState));
if (aState) {
// Now that this window is shown, mHasMappedToplevel needs to be
// tracked on viewable descendants.
SetHasMappedToplevel(mHasMappedToplevel);
}
// Ok, someone called show on a window that isn't sized to a sane
// value. Mark this window as needing to have Show() called on it
// and return.
if ((aState && !AreBoundsSane()) || !mCreated) {
LOG(("\tbounds are insane or window hasn't been created yet\n"));
mNeedsShow = true;
return NS_OK;
}
// If someone is hiding this widget, clear any needing show flag.
if (!aState)
mNeedsShow = false;
// If someone is showing this window and it needs a resize then
// resize the widget.
if (aState) {
if (mNeedsMove) {
NativeResize(mBounds.x, mBounds.y, mBounds.width, mBounds.height,
false);
} else if (mNeedsResize) {
NativeResize(mBounds.width, mBounds.height, false);
}
}
#ifdef ACCESSIBILITY
if (aState && a11y::ShouldA11yBeEnabled())
CreateRootAccessible();
#endif
NativeShow(aState);
return NS_OK;
}
NS_IMETHODIMP
nsWindow::Resize(double aWidth, double aHeight, bool aRepaint)
{
double scale = GetDefaultScale();
int32_t width = NSToIntRound(scale * aWidth);
int32_t height = NSToIntRound(scale * aHeight);
ConstrainSize(&width, &height);
// For top-level windows, aWidth and aHeight should possibly be
// interpreted as frame bounds, but NativeResize treats these as window
// bounds (Bug 581866).
mBounds.SizeTo(width, height);
if (!mCreated)
return NS_OK;
// There are several cases here that we need to handle, based on a
// matrix of the visibility of the widget, the sanity of this resize
// and whether or not the widget was previously sane.
// Has this widget been set to visible?
if (mIsShown) {
// Are the bounds sane?
if (AreBoundsSane()) {
// Yep? Resize the window
//Maybe, the toplevel has moved
// Note that if the widget needs to be positioned because its
// size was previously insane in Resize(x,y,w,h), then we need
// to set the x and y here too, because the widget wasn't
// moved back then
if (mNeedsMove)
NativeResize(mBounds.x, mBounds.y,
mBounds.width, mBounds.height, aRepaint);
else
NativeResize(mBounds.width, mBounds.height, aRepaint);
// Does it need to be shown because it was previously insane?
if (mNeedsShow)
NativeShow(true);
}
else {
// If someone has set this so that the needs show flag is false
// and it needs to be hidden, update the flag and hide the
// window. This flag will be cleared the next time someone
// hides the window or shows it. It also prevents us from
// calling NativeShow(false) excessively on the window which
// causes unneeded X traffic.
if (!mNeedsShow) {
mNeedsShow = true;
NativeShow(false);
}
}
}
// If the widget hasn't been shown, mark the widget as needing to be
// resized before it is shown.
else {
if (AreBoundsSane() && mListenForResizes) {
// For widgets that we listen for resizes for (widgets created
// with native parents) we apparently _always_ have to resize. I
// dunno why, but apparently we're lame like that.
NativeResize(width, height, aRepaint);
}
else {
mNeedsResize = true;
}
}
NotifyRollupGeometryChange();
// send a resize notification if this is a toplevel
if (mIsTopLevel || mListenForResizes) {
DispatchResized(width, height);
}
return NS_OK;
}
NS_IMETHODIMP
nsWindow::Resize(double aX, double aY, double aWidth, double aHeight,
bool aRepaint)
{
double scale = GetDefaultScale();
int32_t width = NSToIntRound(scale * aWidth);
int32_t height = NSToIntRound(scale * aHeight);
ConstrainSize(&width, &height);
int32_t x = NSToIntRound(scale * aX);
int32_t y = NSToIntRound(scale * aY);
mBounds.x = x;
mBounds.y = y;
mBounds.SizeTo(width, height);
mNeedsMove = true;
if (!mCreated)
return NS_OK;
// There are several cases here that we need to handle, based on a
// matrix of the visibility of the widget, the sanity of this resize
// and whether or not the widget was previously sane.
// Has this widget been set to visible?
if (mIsShown) {
// Are the bounds sane?
if (AreBoundsSane()) {
// Yep? Resize the window
NativeResize(x, y, width, height, aRepaint);
// Does it need to be shown because it was previously insane?
if (mNeedsShow)
NativeShow(true);
}
else {
// If someone has set this so that the needs show flag is false
// and it needs to be hidden, update the flag and hide the
// window. This flag will be cleared the next time someone
// hides the window or shows it. It also prevents us from
// calling NativeShow(false) excessively on the window which
// causes unneeded X traffic.
if (!mNeedsShow) {
mNeedsShow = true;
NativeShow(false);
}
}
}
// If the widget hasn't been shown, mark the widget as needing to be
// resized before it is shown
else {
if (AreBoundsSane() && mListenForResizes){
// For widgets that we listen for resizes for (widgets created
// with native parents) we apparently _always_ have to resize. I
// dunno why, but apparently we're lame like that.
NativeResize(x, y, width, height, aRepaint);
}
else {
mNeedsResize = true;
}
}
NotifyRollupGeometryChange();
if (mIsTopLevel || mListenForResizes) {
DispatchResized(width, height);
}
return NS_OK;
}
NS_IMETHODIMP
nsWindow::Enable(bool aState)
{
mEnabled = aState;
return NS_OK;
}
bool
nsWindow::IsEnabled() const
{
return mEnabled;
}
NS_IMETHODIMP
nsWindow::Move(double aX, double aY)
{
LOG(("nsWindow::Move [%p] %f %f\n", (void *)this,
aX, aY));
double scale = GetDefaultScale();
int32_t x = NSToIntRound(aX * scale);
int32_t y = NSToIntRound(aY * scale);
if (mWindowType == eWindowType_toplevel ||
mWindowType == eWindowType_dialog) {
SetSizeMode(nsSizeMode_Normal);
}
// Since a popup window's x/y coordinates are in relation to to
// the parent, the parent might have moved so we always move a
// popup window.
if (x == mBounds.x && y == mBounds.y &&
mWindowType != eWindowType_popup)
return NS_OK;
// XXX Should we do some AreBoundsSane check here?
mBounds.x = x;
mBounds.y = y;
if (!mCreated)
return NS_OK;
mNeedsMove = false;
if (mIsTopLevel) {
gtk_window_move(GTK_WINDOW(mShell), x, y);
}
else if (mGdkWindow) {
gdk_window_move(mGdkWindow, x, y);
}
NotifyRollupGeometryChange();
return NS_OK;
}
NS_IMETHODIMP
nsWindow::PlaceBehind(nsTopLevelWidgetZPlacement aPlacement,
nsIWidget *aWidget,
bool aActivate)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsWindow::SetZIndex(int32_t aZIndex)
{
nsIWidget* oldPrev = GetPrevSibling();
nsBaseWidget::SetZIndex(aZIndex);
if (GetPrevSibling() == oldPrev) {
return NS_OK;
}
NS_ASSERTION(!mContainer, "Expected Mozilla child widget");
// We skip the nsWindows that don't have mGdkWindows.
// These are probably in the process of being destroyed.
if (!GetNextSibling()) {
// We're to be on top.
if (mGdkWindow)
gdk_window_raise(mGdkWindow);
} else {
// All the siblings before us need to be below our widget.
for (nsWindow* w = this; w;
w = static_cast<nsWindow*>(w->GetPrevSibling())) {
if (w->mGdkWindow)
gdk_window_lower(w->mGdkWindow);
}
}
return NS_OK;
}
NS_IMETHODIMP
nsWindow::SetSizeMode(int32_t aMode)
{
nsresult rv;
LOG(("nsWindow::SetSizeMode [%p] %d\n", (void *)this, aMode));
// Save the requested state.
rv = nsBaseWidget::SetSizeMode(aMode);
// return if there's no shell or our current state is the same as
// the mode we were just set to.
if (!mShell || mSizeState == mSizeMode) {
return rv;
}
switch (aMode) {
case nsSizeMode_Maximized:
gtk_window_maximize(GTK_WINDOW(mShell));
break;
case nsSizeMode_Minimized:
gtk_window_iconify(GTK_WINDOW(mShell));
break;
case nsSizeMode_Fullscreen:
MakeFullScreen(true);
break;
default:
// nsSizeMode_Normal, really.
if (mSizeState == nsSizeMode_Minimized)
gtk_window_deiconify(GTK_WINDOW(mShell));
else if (mSizeState == nsSizeMode_Maximized)
gtk_window_unmaximize(GTK_WINDOW(mShell));
break;
}
mSizeState = mSizeMode;
return rv;
}
typedef void (* SetUserTimeFunc)(GdkWindow* aWindow, guint32 aTimestamp);
// This will become obsolete when new GTK APIs are widely supported,
// as described here: http://bugzilla.gnome.org/show_bug.cgi?id=347375
static void
SetUserTimeAndStartupIDForActivatedWindow(GtkWidget* aWindow)
{
nsGTKToolkit* GTKToolkit = nsGTKToolkit::GetToolkit();
if (!GTKToolkit)
return;
nsAutoCString desktopStartupID;
GTKToolkit->GetDesktopStartupID(&desktopStartupID);
if (desktopStartupID.IsEmpty()) {
// We don't have the data we need. Fall back to an
// approximation ... using the timestamp of the remote command
// being received as a guess for the timestamp of the user event
// that triggered it.
uint32_t timestamp = GTKToolkit->GetFocusTimestamp();
if (timestamp) {
gdk_window_focus(gtk_widget_get_window(aWindow), timestamp);
GTKToolkit->SetFocusTimestamp(0);
}
return;
}
#if defined(MOZ_ENABLE_STARTUP_NOTIFICATION)
GdkWindow* gdkWindow = gtk_widget_get_window(aWindow);
GdkScreen* screen = gdk_window_get_screen(gdkWindow);
SnDisplay* snd =
sn_display_new(gdk_x11_display_get_xdisplay(gdk_window_get_display(gdkWindow)),
nullptr, nullptr);
if (!snd)
return;
SnLauncheeContext* ctx =
sn_launchee_context_new(snd, gdk_screen_get_number(screen),
desktopStartupID.get());
if (!ctx) {
sn_display_unref(snd);
return;
}
if (sn_launchee_context_get_id_has_timestamp(ctx)) {
PRLibrary* gtkLibrary;
SetUserTimeFunc setUserTimeFunc = (SetUserTimeFunc)
PR_FindFunctionSymbolAndLibrary("gdk_x11_window_set_user_time", &gtkLibrary);
if (setUserTimeFunc) {
setUserTimeFunc(gdkWindow, sn_launchee_context_get_timestamp(ctx));
PR_UnloadLibrary(gtkLibrary);
}
}
sn_launchee_context_setup_window(ctx, gdk_x11_window_get_xid(gdkWindow));
sn_launchee_context_complete(ctx);
sn_launchee_context_unref(ctx);
sn_display_unref(snd);
#endif
// If we used the startup ID, that already contains the focus timestamp;
// we don't want to reuse the timestamp next time we raise the window
GTKToolkit->SetFocusTimestamp(0);
GTKToolkit->SetDesktopStartupID(EmptyCString());
}
/* static */ guint32
nsWindow::GetLastUserInputTime()
{
// gdk_x11_display_get_user_time tracks button and key presses,
// DESKTOP_STARTUP_ID used to start the app, drop events from external
// drags, WM_DELETE_WINDOW delete events, but not usually mouse motion nor
// button and key releases. Therefore use the most recent of
// gdk_x11_display_get_user_time and the last time that we have seen.
guint32 timestamp =
gdk_x11_display_get_user_time(gdk_display_get_default());
if (sLastUserInputTime != GDK_CURRENT_TIME &&
TimestampIsNewerThan(sLastUserInputTime, timestamp)) {
return sLastUserInputTime;
}
return timestamp;
}
NS_IMETHODIMP
nsWindow::SetFocus(bool aRaise)
{
// Make sure that our owning widget has focus. If it doesn't try to
// grab it. Note that we don't set our focus flag in this case.
LOGFOCUS((" SetFocus %d [%p]\n", aRaise, (void *)this));
GtkWidget *owningWidget = GetMozContainerWidget();
if (!owningWidget)
return NS_ERROR_FAILURE;
// Raise the window if someone passed in true and the prefs are
// set properly.
GtkWidget *toplevelWidget = gtk_widget_get_toplevel(owningWidget);
if (gRaiseWindows && aRaise && toplevelWidget &&
!gtk_widget_has_focus(owningWidget) &&
!gtk_widget_has_focus(toplevelWidget)) {
GtkWidget* top_window = GetToplevelWidget();
if (top_window && (gtk_widget_get_visible(top_window)))
{
gdk_window_show_unraised(gtk_widget_get_window(top_window));
// Unset the urgency hint if possible.
SetUrgencyHint(top_window, false);
}
}
nsRefPtr<nsWindow> owningWindow = get_window_for_gtk_widget(owningWidget);
if (!owningWindow)
return NS_ERROR_FAILURE;
if (aRaise) {
// aRaise == true means request toplevel activation.
// This is asynchronous.
// If and when the window manager accepts the request, then the focus
// widget will get a focus-in-event signal.
if (gRaiseWindows && owningWindow->mIsShown && owningWindow->mShell &&
!gtk_window_is_active(GTK_WINDOW(owningWindow->mShell))) {
uint32_t timestamp = GDK_CURRENT_TIME;
nsGTKToolkit* GTKToolkit = nsGTKToolkit::GetToolkit();
if (GTKToolkit)
timestamp = GTKToolkit->GetFocusTimestamp();
LOGFOCUS((" requesting toplevel activation [%p]\n", (void *)this));
NS_ASSERTION(owningWindow->mWindowType != eWindowType_popup
|| mParent,
"Presenting an override-redirect window");
gtk_window_present_with_time(GTK_WINDOW(owningWindow->mShell), timestamp);
if (GTKToolkit)
GTKToolkit->SetFocusTimestamp(0);
}
return NS_OK;
}
// aRaise == false means that keyboard events should be dispatched
// from this widget.
// Ensure owningWidget is the focused GtkWidget within its toplevel window.
//
// For eWindowType_popup, this GtkWidget may not actually be the one that
// receives the key events as it may be the parent window that is active.
if (!gtk_widget_is_focus(owningWidget)) {
// This is synchronous. It takes focus from a plugin or from a widget
// in an embedder. The focus manager already knows that this window
// is active so gBlockActivateEvent avoids another (unnecessary)
// activate notification.
gBlockActivateEvent = true;
gtk_widget_grab_focus(owningWidget);
gBlockActivateEvent = false;
}
// If this is the widget that already has focus, return.
if (gFocusWindow == this) {
LOGFOCUS((" already have focus [%p]\n", (void *)this));
return NS_OK;
}
// Set this window to be the focused child window
gFocusWindow = this;
if (mIMModule) {
mIMModule->OnFocusWindow(this);
}
LOGFOCUS((" widget now has focus in SetFocus() [%p]\n",
(void *)this));
return NS_OK;
}
NS_IMETHODIMP
nsWindow::GetScreenBounds(nsIntRect &aRect)
{
if (mIsTopLevel && mContainer) {
// use the point including window decorations
gint x, y;
gdk_window_get_root_origin(gtk_widget_get_window(GTK_WIDGET(mContainer)), &x, &y);
aRect.MoveTo(x, y);
}
else {
aRect.MoveTo(WidgetToScreenOffset());
}
// mBounds.Size() is the window bounds, not the window-manager frame
// bounds (bug 581863). gdk_window_get_frame_extents would give the
// frame bounds, but mBounds.Size() is returned here for consistency
// with Resize.
aRect.SizeTo(mBounds.Size());
LOG(("GetScreenBounds %d,%d | %dx%d\n",
aRect.x, aRect.y, aRect.width, aRect.height));
return NS_OK;
}
NS_IMETHODIMP
nsWindow::GetClientBounds(nsIntRect &aRect)
{
// GetBounds returns a rect whose top left represents the top left of the
// outer bounds, but whose width/height represent the size of the inner
// bounds (which is messed up).
GetBounds(aRect);
aRect.MoveBy(GetClientOffset());
return NS_OK;
}
nsIntPoint
nsWindow::GetClientOffset()
{
if (!mIsTopLevel) {
return nsIntPoint(0, 0);
}
GdkAtom cardinal_atom = gdk_x11_xatom_to_atom(XA_CARDINAL);
GdkAtom type_returned;
int format_returned;
int length_returned;
long *frame_extents;
GdkWindow* window;
if (!mShell || !(window = gtk_widget_get_window(mShell)) ||
!gdk_property_get(window,
gdk_atom_intern ("_NET_FRAME_EXTENTS", FALSE),
cardinal_atom,
0, // offset
4*4, // length
FALSE, // delete
&type_returned,
&format_returned,
&length_returned,
(guchar **) &frame_extents) ||
length_returned/sizeof(glong) != 4) {
return nsIntPoint(0, 0);
}
// data returned is in the order left, right, top, bottom
int32_t left = int32_t(frame_extents[0]);
int32_t top = int32_t(frame_extents[2]);
g_free(frame_extents);
return nsIntPoint(left, top);
}
NS_IMETHODIMP
nsWindow::SetForegroundColor(const nscolor &aColor)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsWindow::SetBackgroundColor(const nscolor &aColor)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsWindow::SetCursor(nsCursor aCursor)
{
// if we're not the toplevel window pass up the cursor request to
// the toplevel window to handle it.
if (!mContainer && mGdkWindow) {
nsWindow *window = GetContainerWindow();
if (!window)
return NS_ERROR_FAILURE;
return window->SetCursor(aCursor);
}
// Only change cursor if it's actually been changed
if (aCursor != mCursor) {
GdkCursor *newCursor = NULL;
newCursor = get_gtk_cursor(aCursor);
if (nullptr != newCursor) {
mCursor = aCursor;
if (!mContainer)
return NS_OK;
gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(mContainer)), newCursor);
}
}
return NS_OK;
}
NS_IMETHODIMP
nsWindow::SetCursor(imgIContainer* aCursor,
uint32_t aHotspotX, uint32_t aHotspotY)
{
// if we're not the toplevel window pass up the cursor request to
// the toplevel window to handle it.
if (!mContainer && mGdkWindow) {
nsWindow *window = GetContainerWindow();
if (!window)
return NS_ERROR_FAILURE;
return window->SetCursor(aCursor, aHotspotX, aHotspotY);
}
mCursor = nsCursor(-1);
// Get the image's current frame
GdkPixbuf* pixbuf = nsImageToPixbuf::ImageToPixbuf(aCursor);
if (!pixbuf)
return NS_ERROR_NOT_AVAILABLE;
int width = gdk_pixbuf_get_width(pixbuf);
int height = gdk_pixbuf_get_height(pixbuf);
// Reject cursors greater than 128 pixels in some direction, to prevent
// spoofing.
// XXX ideally we should rescale. Also, we could modify the API to
// allow trusted content to set larger cursors.
if (width > 128 || height > 128) {
g_object_unref(pixbuf);
return NS_ERROR_NOT_AVAILABLE;
}
// Looks like all cursors need an alpha channel (tested on Gtk 2.4.4). This
// is of course not documented anywhere...
// So add one if there isn't one yet
if (!gdk_pixbuf_get_has_alpha(pixbuf)) {
GdkPixbuf* alphaBuf = gdk_pixbuf_add_alpha(pixbuf, FALSE, 0, 0, 0);
g_object_unref(pixbuf);
if (!alphaBuf) {
return NS_ERROR_OUT_OF_MEMORY;
}
pixbuf = alphaBuf;
}
GdkCursor* cursor = gdk_cursor_new_from_pixbuf(gdk_display_get_default(),
pixbuf,
aHotspotX, aHotspotY);
g_object_unref(pixbuf);
nsresult rv = NS_ERROR_OUT_OF_MEMORY;
if (cursor) {
if (mContainer) {
gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(mContainer)), cursor);
rv = NS_OK;
}
gdk_cursor_unref(cursor);
}
return rv;
}
NS_IMETHODIMP
nsWindow::Invalidate(const nsIntRect &aRect)
{
if (!mGdkWindow)
return NS_OK;
GdkRectangle rect;
rect.x = aRect.x;
rect.y = aRect.y;
rect.width = aRect.width;
rect.height = aRect.height;
LOGDRAW(("Invalidate (rect) [%p]: %d %d %d %d\n", (void *)this,
rect.x, rect.y, rect.width, rect.height));
gdk_window_invalidate_rect(mGdkWindow, &rect, FALSE);
return NS_OK;
}
void*
nsWindow::GetNativeData(uint32_t aDataType)
{
switch (aDataType) {
case NS_NATIVE_WINDOW:
case NS_NATIVE_WIDGET: {
if (!mGdkWindow)
return nullptr;
return mGdkWindow;
break;
}
case NS_NATIVE_PLUGIN_PORT:
return SetupPluginPort();
break;
case NS_NATIVE_DISPLAY:
#ifdef MOZ_X11
return GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
#else
return nullptr;
#endif /* MOZ_X11 */
break;
case NS_NATIVE_SHELLWIDGET:
return GetToplevelWidget();
case NS_NATIVE_SHAREABLE_WINDOW:
return (void *) GDK_WINDOW_XID(gdk_window_get_toplevel(mGdkWindow));
default:
NS_WARNING("nsWindow::GetNativeData called with bad value");
return nullptr;
}
}
NS_IMETHODIMP
nsWindow::SetTitle(const nsAString& aTitle)
{
if (!mShell)
return NS_OK;
// convert the string into utf8 and set the title.
#define UTF8_FOLLOWBYTE(ch) (((ch) & 0xC0) == 0x80)
NS_ConvertUTF16toUTF8 titleUTF8(aTitle);
if (titleUTF8.Length() > NS_WINDOW_TITLE_MAX_LENGTH) {
// Truncate overlong titles (bug 167315). Make sure we chop after a
// complete sequence by making sure the next char isn't a follow-byte.
uint32_t len = NS_WINDOW_TITLE_MAX_LENGTH;
while(UTF8_FOLLOWBYTE(titleUTF8[len]))
--len;
titleUTF8.Truncate(len);
}
gtk_window_set_title(GTK_WINDOW(mShell), (const char *)titleUTF8.get());
return NS_OK;
}
NS_IMETHODIMP
nsWindow::SetIcon(const nsAString& aIconSpec)
{
if (!mShell)
return NS_OK;
nsAutoCString iconName;
if (aIconSpec.EqualsLiteral("default")) {
nsXPIDLString brandName;
GetBrandName(brandName);
AppendUTF16toUTF8(brandName, iconName);
ToLowerCase(iconName);
} else {
AppendUTF16toUTF8(aIconSpec, iconName);
}
nsCOMPtr<nsIFile> iconFile;
nsAutoCString path;
gint *iconSizes =
gtk_icon_theme_get_icon_sizes(gtk_icon_theme_get_default(),
iconName.get());
bool foundIcon = (iconSizes[0] != 0);
g_free(iconSizes);
if (!foundIcon) {
// Look for icons with the following suffixes appended to the base name
// The last two entries (for the old XPM format) will be ignored unless
// no icons are found using other suffixes. XPM icons are deprecated.
const char extensions[6][7] = { ".png", "16.png", "32.png", "48.png",
".xpm", "16.xpm" };
for (uint32_t i = 0; i < ArrayLength(extensions); i++) {
// Don't bother looking for XPM versions if we found a PNG.
if (i == ArrayLength(extensions) - 2 && foundIcon)
break;
nsAutoString extension;
extension.AppendASCII(extensions[i]);
ResolveIconName(aIconSpec, extension, getter_AddRefs(iconFile));
if (iconFile) {
iconFile->GetNativePath(path);
GdkPixbuf *icon = gdk_pixbuf_new_from_file(path.get(), NULL);
if (icon){
gtk_icon_theme_add_builtin_icon(iconName.get(),
gdk_pixbuf_get_height(icon),
icon);
g_object_unref(icon);
foundIcon = true;
}
}
}
}
// leave the default icon intact if no matching icons were found
if (foundIcon) {
gtk_window_set_icon_name(GTK_WINDOW(mShell), iconName.get());
}
return NS_OK;
}
nsIntPoint
nsWindow::WidgetToScreenOffset()
{
gint x = 0, y = 0;
if (mGdkWindow) {
gdk_window_get_origin(mGdkWindow, &x, &y);
}
return nsIntPoint(x, y);
}
NS_IMETHODIMP
nsWindow::EnableDragDrop(bool aEnable)
{
return NS_OK;
}
NS_IMETHODIMP
nsWindow::CaptureMouse(bool aCapture)
{
LOG(("CaptureMouse %p\n", (void *)this));
if (!mGdkWindow)
return NS_OK;
if (!mShell)
return NS_ERROR_FAILURE;
if (aCapture) {
gtk_grab_add(mShell);
GrabPointer(GetLastUserInputTime());
}
else {
ReleaseGrabs();
gtk_grab_remove(mShell);
}
return NS_OK;
}
NS_IMETHODIMP
nsWindow::CaptureRollupEvents(nsIRollupListener *aListener,
bool aDoCapture)
{
if (!mGdkWindow)
return NS_OK;
if (!mShell)
return NS_ERROR_FAILURE;
LOG(("CaptureRollupEvents %p %i\n", this, int(aDoCapture)));
if (aDoCapture) {
gRollupListener = aListener;
// real grab is only done when there is no dragging
if (!nsWindow::DragInProgress()) {
// This widget grab ensures that a Gecko GtkWidget receives mouse
// events even when embedded in non-Gecko-owned GtkWidgets.
// The grab is placed on the toplevel GtkWindow instead of the
// MozContainer to avoid double dispatch of keyboard events
// (bug 707623).
gtk_grab_add(mShell);
GrabPointer(GetLastUserInputTime());
}
}
else {
if (!nsWindow::DragInProgress()) {
ReleaseGrabs();
}
// There may not have been a drag in process when aDoCapture was set,
// so make sure to remove any added grab. This is a no-op if the grab
// was not added to this widget.
gtk_grab_remove(mShell);
gRollupListener = nullptr;
}
return NS_OK;
}
NS_IMETHODIMP
nsWindow::GetAttention(int32_t aCycleCount)
{
LOG(("nsWindow::GetAttention [%p]\n", (void *)this));
GtkWidget* top_window = GetToplevelWidget();
GtkWidget* top_focused_window =
gFocusWindow ? gFocusWindow->GetToplevelWidget() : nullptr;
// Don't get attention if the window is focused anyway.
if (top_window && (gtk_widget_get_visible(top_window)) &&
top_window != top_focused_window) {
SetUrgencyHint(top_window, true);
}
return NS_OK;
}
bool
nsWindow::HasPendingInputEvent()
{
// This sucks, but gtk/gdk has no way to answer the question we want while
// excluding paint events, and there's no X API that will let us peek
// without blocking or removing. To prevent event reordering, peek
// anything except expose events. Reordering expose and others should be
// ok, hopefully.
bool haveEvent;
#ifdef MOZ_X11
XEvent ev;
Display *display = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
haveEvent =
XCheckMaskEvent(display,
KeyPressMask | KeyReleaseMask | ButtonPressMask |
ButtonReleaseMask | EnterWindowMask | LeaveWindowMask |
PointerMotionMask | PointerMotionHintMask |
Button1MotionMask | Button2MotionMask |
Button3MotionMask | Button4MotionMask |
Button5MotionMask | ButtonMotionMask | KeymapStateMask |
VisibilityChangeMask | StructureNotifyMask |
ResizeRedirectMask | SubstructureNotifyMask |
SubstructureRedirectMask | FocusChangeMask |
PropertyChangeMask | ColormapChangeMask |
OwnerGrabButtonMask, &ev);
if (haveEvent) {
XPutBackEvent(display, &ev);
}
#else
haveEvent = false;
#endif
return haveEvent;
}
#if 0
#ifdef DEBUG
// Paint flashing code (disabled for cairo - see below)
#define CAPS_LOCK_IS_ON \
(KeymapWrapper::AreModifiersCurrentlyActive(KeymapWrapper::CAPS_LOCK))
#define WANT_PAINT_FLASHING \
(debug_WantPaintFlashing() && CAPS_LOCK_IS_ON)
#ifdef MOZ_X11
static void
gdk_window_flash(GdkWindow * aGdkWindow,
unsigned int aTimes,
unsigned int aInterval, // Milliseconds
GdkRegion * aRegion)
{
gint x;
gint y;
gint width;
gint height;
guint i;
GdkGC * gc = 0;
GdkColor white;
#if defined(MOZ_WIDGET_GTK2)
gdk_window_get_geometry(aGdkWindow,NULL,NULL,&width,&height,NULL);
#else
gdk_window_get_geometry(aGdkWindow,NULL,NULL,&width,&height);
#endif
gdk_window_get_origin (aGdkWindow,
&x,
&y);
gc = gdk_gc_new(GDK_ROOT_PARENT());
white.pixel = WhitePixel(gdk_display,DefaultScreen(gdk_display));
gdk_gc_set_foreground(gc,&white);
gdk_gc_set_function(gc,GDK_XOR);
gdk_gc_set_subwindow(gc,GDK_INCLUDE_INFERIORS);
gdk_region_offset(aRegion, x, y);
gdk_gc_set_clip_region(gc, aRegion);
/*
* Need to do this twice so that the XOR effect can replace
* the original window contents.
*/
for (i = 0; i < aTimes * 2; i++)
{
gdk_draw_rectangle(GDK_ROOT_PARENT(),
gc,
TRUE,
x,
y,
width,
height);
gdk_flush();
PR_Sleep(PR_MillisecondsToInterval(aInterval));
}
gdk_gc_destroy(gc);
gdk_region_offset(aRegion, -x, -y);
}
#endif /* MOZ_X11 */
#endif // DEBUG
#endif
#if defined(MOZ_WIDGET_GTK2)
gboolean
nsWindow::OnExposeEvent(GdkEventExpose *aEvent)
#else
gboolean
nsWindow::OnExposeEvent(cairo_t *cr)
#endif
{
if (mIsDestroyed) {
return FALSE;
}
// Windows that are not visible will be painted after they become visible.
if (!mGdkWindow || mIsFullyObscured || !mHasMappedToplevel)
return FALSE;
nsIWidgetListener *listener =
mAttachedWidgetListener ? mAttachedWidgetListener : mWidgetListener;
if (!listener)
return FALSE;
// Dispatch WillPaintWindow notification to allow scripts etc. to run
// before we paint
{
listener->WillPaintWindow(this);
// If the window has been destroyed during the will paint notification,
// there is nothing left to do.
if (!mGdkWindow)
return TRUE;
// Re-get the listener since the will paint notification might have
// killed it.
listener =
mAttachedWidgetListener ? mAttachedWidgetListener : mWidgetListener;
if (!listener)
return FALSE;
}
#if defined(MOZ_WIDGET_GTK2)
GdkRectangle *rects;
gint nrects;
gdk_region_get_rectangles(aEvent->region, &rects, &nrects);
if (MOZ_UNLIKELY(!rects)) // OOM
return FALSE;
#else
#ifdef cairo_copy_clip_rectangle_list
#error "Looks like we're including Mozilla's cairo instead of system cairo"
#else
cairo_rectangle_list_t *rects;
rects = cairo_copy_clip_rectangle_list(cr);
if (MOZ_UNLIKELY(rects->status != CAIRO_STATUS_SUCCESS)) {
NS_WARNING("Failed to obtain cairo rectangle list.");
return FALSE;
}
#endif
#endif
// GTK3 TODO?
#if defined(MOZ_WIDGET_GTK2)
if (nrects > MAX_RECTS_IN_REGION) {
// Just use the bounding box
rects[0] = aEvent->area;
nrects = 1;
}
#endif
LOGDRAW(("sending expose event [%p] %p 0x%lx (rects follow):\n",
(void *)this, (void *)mGdkWindow,
gdk_x11_window_get_xid(mGdkWindow)));
nsIntRegion region;
#if defined(MOZ_WIDGET_GTK2)
GdkRectangle *r = rects;
GdkRectangle *r_end = rects + nrects;
#else
cairo_rectangle_t *r = rects->rectangles;
cairo_rectangle_t *r_end = r + rects->num_rectangles;
#endif
for (; r < r_end; ++r) {
region.Or(region, nsIntRect(r->x, r->y, r->width, r->height));
LOGDRAW(("\t%d %d %d %d\n", r->x, r->y, r->width, r->height));
}
// Our bounds may have changed after calling WillPaintWindow. Clip
// to the new bounds here. The region is relative to this
// window.
region.And(region, nsIntRect(0, 0, mBounds.width, mBounds.height));
bool shaped = false;
if (eTransparencyTransparent == GetTransparencyMode()) {
GdkScreen *screen = gdk_window_get_screen(mGdkWindow);
if (gdk_screen_is_composited(screen) &&
gdk_window_get_visual(mGdkWindow) ==
gdk_screen_get_rgba_visual(screen)) {
// Remove possible shape mask from when window manger was not
// previously compositing.
static_cast<nsWindow*>(GetTopLevelWidget())->
ClearTransparencyBitmap();
} else {
shaped = true;
}
}
if (!shaped) {
GList *children =
gdk_window_peek_children(mGdkWindow);
while (children) {
GdkWindow *gdkWin = GDK_WINDOW(children->data);
nsWindow *kid = get_window_for_gdk_window(gdkWin);
if (kid && gdk_window_is_visible(gdkWin)) {
nsAutoTArray<nsIntRect,1> clipRects;
kid->GetWindowClipRegion(&clipRects);
nsIntRect bounds;
kid->GetBounds(bounds);
for (uint32_t i = 0; i < clipRects.Length(); ++i) {
nsIntRect r = clipRects[i] + bounds.TopLeft();
region.Sub(region, r);
}
}
children = children->next;
}
}
if (region.IsEmpty()) {
#if defined(MOZ_WIDGET_GTK2)
g_free(rects);
#else
cairo_rectangle_list_destroy(rects);
#endif
return TRUE;
}
// If this widget uses OMTC...
if (GetLayerManager()->AsShadowForwarder() && GetLayerManager()->AsShadowForwarder()->HasShadowManager()) {
#if defined(MOZ_WIDGET_GTK2)
nsRefPtr<gfxContext> ctx = new gfxContext(GetThebesSurface());
#else
nsRefPtr<gfxContext> ctx = new gfxContext(GetThebesSurface(cr));
#endif
nsBaseWidget::AutoLayerManagerSetup
setupLayerManager(this, ctx, mozilla::layers::BUFFER_NONE);
listener->PaintWindow(this, region, 0);
listener->DidPaintWindow();
g_free(rects);
return TRUE;
} else if (GetLayerManager()->GetBackendType() == mozilla::layers::LAYERS_OPENGL) {
LayerManagerOGL *manager = static_cast<LayerManagerOGL*>(GetLayerManager());
manager->SetClippingRegion(region);
listener->PaintWindow(this, region, 0);
listener->DidPaintWindow();
g_free(rects);
return TRUE;
}
#if defined(MOZ_WIDGET_GTK2)
nsRefPtr<gfxContext> ctx = new gfxContext(GetThebesSurface());
#else
nsRefPtr<gfxContext> ctx = new gfxContext(GetThebesSurface(cr));
#endif
#ifdef MOZ_X11
nsIntRect boundsRect; // for shaped only
ctx->NewPath();
if (shaped) {
// Collapse update area to the bounding box. This is so we only have to
// call UpdateTranslucentWindowAlpha once. After we have dropped
// support for non-Thebes graphics, UpdateTranslucentWindowAlpha will be
// our private interface so we can rework things to avoid this.
boundsRect = region.GetBounds();
ctx->Rectangle(gfxRect(boundsRect.x, boundsRect.y,
boundsRect.width, boundsRect.height));
} else {
gfxUtils::PathFromRegion(ctx, region);
}
ctx->Clip();
BufferMode layerBuffering;
if (shaped) {
// The double buffering is done here to extract the shape mask.
// (The shape mask won't be necessary when a visual with an alpha
// channel is used on compositing window managers.)
layerBuffering = mozilla::layers::BUFFER_NONE;
ctx->PushGroup(gfxASurface::CONTENT_COLOR_ALPHA);
#ifdef MOZ_HAVE_SHMIMAGE
} else if (nsShmImage::UseShm()) {
// We're using an xshm mapping as a back buffer.
layerBuffering = mozilla::layers::BUFFER_NONE;
#endif // MOZ_HAVE_SHMIMAGE
} else {
// Get the layer manager to do double buffering (if necessary).
layerBuffering = mozilla::layers::BUFFER_BUFFERED;
}
#if 0
// NOTE: Paint flashing region would be wrong for cairo, since
// cairo inflates the update region, etc. So don't paint flash
// for cairo.
#ifdef DEBUG
// XXX aEvent->region may refer to a newly-invalid area. FIXME
if (0 && WANT_PAINT_FLASHING && gtk_widget_get_window(aEvent))
gdk_window_flash(mGdkWindow, 1, 100, aEvent->region);
#endif
#endif
#endif // MOZ_X11
bool painted = false;
{
AutoLayerManagerSetup setupLayerManager(this, ctx, layerBuffering);
painted = listener->PaintWindow(this, region, 0);
}
#ifdef MOZ_X11
// PaintWindow can Destroy us (bug 378273), avoid doing any paint
// operations below if that happened - it will lead to XError and exit().
if (shaped) {
if (MOZ_LIKELY(!mIsDestroyed)) {
if (painted) {
nsRefPtr<gfxPattern> pattern = ctx->PopGroup();
nsRefPtr<gfxImageSurface> img =
new gfxImageSurface(gfxIntSize(boundsRect.width, boundsRect.height),
gfxImageSurface::ImageFormatA8);
if (img && !img->CairoStatus()) {
img->SetDeviceOffset(gfxPoint(-boundsRect.x, -boundsRect.y));
nsRefPtr<gfxContext> imgCtx = new gfxContext(img);
if (imgCtx) {
imgCtx->SetPattern(pattern);
imgCtx->SetOperator(gfxContext::OPERATOR_SOURCE);
imgCtx->Paint();
}
UpdateTranslucentWindowAlphaInternal(nsIntRect(boundsRect.x, boundsRect.y,
boundsRect.width, boundsRect.height),
img->Data(), img->Stride());
}
ctx->SetOperator(gfxContext::OPERATOR_SOURCE);
ctx->SetPattern(pattern);
ctx->Paint();
}
}
}
# ifdef MOZ_HAVE_SHMIMAGE
if (nsShmImage::UseShm() && MOZ_LIKELY(!mIsDestroyed)) {
#if defined(MOZ_WIDGET_GTK2)
mShmImage->Put(mGdkWindow, rects, r_end);
#else
mShmImage->Put(mGdkWindow, rects);
#endif
}
# endif // MOZ_HAVE_SHMIMAGE
#endif // MOZ_X11
#if defined(MOZ_WIDGET_GTK2)
g_free(rects);
#else
cairo_rectangle_list_destroy(rects);
#endif
listener->DidPaintWindow();
// Synchronously flush any new dirty areas
#if defined(MOZ_WIDGET_GTK2)
GdkRegion* dirtyArea = gdk_window_get_update_area(mGdkWindow);
#else
cairo_region_t* dirtyArea = gdk_window_get_update_area(mGdkWindow);
#endif
if (dirtyArea) {
gdk_window_invalidate_region(mGdkWindow, dirtyArea, false);
#if defined(MOZ_WIDGET_GTK2)
gdk_region_destroy(dirtyArea);
#else
cairo_region_destroy(dirtyArea);
#endif
gdk_window_process_updates(mGdkWindow, false);
}
// check the return value!
return TRUE;
}
gboolean
nsWindow::OnConfigureEvent(GtkWidget *aWidget, GdkEventConfigure *aEvent)
{
// These events are only received on toplevel windows.
//
// GDK ensures that the coordinates are the client window top-left wrt the
// root window.
//
// GDK calculates the cordinates for real ConfigureNotify events on
// managed windows (that would normally be relative to the parent
// window).
//
// Synthetic ConfigureNotify events are from the window manager and
// already relative to the root window. GDK creates all X windows with
// border_width = 0, so synthetic events also indicate the top-left of
// the client window.
//
// Override-redirect windows are children of the root window so parent
// coordinates are root coordinates.
LOG(("configure event [%p] %d %d %d %d\n", (void *)this,
aEvent->x, aEvent->y, aEvent->width, aEvent->height));
nsIntRect screenBounds;
GetScreenBounds(screenBounds);
if (mWindowType == eWindowType_toplevel || mWindowType == eWindowType_dialog) {
// This check avoids unwanted rollup on spurious configure events from
// Cygwin/X (bug 672103).
if (mBounds.x != screenBounds.x ||
mBounds.y != screenBounds.y) {
CheckForRollup(0, 0, false, true);
}
}
// This event indicates that the window position may have changed.
// mBounds.Size() is updated in OnSizeAllocate().
// (The gtk_window_get_window_type() function is only available from
// version 2.20.)
NS_ASSERTION(GTK_IS_WINDOW(aWidget),
"Configure event on widget that is not a GtkWindow");
gint type;
g_object_get(aWidget, "type", &type, NULL);
if (type == GTK_WINDOW_POPUP) {
// Override-redirect window
//
// These windows should not be moved by the window manager, and so any
// change in position is a result of our direction. mBounds has
// already been set in Move() or Resize(), and that is more
// up-to-date than the position in the ConfigureNotify event if the
// event is from an earlier window move.
//
// Skipping the WindowMoved call saves context menus from an infinite
// loop when nsXULPopupManager::PopupMoved moves the window to the new
// position and nsMenuPopupFrame::SetPopupPosition adds
// offsetForContextMenu on each iteration.
return FALSE;
}
mBounds.MoveTo(screenBounds.TopLeft());
// XXX mozilla will invalidate the entire window after this move
// complete. wtf?
if (mWidgetListener)
mWidgetListener->WindowMoved(this, mBounds.x, mBounds.y);
return FALSE;
}
void
nsWindow::OnContainerUnrealize()
{
// The GdkWindows are about to be destroyed (but not deleted), so remove
// their references back to their container widget while the GdkWindow
// hierarchy is still available.
if (mGdkWindow) {
DestroyChildWindows();
g_object_set_data(G_OBJECT(mGdkWindow), "nsWindow", NULL);
mGdkWindow = NULL;
}
}
void
nsWindow::OnSizeAllocate(GtkAllocation *aAllocation)
{
LOG(("size_allocate [%p] %d %d %d %d\n",
(void *)this, aAllocation->x, aAllocation->y,
aAllocation->width, aAllocation->height));
nsIntSize size(aAllocation->width, aAllocation->height);
if (mBounds.Size() == size)
return;
// Invalidate the new part of the window now for the pending paint to
// minimize background flashes (GDK does not do this for external resizes
// of toplevels.)
if (mBounds.width < size.width) {
GdkRectangle rect =
{ mBounds.width, 0, size.width - mBounds.width, size.height };
gdk_window_invalidate_rect(mGdkWindow, &rect, FALSE);
}
if (mBounds.height < size.height) {
GdkRectangle rect =
{ 0, mBounds.height, size.width, size.height - mBounds.height };
gdk_window_invalidate_rect(mGdkWindow, &rect, FALSE);
}
mBounds.SizeTo(size);
if (!mGdkWindow)
return;
DispatchResized(size.width, size.height);
}
void
nsWindow::OnDeleteEvent()
{
if (mWidgetListener)
mWidgetListener->RequestWindowClose(this);
}
void
nsWindow::OnEnterNotifyEvent(GdkEventCrossing *aEvent)
{
// This skips NotifyVirtual and NotifyNonlinearVirtual enter notify events
// when the pointer enters a child window. If the destination window is a
// Gecko window then we'll catch the corresponding event on that window,
// but we won't notice when the pointer directly enters a foreign (plugin)
// child window without passing over a visible portion of a Gecko window.
if (aEvent->subwindow != NULL)
return;
// Check before is_parent_ungrab_enter() as the button state may have
// changed while a non-Gecko ancestor window had a pointer grab.
DispatchMissedButtonReleases(aEvent);
if (is_parent_ungrab_enter(aEvent))
return;
nsMouseEvent event(true, NS_MOUSE_ENTER, this, nsMouseEvent::eReal);
event.refPoint.x = nscoord(aEvent->x);
event.refPoint.y = nscoord(aEvent->y);
event.time = aEvent->time;
LOG(("OnEnterNotify: %p\n", (void *)this));
nsEventStatus status;
DispatchEvent(&event, status);
}
// XXX Is this the right test for embedding cases?
static bool
is_top_level_mouse_exit(GdkWindow* aWindow, GdkEventCrossing *aEvent)
{
gint x = gint(aEvent->x_root);
gint y = gint(aEvent->y_root);
GdkDisplay* display = gdk_window_get_display(aWindow);
GdkWindow* winAtPt = gdk_display_get_window_at_pointer(display, &x, &y);
if (!winAtPt)
return true;
GdkWindow* topLevelAtPt = gdk_window_get_toplevel(winAtPt);
GdkWindow* topLevelWidget = gdk_window_get_toplevel(aWindow);
return topLevelAtPt != topLevelWidget;
}
void
nsWindow::OnLeaveNotifyEvent(GdkEventCrossing *aEvent)
{
// This ignores NotifyVirtual and NotifyNonlinearVirtual leave notify
// events when the pointer leaves a child window. If the destination
// window is a Gecko window then we'll catch the corresponding event on
// that window.
//
// XXXkt However, we will miss toplevel exits when the pointer directly
// leaves a foreign (plugin) child window without passing over a visible
// portion of a Gecko window.
if (aEvent->subwindow != NULL)
return;
nsMouseEvent event(true, NS_MOUSE_EXIT, this, nsMouseEvent::eReal);
event.refPoint.x = nscoord(aEvent->x);
event.refPoint.y = nscoord(aEvent->y);
event.time = aEvent->time;
event.exit = is_top_level_mouse_exit(mGdkWindow, aEvent)
? nsMouseEvent::eTopLevel : nsMouseEvent::eChild;
LOG(("OnLeaveNotify: %p\n", (void *)this));
nsEventStatus status;
DispatchEvent(&event, status);
}
void
nsWindow::OnMotionNotifyEvent(GdkEventMotion *aEvent)
{
// see if we can compress this event
// XXXldb Why skip every other motion event when we have multiple,
// but not more than that?
bool synthEvent = false;
#ifdef MOZ_X11
XEvent xevent;
while (XPending (GDK_WINDOW_XDISPLAY(aEvent->window))) {
XEvent peeked;
XPeekEvent (GDK_WINDOW_XDISPLAY(aEvent->window), &peeked);
if (peeked.xany.window != gdk_x11_window_get_xid(aEvent->window)
|| peeked.type != MotionNotify)
break;
synthEvent = true;
XNextEvent (GDK_WINDOW_XDISPLAY(aEvent->window), &xevent);
}
#if defined(MOZ_WIDGET_GTK2)
// if plugins still keeps the focus, get it back
if (gPluginFocusWindow && gPluginFocusWindow != this) {
nsRefPtr<nsWindow> kungFuDeathGrip = gPluginFocusWindow;
gPluginFocusWindow->LoseNonXEmbedPluginFocus();
}
#endif /* MOZ_WIDGET_GTK2 */
#endif /* MOZ_X11 */
nsMouseEvent event(true, NS_MOUSE_MOVE, this, nsMouseEvent::eReal);
gdouble pressure = 0;
gdk_event_get_axis ((GdkEvent*)aEvent, GDK_AXIS_PRESSURE, &pressure);
// Sometime gdk generate 0 pressure value between normal values
// We have to ignore that and use last valid value
if (pressure)
mLastMotionPressure = pressure;
event.pressure = mLastMotionPressure;
guint modifierState;
if (synthEvent) {
#ifdef MOZ_X11
event.refPoint.x = nscoord(xevent.xmotion.x);
event.refPoint.y = nscoord(xevent.xmotion.y);
modifierState = xevent.xmotion.state;
event.time = xevent.xmotion.time;
#else
event.refPoint.x = nscoord(aEvent->x);
event.refPoint.y = nscoord(aEvent->y);
modifierState = aEvent->state;
event.time = aEvent->time;
#endif /* MOZ_X11 */
}
else {
// XXX see OnScrollEvent()
if (aEvent->window == mGdkWindow) {
event.refPoint.x = nscoord(aEvent->x);
event.refPoint.y = nscoord(aEvent->y);
} else {
nsIntPoint point(NSToIntFloor(aEvent->x_root), NSToIntFloor(aEvent->y_root));
event.refPoint = point - WidgetToScreenOffset();
}
modifierState = aEvent->state;
event.time = aEvent->time;
}
KeymapWrapper::InitInputEvent(event, modifierState);
nsEventStatus status;
DispatchEvent(&event, status);
}
// If the automatic pointer grab on ButtonPress has deactivated before
// ButtonRelease, and the mouse button is released while the pointer is not
// over any a Gecko window, then the ButtonRelease event will not be received.
// (A similar situation exists when the pointer is grabbed with owner_events
// True as the ButtonRelease may be received on a foreign [plugin] window).
// Use this method to check for released buttons when the pointer returns to a
// Gecko window.
void
nsWindow::DispatchMissedButtonReleases(GdkEventCrossing *aGdkEvent)
{
guint changed = aGdkEvent->state ^ gButtonState;
// Only consider button releases.
// (Ignore button presses that occurred outside Gecko.)
guint released = changed & gButtonState;
gButtonState = aGdkEvent->state;
// Loop over each button, excluding mouse wheel buttons 4 and 5 for which
// GDK ignores releases.
for (guint buttonMask = GDK_BUTTON1_MASK;
buttonMask <= GDK_BUTTON3_MASK;
buttonMask <<= 1) {
if (released & buttonMask) {
int16_t buttonType;
switch (buttonMask) {
case GDK_BUTTON1_MASK:
buttonType = nsMouseEvent::eLeftButton;
break;
case GDK_BUTTON2_MASK:
buttonType = nsMouseEvent::eMiddleButton;
break;
default:
NS_ASSERTION(buttonMask == GDK_BUTTON3_MASK,
"Unexpected button mask");
buttonType = nsMouseEvent::eRightButton;
}
LOG(("Synthesized button %u release on %p\n",
guint(buttonType + 1), (void *)this));
// Dispatch a synthesized button up event to tell Gecko about the
// change in state. This event is marked as synthesized so that
// it is not dispatched as a DOM event, because we don't know the
// position, widget, modifiers, or time/order.
nsMouseEvent synthEvent(true, NS_MOUSE_BUTTON_UP, this,
nsMouseEvent::eSynthesized);
synthEvent.button = buttonType;
nsEventStatus status;
DispatchEvent(&synthEvent, status);
}
}
}
void
nsWindow::InitButtonEvent(nsMouseEvent &aEvent,
GdkEventButton *aGdkEvent)
{
// XXX see OnScrollEvent()
if (aGdkEvent->window == mGdkWindow) {
aEvent.refPoint.x = nscoord(aGdkEvent->x);
aEvent.refPoint.y = nscoord(aGdkEvent->y);
} else {
nsIntPoint point(NSToIntFloor(aGdkEvent->x_root), NSToIntFloor(aGdkEvent->y_root));
aEvent.refPoint = point - WidgetToScreenOffset();
}
guint modifierState = aGdkEvent->state;
// aEvent's state doesn't include this event's information. Therefore,
// if aEvent is mouse button down event, we need to set it manually.
// Note that we cannot do same thing for NS_MOUSE_BUTTON_UP because
// system may have two or more mice and same button of another mouse
// may be still pressed.
if (aGdkEvent->type != GDK_BUTTON_RELEASE) {
switch (aGdkEvent->button) {
case 1:
modifierState |= GDK_BUTTON1_MASK;
break;
case 2:
modifierState |= GDK_BUTTON2_MASK;
break;
case 3:
modifierState |= GDK_BUTTON3_MASK;
break;
}
}
KeymapWrapper::InitInputEvent(aEvent, modifierState);
aEvent.time = aGdkEvent->time;
switch (aGdkEvent->type) {
case GDK_2BUTTON_PRESS:
aEvent.clickCount = 2;
break;
case GDK_3BUTTON_PRESS:
aEvent.clickCount = 3;
break;
// default is one click
default:
aEvent.clickCount = 1;
}
}
static guint ButtonMaskFromGDKButton(guint button)
{
return GDK_BUTTON1_MASK << (button - 1);
}
void
nsWindow::OnButtonPressEvent(GdkEventButton *aEvent)
{
LOG(("Button %u press on %p\n", aEvent->button, (void *)this));
nsEventStatus status;
// If you double click in GDK, it will actually generate a second
// GDK_BUTTON_PRESS before sending the GDK_2BUTTON_PRESS, and this is
// different than the DOM spec. GDK puts this in the queue
// programatically, so it's safe to assume that if there's a
// double click in the queue, it was generated so we can just drop
// this click.
GdkEvent *peekedEvent = gdk_event_peek();
if (peekedEvent) {
GdkEventType type = peekedEvent->any.type;
gdk_event_free(peekedEvent);
if (type == GDK_2BUTTON_PRESS || type == GDK_3BUTTON_PRESS)
return;
}
nsWindow *containerWindow = GetContainerWindow();
if (!gFocusWindow && containerWindow) {
containerWindow->DispatchActivateEvent();
}
// check to see if we should rollup
if (CheckForRollup(aEvent->x_root, aEvent->y_root, false, false))
return;
gdouble pressure = 0;
gdk_event_get_axis ((GdkEvent*)aEvent, GDK_AXIS_PRESSURE, &pressure);
mLastMotionPressure = pressure;
uint16_t domButton;
switch (aEvent->button) {
case 1:
domButton = nsMouseEvent::eLeftButton;
break;
case 2:
domButton = nsMouseEvent::eMiddleButton;
break;
case 3:
domButton = nsMouseEvent::eRightButton;
break;
// These are mapped to horizontal scroll
case 6:
case 7:
NS_WARNING("We're not supporting legacy horizontal scroll event");
return;
// Map buttons 8-9 to back/forward
case 8:
DispatchCommandEvent(nsGkAtoms::Back);
return;
case 9:
DispatchCommandEvent(nsGkAtoms::Forward);
return;
default:
return;
}
gButtonState |= ButtonMaskFromGDKButton(aEvent->button);
nsMouseEvent event(true, NS_MOUSE_BUTTON_DOWN, this, nsMouseEvent::eReal);
event.button = domButton;
InitButtonEvent(event, aEvent);
event.pressure = mLastMotionPressure;
DispatchEvent(&event, status);
// right menu click on linux should also pop up a context menu
if (domButton == nsMouseEvent::eRightButton &&
MOZ_LIKELY(!mIsDestroyed)) {
nsMouseEvent contextMenuEvent(true, NS_CONTEXTMENU, this,
nsMouseEvent::eReal);
InitButtonEvent(contextMenuEvent, aEvent);
contextMenuEvent.pressure = mLastMotionPressure;
DispatchEvent(&contextMenuEvent, status);
}
}
void
nsWindow::OnButtonReleaseEvent(GdkEventButton *aEvent)
{
LOG(("Button %u release on %p\n", aEvent->button, (void *)this));
uint16_t domButton;
switch (aEvent->button) {
case 1:
domButton = nsMouseEvent::eLeftButton;
break;
case 2:
domButton = nsMouseEvent::eMiddleButton;
break;
case 3:
domButton = nsMouseEvent::eRightButton;
break;
default:
return;
}
gButtonState &= ~ButtonMaskFromGDKButton(aEvent->button);
nsMouseEvent event(true, NS_MOUSE_BUTTON_UP, this, nsMouseEvent::eReal);
event.button = domButton;
InitButtonEvent(event, aEvent);
gdouble pressure = 0;
gdk_event_get_axis ((GdkEvent*)aEvent, GDK_AXIS_PRESSURE, &pressure);
event.pressure = pressure ? pressure : mLastMotionPressure;
nsEventStatus status;
DispatchEvent(&event, status);
mLastMotionPressure = pressure;
}
void
nsWindow::OnContainerFocusInEvent(GdkEventFocus *aEvent)
{
NS_ASSERTION(mWindowType != eWindowType_popup,
"Unexpected focus on a popup window");
LOGFOCUS(("OnContainerFocusInEvent [%p]\n", (void *)this));
// Unset the urgency hint, if possible
GtkWidget* top_window = GetToplevelWidget();
if (top_window && (gtk_widget_get_visible(top_window)))
SetUrgencyHint(top_window, false);
// Return if being called within SetFocus because the focus manager
// already knows that the window is active.
if (gBlockActivateEvent) {
LOGFOCUS(("activated notification is blocked [%p]\n", (void *)this));
return;
}
// If keyboard input will be accepted, the focus manager will call
// SetFocus to set the correct window.
gFocusWindow = nullptr;
DispatchActivateEvent();
if (!gFocusWindow) {
// We don't really have a window for dispatching key events, but
// setting a non-NULL value here prevents OnButtonPressEvent() from
// dispatching an activation notification if the widget is already
// active.
gFocusWindow = this;
}
LOGFOCUS(("Events sent from focus in event [%p]\n", (void *)this));
}
void
nsWindow::OnContainerFocusOutEvent(GdkEventFocus *aEvent)
{
LOGFOCUS(("OnContainerFocusOutEvent [%p]\n", (void *)this));
if (mWindowType == eWindowType_toplevel || mWindowType == eWindowType_dialog) {
nsCOMPtr<nsIDragService> dragService = do_GetService(kCDragServiceCID);
nsCOMPtr<nsIDragSession> dragSession;
dragService->GetCurrentSession(getter_AddRefs(dragSession));
// Rollup popups when a window is focused out unless a drag is occurring.
// This check is because drags grab the keyboard and cause a focus out on
// versions of GTK before 2.18.
bool shouldRollup = !dragSession;
if (!shouldRollup) {
// we also roll up when a drag is from a different application
nsCOMPtr<nsIDOMNode> sourceNode;
dragSession->GetSourceNode(getter_AddRefs(sourceNode));
shouldRollup = (sourceNode == nullptr);
}
if (shouldRollup) {
CheckForRollup(0, 0, false, true);
}
}
#if defined(MOZ_WIDGET_GTK2) && defined(MOZ_X11)
// plugin lose focus
if (gPluginFocusWindow) {
nsRefPtr<nsWindow> kungFuDeathGrip = gPluginFocusWindow;
gPluginFocusWindow->LoseNonXEmbedPluginFocus();
}
#endif /* MOZ_X11 && MOZ_WIDGET_GTK2 */
if (gFocusWindow) {
nsRefPtr<nsWindow> kungFuDeathGrip = gFocusWindow;
if (gFocusWindow->mIMModule) {
gFocusWindow->mIMModule->OnBlurWindow(gFocusWindow);
}
gFocusWindow = nullptr;
}
DispatchDeactivateEvent();
LOGFOCUS(("Done with container focus out [%p]\n", (void *)this));
}
bool
nsWindow::DispatchCommandEvent(nsIAtom* aCommand)
{
nsEventStatus status;
nsCommandEvent event(true, nsGkAtoms::onAppCommand, aCommand, this);
DispatchEvent(&event, status);
return TRUE;
}
bool
nsWindow::DispatchContentCommandEvent(int32_t aMsg)
{
nsEventStatus status;
nsContentCommandEvent event(true, aMsg, this);
DispatchEvent(&event, status);
return TRUE;
}
static bool
IsCtrlAltTab(GdkEventKey *aEvent)
{
return aEvent->keyval == GDK_Tab &&
KeymapWrapper::AreModifiersActive(
KeymapWrapper::CTRL | KeymapWrapper::ALT, aEvent->state);
}
bool
nsWindow::DispatchKeyDownEvent(GdkEventKey *aEvent, bool *aCancelled)
{
NS_PRECONDITION(aCancelled, "aCancelled must not be null");
*aCancelled = false;
if (IsCtrlAltTab(aEvent)) {
return false;
}
// send the key down event
nsEventStatus status;
nsKeyEvent downEvent(true, NS_KEY_DOWN, this);
KeymapWrapper::InitKeyEvent(downEvent, aEvent);
DispatchEvent(&downEvent, status);
*aCancelled = (status == nsEventStatus_eConsumeNoDefault);
return true;
}
gboolean
nsWindow::OnKeyPressEvent(GdkEventKey *aEvent)
{
LOGFOCUS(("OnKeyPressEvent [%p]\n", (void *)this));
// if we are in the middle of composing text, XIM gets to see it
// before mozilla does.
bool IMEWasEnabled = false;
if (mIMModule) {
IMEWasEnabled = mIMModule->IsEnabled();
if (mIMModule->OnKeyEvent(this, aEvent)) {
return TRUE;
}
}
nsEventStatus status;
// work around for annoying things.
if (IsCtrlAltTab(aEvent)) {
return TRUE;
}
nsCOMPtr<nsIWidget> kungFuDeathGrip = this;
// Dispatch keydown event always. At auto repeating, we should send
// KEYDOWN -> KEYPRESS -> KEYDOWN -> KEYPRESS ... -> KEYUP
// However, old distributions (e.g., Ubuntu 9.10) sent native key
// release event, so, on such platform, the DOM events will be:
// KEYDOWN -> KEYPRESS -> KEYUP -> KEYDOWN -> KEYPRESS -> KEYUP...
bool isKeyDownCancelled = false;
if (DispatchKeyDownEvent(aEvent, &isKeyDownCancelled) &&
MOZ_UNLIKELY(mIsDestroyed)) {
return TRUE;
}
// If a keydown event handler causes to enable IME, i.e., it moves
// focus from IME unusable content to IME usable editor, we should
// send the native key event to IME for the first input on the editor.
if (!IMEWasEnabled && mIMModule && mIMModule->IsEnabled()) {
// Notice our keydown event was already dispatched. This prevents
// unnecessary DOM keydown event in the editor.
if (mIMModule->OnKeyEvent(this, aEvent, true)) {
return TRUE;
}
}
// Don't pass modifiers as NS_KEY_PRESS events.
// TODO: Instead of selectively excluding some keys from NS_KEY_PRESS events,
// we should instead selectively include (as per MSDN spec; no official
// spec covers KeyPress events).
if (!KeymapWrapper::IsKeyPressEventNecessary(aEvent)) {
return TRUE;
}
#ifdef MOZ_X11
#if ! defined AIX // no XFree86 on AIX 5L
// Look for specialized app-command keys
switch (aEvent->keyval) {
case XF86XK_Back:
return DispatchCommandEvent(nsGkAtoms::Back);
case XF86XK_Forward:
return DispatchCommandEvent(nsGkAtoms::Forward);
case XF86XK_Refresh:
return DispatchCommandEvent(nsGkAtoms::Reload);
case XF86XK_Stop:
return DispatchCommandEvent(nsGkAtoms::Stop);
case XF86XK_Search:
return DispatchCommandEvent(nsGkAtoms::Search);
case XF86XK_Favorites:
return DispatchCommandEvent(nsGkAtoms::Bookmarks);
case XF86XK_HomePage:
return DispatchCommandEvent(nsGkAtoms::Home);
case XF86XK_Copy:
case GDK_F16: // F16, F20, F18, F14 are old keysyms for Copy Cut Paste Undo
return DispatchContentCommandEvent(NS_CONTENT_COMMAND_COPY);
case XF86XK_Cut:
case GDK_F20:
return DispatchContentCommandEvent(NS_CONTENT_COMMAND_CUT);
case XF86XK_Paste:
case GDK_F18:
return DispatchContentCommandEvent(NS_CONTENT_COMMAND_PASTE);
case GDK_Redo:
return DispatchContentCommandEvent(NS_CONTENT_COMMAND_REDO);
case GDK_Undo:
case GDK_F14:
return DispatchContentCommandEvent(NS_CONTENT_COMMAND_UNDO);
}
#endif /* ! AIX */
#endif /* MOZ_X11 */
nsKeyEvent event(true, NS_KEY_PRESS, this);
KeymapWrapper::InitKeyEvent(event, aEvent);
if (isKeyDownCancelled) {
// If prevent default set for onkeydown, do the same for onkeypress
event.mFlags.mDefaultPrevented = true;
}
// before we dispatch a key, check if it's the context menu key.
// If so, send a context menu key event instead.
if (is_context_menu_key(event)) {
nsMouseEvent contextMenuEvent(true, NS_CONTEXTMENU, this,
nsMouseEvent::eReal,
nsMouseEvent::eContextMenuKey);
contextMenuEvent.refPoint = nsIntPoint(0, 0);
contextMenuEvent.time = aEvent->time;
contextMenuEvent.clickCount = 1;
KeymapWrapper::InitInputEvent(contextMenuEvent, aEvent->state);
DispatchEvent(&contextMenuEvent, status);
}
else {
// If the character code is in the BMP, send the key press event.
// Otherwise, send a text event with the equivalent UTF-16 string.
if (IS_IN_BMP(event.charCode)) {
DispatchEvent(&event, status);
}
else {
nsTextEvent textEvent(true, NS_TEXT_TEXT, this);
PRUnichar textString[3];
textString[0] = H_SURROGATE(event.charCode);
textString[1] = L_SURROGATE(event.charCode);
textString[2] = 0;
textEvent.theText = textString;
textEvent.time = event.time;
DispatchEvent(&textEvent, status);
}
}
// If the event was consumed, return.
if (status == nsEventStatus_eConsumeNoDefault) {
return TRUE;
}
return FALSE;
}
gboolean
nsWindow::OnKeyReleaseEvent(GdkEventKey *aEvent)
{
LOGFOCUS(("OnKeyReleaseEvent [%p]\n", (void *)this));
if (mIMModule && mIMModule->OnKeyEvent(this, aEvent)) {
return TRUE;
}
// send the key event as a key up event
nsKeyEvent event(true, NS_KEY_UP, this);
KeymapWrapper::InitKeyEvent(event, aEvent);
nsEventStatus status;
DispatchEvent(&event, status);
// If the event was consumed, return.
if (status == nsEventStatus_eConsumeNoDefault) {
return TRUE;
}
return FALSE;
}
void
nsWindow::OnScrollEvent(GdkEventScroll *aEvent)
{
// check to see if we should rollup
if (CheckForRollup(aEvent->x_root, aEvent->y_root, true, false))
return;
WheelEvent wheelEvent(true, NS_WHEEL_WHEEL, this);
wheelEvent.deltaMode = nsIDOMWheelEvent::DOM_DELTA_LINE;
switch (aEvent->direction) {
case GDK_SCROLL_UP:
wheelEvent.deltaY = wheelEvent.lineOrPageDeltaY = -3;
break;
case GDK_SCROLL_DOWN:
wheelEvent.deltaY = wheelEvent.lineOrPageDeltaY = 3;
break;
case GDK_SCROLL_LEFT:
wheelEvent.deltaX = wheelEvent.lineOrPageDeltaX = -1;
break;
case GDK_SCROLL_RIGHT:
wheelEvent.deltaX = wheelEvent.lineOrPageDeltaX = 1;
break;
}
NS_ASSERTION(wheelEvent.deltaX || wheelEvent.deltaY,
"deltaX or deltaY must be non-zero");
if (aEvent->window == mGdkWindow) {
// we are the window that the event happened on so no need for expensive WidgetToScreenOffset
wheelEvent.refPoint.x = nscoord(aEvent->x);
wheelEvent.refPoint.y = nscoord(aEvent->y);
} else {
// XXX we're never quite sure which GdkWindow the event came from due to our custom bubbling
// in scroll_event_cb(), so use ScreenToWidget to translate the screen root coordinates into
// coordinates relative to this widget.
nsIntPoint point(NSToIntFloor(aEvent->x_root), NSToIntFloor(aEvent->y_root));
wheelEvent.refPoint = point - WidgetToScreenOffset();
}
KeymapWrapper::InitInputEvent(wheelEvent, aEvent->state);
wheelEvent.time = aEvent->time;
nsEventStatus status;
DispatchEvent(&wheelEvent, status);
}
void
nsWindow::OnVisibilityNotifyEvent(GdkEventVisibility *aEvent)
{
LOGDRAW(("Visibility event %i on [%p] %p\n",
aEvent->state, this, aEvent->window));
if (!mGdkWindow)
return;
switch (aEvent->state) {
case GDK_VISIBILITY_UNOBSCURED:
case GDK_VISIBILITY_PARTIAL:
if (mIsFullyObscured && mHasMappedToplevel) {
// GDK_EXPOSE events have been ignored, so make sure GDK
// doesn't think that the window has already been painted.
gdk_window_invalidate_rect(mGdkWindow, NULL, FALSE);
}
mIsFullyObscured = false;
// In Hildon/Maemo, a browser window will get into 'patially visible' state wheneven an
// autocomplete feature is dropped down (from urlbar or from an entry form completion),
// and there are no much further ways for that to happen in the plaftorm. In such cases, if hildon
// virtual keyboard is up, we can not grab focus to any dropdown list. Reason: nsWindow::EnsureGrabs()
// calls gdk_pointer_grab() which grabs the pointer (usually a mouse) so that all events are passed
// to this it until the pointer is ungrabbed.
if (!nsGtkIMModule::IsVirtualKeyboardOpened()) {
// if we have to retry the grab, retry it.
EnsureGrabs();
}
break;
default: // includes GDK_VISIBILITY_FULLY_OBSCURED
mIsFullyObscured = true;
break;
}
}
void
nsWindow::OnWindowStateEvent(GtkWidget *aWidget, GdkEventWindowState *aEvent)
{
LOG(("nsWindow::OnWindowStateEvent [%p] changed %d new_window_state %d\n",
(void *)this, aEvent->changed_mask, aEvent->new_window_state));
if (IS_MOZ_CONTAINER(aWidget)) {
// This event is notifying the container widget of changes to the
// toplevel window. Just detect changes affecting whether windows are
// viewable.
//
// (A visibility notify event is sent to each window that becomes
// viewable when the toplevel is mapped, but we can't rely on that for
// setting mHasMappedToplevel because these toplevel window state
// events are asynchronous. The windows in the hierarchy now may not
// be the same windows as when the toplevel was mapped, so they may
// not get VisibilityNotify events.)
bool mapped =
!(aEvent->new_window_state &
(GDK_WINDOW_STATE_ICONIFIED|GDK_WINDOW_STATE_WITHDRAWN));
if (mHasMappedToplevel != mapped) {
SetHasMappedToplevel(mapped);
}
return;
}
// else the widget is a shell widget.
// We don't care about anything but changes in the maximized/icon/fullscreen
// states
if ((aEvent->changed_mask
& (GDK_WINDOW_STATE_ICONIFIED |
GDK_WINDOW_STATE_MAXIMIZED |
GDK_WINDOW_STATE_FULLSCREEN)) == 0) {
return;
}
if (aEvent->new_window_state & GDK_WINDOW_STATE_ICONIFIED) {
LOG(("\tIconified\n"));
mSizeState = nsSizeMode_Minimized;
#ifdef ACCESSIBILITY
DispatchMinimizeEventAccessible();
#endif //ACCESSIBILITY
}
else if (aEvent->new_window_state & GDK_WINDOW_STATE_FULLSCREEN) {
LOG(("\tFullscreen\n"));
mSizeState = nsSizeMode_Fullscreen;
}
else if (aEvent->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) {
LOG(("\tMaximized\n"));
mSizeState = nsSizeMode_Maximized;
#ifdef ACCESSIBILITY
DispatchMaximizeEventAccessible();
#endif //ACCESSIBILITY
}
else {
LOG(("\tNormal\n"));
mSizeState = nsSizeMode_Normal;
#ifdef ACCESSIBILITY
DispatchRestoreEventAccessible();
#endif //ACCESSIBILITY
}
if (mWidgetListener)
mWidgetListener->SizeModeChanged(mSizeState);
}
void
nsWindow::ThemeChanged()
{
NotifyThemeChanged();
if (!mGdkWindow || MOZ_UNLIKELY(mIsDestroyed))
return;
// Dispatch theme change notification to all child windows
GList *children =
gdk_window_peek_children(mGdkWindow);
while (children) {
GdkWindow *gdkWin = GDK_WINDOW(children->data);
nsWindow *win = (nsWindow*) g_object_get_data(G_OBJECT(gdkWin),
"nsWindow");
if (win && win != this) { // guard against infinite recursion
nsRefPtr<nsWindow> kungFuDeathGrip = win;
win->ThemeChanged();
}
children = children->next;
}
}
void
nsWindow::DispatchDragEvent(uint32_t aMsg, const nsIntPoint& aRefPoint,
guint aTime)
{
nsDragEvent event(true, aMsg, this);
if (aMsg == NS_DRAGDROP_OVER) {
InitDragEvent(event);
}
event.refPoint = aRefPoint;
event.time = aTime;
nsEventStatus status;
DispatchEvent(&event, status);
}
void
nsWindow::OnDragDataReceivedEvent(GtkWidget *aWidget,
GdkDragContext *aDragContext,
gint aX,
gint aY,
GtkSelectionData *aSelectionData,
guint aInfo,
guint aTime,
gpointer aData)
{
LOGDRAG(("nsWindow::OnDragDataReceived(%p)\n", (void*)this));
nsDragService::GetInstance()->
TargetDataReceived(aWidget, aDragContext, aX, aY,
aSelectionData, aInfo, aTime);
}
static void
GetBrandName(nsXPIDLString& brandName)
{
nsCOMPtr<nsIStringBundleService> bundleService =
do_GetService(NS_STRINGBUNDLE_CONTRACTID);
nsCOMPtr<nsIStringBundle> bundle;
if (bundleService)
bundleService->CreateBundle(
"chrome://branding/locale/brand.properties",
getter_AddRefs(bundle));
if (bundle)
bundle->GetStringFromName(
NS_LITERAL_STRING("brandShortName").get(),
getter_Copies(brandName));
if (brandName.IsEmpty())
brandName.Assign(NS_LITERAL_STRING("Mozilla"));
}
static GdkWindow *
CreateGdkWindow(GdkWindow *parent, GtkWidget *widget)
{
GdkWindowAttr attributes;
gint attributes_mask = GDK_WA_VISUAL;
attributes.event_mask = kEvents;
attributes.width = 1;
attributes.height = 1;
attributes.wclass = GDK_INPUT_OUTPUT;
attributes.visual = gtk_widget_get_visual(widget);
attributes.window_type = GDK_WINDOW_CHILD;
#if defined(MOZ_WIDGET_GTK2)
attributes_mask |= GDK_WA_COLORMAP;
attributes.colormap = gtk_widget_get_colormap(widget);
#endif
GdkWindow *window = gdk_window_new(parent, &attributes, attributes_mask);
gdk_window_set_user_data(window, widget);
// GTK3 TODO?
#if defined(MOZ_WIDGET_GTK2)
/* set the default pixmap to None so that you don't end up with the
gtk default which is BlackPixel. */
gdk_window_set_back_pixmap(window, NULL, FALSE);
#endif
return window;
}
nsresult
nsWindow::Create(nsIWidget *aParent,
nsNativeWidget aNativeParent,
const nsIntRect &aRect,
nsDeviceContext *aContext,
nsWidgetInitData *aInitData)
{
// only set the base parent if we're going to be a dialog or a
// toplevel
nsIWidget *baseParent = aInitData &&
(aInitData->mWindowType == eWindowType_dialog ||
aInitData->mWindowType == eWindowType_toplevel ||
aInitData->mWindowType == eWindowType_invisible) ?
nullptr : aParent;
#ifdef ACCESSIBILITY
// Send a DBus message to check whether a11y is enabled
a11y::PreInit();
#endif
// Ensure that the toolkit is created.
nsGTKToolkit::GetToolkit();
// initialize all the common bits of this class
BaseCreate(baseParent, aRect, aContext, aInitData);
// Do we need to listen for resizes?
bool listenForResizes = false;;
if (aNativeParent || (aInitData && aInitData->mListenForResizes))
listenForResizes = true;
// and do our common creation
CommonCreate(aParent, listenForResizes);
// save our bounds
mBounds = aRect;
ConstrainSize(&mBounds.width, &mBounds.height);
// figure out our parent window
GtkWidget *parentMozContainer = nullptr;
GtkContainer *parentGtkContainer = nullptr;
GdkWindow *parentGdkWindow = nullptr;
GtkWindow *topLevelParent = nullptr;
nsWindow *parentnsWindow = nullptr;
GtkWidget *eventWidget = nullptr;
if (aParent) {
parentnsWindow = static_cast<nsWindow*>(aParent);
parentGdkWindow = parentnsWindow->mGdkWindow;
} else if (aNativeParent && GDK_IS_WINDOW(aNativeParent)) {
parentGdkWindow = GDK_WINDOW(aNativeParent);
parentnsWindow = get_window_for_gdk_window(parentGdkWindow);
if (!parentnsWindow)
return NS_ERROR_FAILURE;
} else if (aNativeParent && GTK_IS_CONTAINER(aNativeParent)) {
parentGtkContainer = GTK_CONTAINER(aNativeParent);
}
if (parentGdkWindow) {
// get the widget for the window - it should be a moz container
parentMozContainer = parentnsWindow->GetMozContainerWidget();
if (!parentMozContainer)
return NS_ERROR_FAILURE;
// get the toplevel window just in case someone needs to use it
// for setting transients or whatever.
topLevelParent =
GTK_WINDOW(gtk_widget_get_toplevel(parentMozContainer));
}
// ok, create our windows
switch (mWindowType) {
case eWindowType_dialog:
case eWindowType_popup:
case eWindowType_toplevel:
case eWindowType_invisible: {
mIsTopLevel = true;
// We only move a general managed toplevel window if someone has
// actually placed the window somewhere. If no placement has taken
// place, we just let the window manager Do The Right Thing.
//
// Indicate that if we're shown, we at least need to have our size set.
// If we get explicitly moved, the position will also be set.
mNeedsResize = true;
if (mWindowType == eWindowType_dialog) {
mShell = gtk_window_new(GTK_WINDOW_TOPLEVEL);
SetDefaultIcon();
gtk_window_set_wmclass(GTK_WINDOW(mShell), "Dialog",
gdk_get_program_class());
gtk_window_set_type_hint(GTK_WINDOW(mShell),
GDK_WINDOW_TYPE_HINT_DIALOG);
gtk_window_set_transient_for(GTK_WINDOW(mShell),
topLevelParent);
}
else if (mWindowType == eWindowType_popup) {
// With popup windows, we want to control their position, so don't
// wait for the window manager to place them (which wouldn't
// happen with override-redirect windows anyway).
mNeedsMove = true;
// Popups that are not noautohide are only temporary. The are used
// for menus and the like and disappear when another window is used.
// For most popups, use the standard GtkWindowType GTK_WINDOW_POPUP,
// which will use a Window with the override-redirect attribute
// (for temporary windows).
// For long-lived windows, their stacking order is managed by the
// window manager, as indicated by GTK_WINDOW_TOPLEVEL ...
GtkWindowType type = aInitData->mNoAutoHide ?
GTK_WINDOW_TOPLEVEL : GTK_WINDOW_POPUP;
mShell = gtk_window_new(type);
gtk_window_set_wmclass(GTK_WINDOW(mShell), "Popup",
gdk_get_program_class());
if (aInitData->mSupportTranslucency) {
// We need to select an ARGB visual here instead of in
// SetTransparencyMode() because it has to be done before the
// widget is realized. An ARGB visual is only useful if we
// are on a compositing window manager.
GdkScreen *screen = gtk_widget_get_screen(mShell);
if (gdk_screen_is_composited(screen)) {
#if defined(MOZ_WIDGET_GTK2)
GdkColormap *colormap =
gdk_screen_get_rgba_colormap(screen);
gtk_widget_set_colormap(mShell, colormap);
#else
GdkVisual *visual = gdk_screen_get_rgba_visual(screen);
gtk_widget_set_visual(mShell, visual);
#endif
}
}
if (aInitData->mNoAutoHide) {
// ... but the window manager does not decorate this window,
// nor provide a separate taskbar icon.
if (mBorderStyle == eBorderStyle_default) {
gtk_window_set_decorated(GTK_WINDOW(mShell), FALSE);
}
else {
bool decorate = mBorderStyle & eBorderStyle_title;
gtk_window_set_decorated(GTK_WINDOW(mShell), decorate);
if (decorate) {
gtk_window_set_deletable(GTK_WINDOW(mShell), mBorderStyle & eBorderStyle_close);
}
}
gtk_window_set_skip_taskbar_hint(GTK_WINDOW(mShell), TRUE);
// Element focus is managed by the parent window so the
// WM_HINTS input field is set to False to tell the window
// manager not to set input focus to this window ...
gtk_window_set_accept_focus(GTK_WINDOW(mShell), FALSE);
#ifdef MOZ_X11
// ... but when the window manager offers focus through
// WM_TAKE_FOCUS, focus is requested on the parent window.
gtk_widget_realize(mShell);
gdk_window_add_filter(gtk_widget_get_window(mShell),
popup_take_focus_filter, NULL);
#endif
}
GdkWindowTypeHint gtkTypeHint;
if (aInitData->mIsDragPopup) {
gtkTypeHint = GDK_WINDOW_TYPE_HINT_DND;
}
else {
switch (aInitData->mPopupHint) {
case ePopupTypeMenu:
gtkTypeHint = GDK_WINDOW_TYPE_HINT_POPUP_MENU;
break;
case ePopupTypeTooltip:
gtkTypeHint = GDK_WINDOW_TYPE_HINT_TOOLTIP;
break;
default:
gtkTypeHint = GDK_WINDOW_TYPE_HINT_UTILITY;
break;
}
}
gtk_window_set_type_hint(GTK_WINDOW(mShell), gtkTypeHint);
if (topLevelParent) {
gtk_window_set_transient_for(GTK_WINDOW(mShell),
topLevelParent);
}
}
else { // must be eWindowType_toplevel
mShell = gtk_window_new(GTK_WINDOW_TOPLEVEL);
SetDefaultIcon();
gtk_window_set_wmclass(GTK_WINDOW(mShell), "Toplevel",
gdk_get_program_class());
// each toplevel window gets its own window group
GtkWindowGroup *group = gtk_window_group_new();
gtk_window_group_add_window(group, GTK_WINDOW(mShell));
g_object_unref(group);
}
// Prevent GtkWindow from painting a background to flicker.
gtk_widget_set_app_paintable(mShell, TRUE);
// Create a container to hold child windows and child GtkWidgets.
GtkWidget *container = moz_container_new();
mContainer = MOZ_CONTAINER(container);
// Use mShell's window for drawing and events.
gtk_widget_set_has_window(container, FALSE);
eventWidget = mShell;
gtk_widget_add_events(eventWidget, kEvents);
gtk_container_add(GTK_CONTAINER(mShell), container);
gtk_widget_realize(container);
// make sure this is the focus widget in the container
gtk_widget_show(container);
gtk_widget_grab_focus(container);
// the drawing window
mGdkWindow = gtk_widget_get_window(mShell);
if (mWindowType == eWindowType_popup) {
// gdk does not automatically set the cursor for "temporary"
// windows, which are what gtk uses for popups.
mCursor = eCursor_wait; // force SetCursor to actually set the
// cursor, even though our internal state
// indicates that we already have the
// standard cursor.
SetCursor(eCursor_standard);
if (aInitData->mNoAutoHide) {
gint wmd = ConvertBorderStyles(mBorderStyle);
if (wmd != -1)
gdk_window_set_decorations(gtk_widget_get_window(mShell), (GdkWMDecoration) wmd);
}
}
}
break;
case eWindowType_plugin:
case eWindowType_child: {
if (parentMozContainer) {
mGdkWindow = CreateGdkWindow(parentGdkWindow, parentMozContainer);
mHasMappedToplevel = parentnsWindow->mHasMappedToplevel;
}
else if (parentGtkContainer) {
// This MozContainer has its own window for drawing and receives
// events because there is no mShell widget (corresponding to this
// nsWindow).
GtkWidget *container = moz_container_new();
mContainer = MOZ_CONTAINER(container);
eventWidget = container;
gtk_widget_add_events(eventWidget, kEvents);
gtk_container_add(parentGtkContainer, container);
gtk_widget_realize(container);
mGdkWindow = gtk_widget_get_window(container);
}
else {
NS_WARNING("Warning: tried to create a new child widget with no parent!");
return NS_ERROR_FAILURE;
}
}
break;
default:
break;
}
// label the drawing window with this object so we can find our way home
g_object_set_data(G_OBJECT(mGdkWindow), "nsWindow", this);
if (mContainer)
g_object_set_data(G_OBJECT(mContainer), "nsWindow", this);
if (mShell)
g_object_set_data(G_OBJECT(mShell), "nsWindow", this);
// attach listeners for events
if (mShell) {
g_signal_connect(mShell, "configure_event",
G_CALLBACK(configure_event_cb), NULL);
g_signal_connect(mShell, "delete_event",
G_CALLBACK(delete_event_cb), NULL);
g_signal_connect(mShell, "window_state_event",
G_CALLBACK(window_state_event_cb), NULL);
GtkSettings* default_settings = gtk_settings_get_default();
g_signal_connect_after(default_settings,
"notify::gtk-theme-name",
G_CALLBACK(theme_changed_cb), this);
g_signal_connect_after(default_settings,
"notify::gtk-font-name",
G_CALLBACK(theme_changed_cb), this);
#ifdef MOZ_PLATFORM_MAEMO
if (mWindowType == eWindowType_toplevel) {
GdkWindow *gdkwin = gtk_widget_get_window(mShell);
// Tell the Hildon desktop that we support being rotated
gulong portrait_set = 1;
GdkAtom support = gdk_atom_intern("_HILDON_PORTRAIT_MODE_SUPPORT", FALSE);
gdk_property_change(gdkwin, support, gdk_x11_xatom_to_atom(XA_CARDINAL),
32, GDK_PROP_MODE_REPLACE,
(const guchar *) &portrait_set, 1);
// Tell maemo-status-volume daemon to ungrab keys
gulong volume_set = 1;
GdkAtom keys = gdk_atom_intern("_HILDON_ZOOM_KEY_ATOM", FALSE);
gdk_property_change(gdkwin, keys, gdk_x11_xatom_to_atom(XA_INTEGER),
32, GDK_PROP_MODE_REPLACE, (const guchar *) &volume_set, 1);
}
#endif
}
if (mContainer) {
// Widget signals
g_signal_connect(mContainer, "unrealize",
G_CALLBACK(container_unrealize_cb), NULL);
g_signal_connect_after(mContainer, "size_allocate",
G_CALLBACK(size_allocate_cb), NULL);
g_signal_connect(mContainer, "hierarchy-changed",
G_CALLBACK(hierarchy_changed_cb), NULL);
// Initialize mHasMappedToplevel.
hierarchy_changed_cb(GTK_WIDGET(mContainer), NULL);
// Expose, focus, key, and drag events are sent even to GTK_NO_WINDOW
// widgets.
#if defined(MOZ_WIDGET_GTK2)
g_signal_connect(mContainer, "expose_event",
G_CALLBACK(expose_event_cb), NULL);
#else
g_signal_connect(G_OBJECT(mContainer), "draw",
G_CALLBACK(expose_event_cb), NULL);
#endif
g_signal_connect(mContainer, "focus_in_event",
G_CALLBACK(focus_in_event_cb), NULL);
g_signal_connect(mContainer, "focus_out_event",
G_CALLBACK(focus_out_event_cb), NULL);
g_signal_connect(mContainer, "key_press_event",
G_CALLBACK(key_press_event_cb), NULL);
g_signal_connect(mContainer, "key_release_event",
G_CALLBACK(key_release_event_cb), NULL);
gtk_drag_dest_set((GtkWidget *)mContainer,
(GtkDestDefaults)0,
NULL,
0,
(GdkDragAction)0);
g_signal_connect(mContainer, "drag_motion",
G_CALLBACK(drag_motion_event_cb), NULL);
g_signal_connect(mContainer, "drag_leave",
G_CALLBACK(drag_leave_event_cb), NULL);
g_signal_connect(mContainer, "drag_drop",
G_CALLBACK(drag_drop_event_cb), NULL);
g_signal_connect(mContainer, "drag_data_received",
G_CALLBACK(drag_data_received_event_cb), NULL);
GtkWidget *widgets[] = { GTK_WIDGET(mContainer), mShell };
for (size_t i = 0; i < ArrayLength(widgets) && widgets[i]; ++i) {
// Visibility events are sent to the owning widget of the relevant
// window but do not propagate to parent widgets so connect on
// mShell (if it exists) as well as mContainer.
g_signal_connect(widgets[i], "visibility-notify-event",
G_CALLBACK(visibility_notify_event_cb), NULL);
// Similarly double buffering is controlled by the window's owning
// widget. Disable double buffering for painting directly to the
// X Window.
gtk_widget_set_double_buffered(widgets[i], FALSE);
}
// We create input contexts for all containers, except for
// toplevel popup windows
if (mWindowType != eWindowType_popup) {
mIMModule = new nsGtkIMModule(this);
}
} else if (!mIMModule) {
nsWindow *container = GetContainerWindow();
if (container) {
mIMModule = container->mIMModule;
}
}
if (eventWidget) {
// Don't let GTK mess with the shapes of our GdkWindows
GTK_PRIVATE_SET_FLAG(eventWidget, GTK_HAS_SHAPE_MASK);
// These events are sent to the owning widget of the relevant window
// and propagate up to the first widget that handles the events, so we
// need only connect on mShell, if it exists, to catch events on its
// window and windows of mContainer.
g_signal_connect(eventWidget, "enter-notify-event",
G_CALLBACK(enter_notify_event_cb), NULL);
g_signal_connect(eventWidget, "leave-notify-event",
G_CALLBACK(leave_notify_event_cb), NULL);
g_signal_connect(eventWidget, "motion-notify-event",
G_CALLBACK(motion_notify_event_cb), NULL);
g_signal_connect(eventWidget, "button-press-event",
G_CALLBACK(button_press_event_cb), NULL);
g_signal_connect(eventWidget, "button-release-event",
G_CALLBACK(button_release_event_cb), NULL);
g_signal_connect(eventWidget, "scroll-event",
G_CALLBACK(scroll_event_cb), NULL);
}
LOG(("nsWindow [%p]\n", (void *)this));
if (mShell) {
LOG(("\tmShell %p mContainer %p mGdkWindow %p 0x%lx\n",
mShell, mContainer, mGdkWindow,
gdk_x11_window_get_xid(mGdkWindow)));
} else if (mContainer) {
LOG(("\tmContainer %p mGdkWindow %p\n", mContainer, mGdkWindow));
}
else if (mGdkWindow) {
LOG(("\tmGdkWindow %p parent %p\n",
mGdkWindow, gdk_window_get_parent(mGdkWindow)));
}
// resize so that everything is set to the right dimensions
if (!mIsTopLevel)
Resize(mBounds.x, mBounds.y, mBounds.width, mBounds.height, false);
return NS_OK;
}
NS_IMETHODIMP
nsWindow::SetWindowClass(const nsAString &xulWinType)
{
if (!mShell)
return NS_ERROR_FAILURE;
const char *res_class = gdk_get_program_class();
if (!res_class)
return NS_ERROR_FAILURE;
char *res_name = ToNewCString(xulWinType);
if (!res_name)
return NS_ERROR_OUT_OF_MEMORY;
const char *role = NULL;
// Parse res_name into a name and role. Characters other than
// [A-Za-z0-9_-] are converted to '_'. Anything after the first
// colon is assigned to role; if there's no colon, assign the
// whole thing to both role and res_name.
for (char *c = res_name; *c; c++) {
if (':' == *c) {
*c = 0;
role = c + 1;
}
else if (!isascii(*c) || (!isalnum(*c) && ('_' != *c) && ('-' != *c)))
*c = '_';
}
res_name[0] = toupper(res_name[0]);
if (!role) role = res_name;
GdkWindow *shellWindow = gtk_widget_get_window(GTK_WIDGET(mShell));
gdk_window_set_role(shellWindow, role);
#ifdef MOZ_X11
XClassHint *class_hint = XAllocClassHint();
if (!class_hint) {
nsMemory::Free(res_name);
return NS_ERROR_OUT_OF_MEMORY;
}
class_hint->res_name = res_name;
class_hint->res_class = const_cast<char*>(res_class);
// Can't use gtk_window_set_wmclass() for this; it prints
// a warning & refuses to make the change.
XSetClassHint(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()),
gdk_x11_window_get_xid(shellWindow),
class_hint);
XFree(class_hint);
#endif /* MOZ_X11 */
nsMemory::Free(res_name);
return NS_OK;
}
void
nsWindow::NativeResize(int32_t aWidth, int32_t aHeight, bool aRepaint)
{
LOG(("nsWindow::NativeResize [%p] %d %d\n", (void *)this,
aWidth, aHeight));
// clear our resize flag
mNeedsResize = false;
if (mIsTopLevel) {
gtk_window_resize(GTK_WINDOW(mShell), aWidth, aHeight);
}
else if (mContainer) {
GtkWidget *widget = GTK_WIDGET(mContainer);
GtkAllocation allocation, prev_allocation;
gtk_widget_get_allocation(widget, &prev_allocation);
allocation.x = prev_allocation.x;
allocation.y = prev_allocation.y;
allocation.width = aWidth;
allocation.height = aHeight;
gtk_widget_size_allocate(widget, &allocation);
}
else if (mGdkWindow) {
gdk_window_resize(mGdkWindow, aWidth, aHeight);
}
}
void
nsWindow::NativeResize(int32_t aX, int32_t aY,
int32_t aWidth, int32_t aHeight,
bool aRepaint)
{
mNeedsResize = false;
mNeedsMove = false;
LOG(("nsWindow::NativeResize [%p] %d %d %d %d\n", (void *)this,
aX, aY, aWidth, aHeight));
if (mIsTopLevel) {
// aX and aY give the position of the window manager frame top-left.
gtk_window_move(GTK_WINDOW(mShell), aX, aY);
// This sets the client window size.
gtk_window_resize(GTK_WINDOW(mShell), aWidth, aHeight);
}
else if (mContainer) {
GtkAllocation allocation;
allocation.x = aX;
allocation.y = aY;
allocation.width = aWidth;
allocation.height = aHeight;
gtk_widget_size_allocate(GTK_WIDGET(mContainer), &allocation);
}
else if (mGdkWindow) {
gdk_window_move_resize(mGdkWindow, aX, aY, aWidth, aHeight);
}
}
void
nsWindow::NativeShow(bool aAction)
{
if (aAction) {
// unset our flag now that our window has been shown
mNeedsShow = false;
if (mIsTopLevel) {
// Set up usertime/startupID metadata for the created window.
if (mWindowType != eWindowType_invisible) {
SetUserTimeAndStartupIDForActivatedWindow(mShell);
}
gtk_widget_show(mShell);
}
else if (mContainer) {
gtk_widget_show(GTK_WIDGET(mContainer));
}
else if (mGdkWindow) {
gdk_window_show_unraised(mGdkWindow);
}
}
else {
if (mIsTopLevel) {
gtk_widget_hide(GTK_WIDGET(mShell));
ClearTransparencyBitmap(); // Release some resources
}
else if (mContainer) {
gtk_widget_hide(GTK_WIDGET(mContainer));
}
else if (mGdkWindow) {
gdk_window_hide(mGdkWindow);
}
}
}
void
nsWindow::SetHasMappedToplevel(bool aState)
{
// Even when aState == mHasMappedToplevel (as when this method is called
// from Show()), child windows need to have their state checked, so don't
// return early.
bool oldState = mHasMappedToplevel;
mHasMappedToplevel = aState;
// mHasMappedToplevel is not updated for children of windows that are
// hidden; GDK knows not to send expose events for these windows. The
// state is recorded on the hidden window itself, but, for child trees of
// hidden windows, their state essentially becomes disconnected from their
// hidden parent. When the hidden parent gets shown, the child trees are
// reconnected, and the state of the window being shown can be easily
// propagated.
if (!mIsShown || !mGdkWindow)
return;
if (aState && !oldState && !mIsFullyObscured) {
// GDK_EXPOSE events have been ignored but the window is now visible,
// so make sure GDK doesn't think that the window has already been
// painted.
gdk_window_invalidate_rect(mGdkWindow, NULL, FALSE);
// Check that a grab didn't fail due to the window not being
// viewable.
EnsureGrabs();
}
for (GList *children = gdk_window_peek_children(mGdkWindow);
children;
children = children->next) {
GdkWindow *gdkWin = GDK_WINDOW(children->data);
nsWindow *child = get_window_for_gdk_window(gdkWin);
if (child && child->mHasMappedToplevel != aState) {
child->SetHasMappedToplevel(aState);
}
}
}
nsIntSize
nsWindow::GetSafeWindowSize(nsIntSize aSize)
{
// The X protocol uses CARD32 for window sizes, but the server (1.11.3)
// reads it as CARD16. Sizes of pixmaps, used for drawing, are (unsigned)
// CARD16 in the protocol, but the server's ProcCreatePixmap returns
// BadAlloc if dimensions cannot be represented by signed shorts.
nsIntSize result = aSize;
const int32_t kInt16Max = 32767;
if (result.width > kInt16Max) {
result.width = kInt16Max;
}
if (result.height > kInt16Max) {
result.height = kInt16Max;
}
return result;
}
void
nsWindow::EnsureGrabs(void)
{
if (mRetryPointerGrab)
GrabPointer(sRetryGrabTime);
}
void
nsWindow::CleanLayerManagerRecursive(void) {
if (mLayerManager) {
mLayerManager->Destroy();
mLayerManager = nullptr;
}
DestroyCompositor();
GList* children = gdk_window_peek_children(mGdkWindow);
for (GList* list = children; list; list = list->next) {
nsWindow* window = get_window_for_gdk_window(GDK_WINDOW(list->data));
if (window) {
window->CleanLayerManagerRecursive();
}
}
}
void
nsWindow::SetTransparencyMode(nsTransparencyMode aMode)
{
if (!mShell) {
// Pass the request to the toplevel window
GtkWidget *topWidget = GetToplevelWidget();
if (!topWidget)
return;
nsWindow *topWindow = get_window_for_gtk_widget(topWidget);
if (!topWindow)
return;
topWindow->SetTransparencyMode(aMode);
return;
}
bool isTransparent = aMode == eTransparencyTransparent;
if (mIsTransparent == isTransparent)
return;
if (!isTransparent) {
ClearTransparencyBitmap();
} // else the new default alpha values are "all 1", so we don't
// need to change anything yet
mIsTransparent = isTransparent;
// Need to clean our LayerManager up while still alive because
// we don't want to use layers acceleration on shaped windows
CleanLayerManagerRecursive();
}
nsTransparencyMode
nsWindow::GetTransparencyMode()
{
if (!mShell) {
// Pass the request to the toplevel window
GtkWidget *topWidget = GetToplevelWidget();
if (!topWidget) {
return eTransparencyOpaque;
}
nsWindow *topWindow = get_window_for_gtk_widget(topWidget);
if (!topWindow) {
return eTransparencyOpaque;
}
return topWindow->GetTransparencyMode();
}
return mIsTransparent ? eTransparencyTransparent : eTransparencyOpaque;
}
nsresult
nsWindow::ConfigureChildren(const nsTArray<Configuration>& aConfigurations)
{
for (uint32_t i = 0; i < aConfigurations.Length(); ++i) {
const Configuration& configuration = aConfigurations[i];
nsWindow* w = static_cast<nsWindow*>(configuration.mChild);
NS_ASSERTION(w->GetParent() == this,
"Configured widget is not a child");
w->SetWindowClipRegion(configuration.mClipRegion, true);
if (w->mBounds.Size() != configuration.mBounds.Size()) {
w->Resize(configuration.mBounds.x, configuration.mBounds.y,
configuration.mBounds.width, configuration.mBounds.height,
true);
} else if (w->mBounds.TopLeft() != configuration.mBounds.TopLeft()) {
w->Move(configuration.mBounds.x, configuration.mBounds.y);
}
w->SetWindowClipRegion(configuration.mClipRegion, false);
}
return NS_OK;
}
static pixman_box32
ToPixmanBox(const nsIntRect& aRect)
{
pixman_box32_t result;
result.x1 = aRect.x;
result.y1 = aRect.y;
result.x2 = aRect.XMost();
result.y2 = aRect.YMost();
return result;
}
static nsIntRect
ToIntRect(const pixman_box32& aBox)
{
nsIntRect result;
result.x = aBox.x1;
result.y = aBox.y1;
result.width = aBox.x2 - aBox.x1;
result.height = aBox.y2 - aBox.y1;
return result;
}
static void
InitRegion(pixman_region32* aRegion,
const nsTArray<nsIntRect>& aRects)
{
nsAutoTArray<pixman_box32,10> rects;
rects.SetCapacity(aRects.Length());
for (uint32_t i = 0; i < aRects.Length (); ++i) {
if (!aRects[i].IsEmpty()) {
rects.AppendElement(ToPixmanBox(aRects[i]));
}
}
pixman_region32_init_rects(aRegion,
rects.Elements(), rects.Length());
}
static void
GetIntRects(pixman_region32& aRegion, nsTArray<nsIntRect>* aRects)
{
int nRects;
pixman_box32* boxes = pixman_region32_rectangles(&aRegion, &nRects);
aRects->SetCapacity(aRects->Length() + nRects);
for (int i = 0; i < nRects; ++i) {
aRects->AppendElement(ToIntRect(boxes[i]));
}
}
void
nsWindow::SetWindowClipRegion(const nsTArray<nsIntRect>& aRects,
bool aIntersectWithExisting)
{
const nsTArray<nsIntRect>* newRects = &aRects;
nsAutoTArray<nsIntRect,1> intersectRects;
if (aIntersectWithExisting) {
nsAutoTArray<nsIntRect,1> existingRects;
GetWindowClipRegion(&existingRects);
nsAutoRef<pixman_region32> existingRegion;
InitRegion(&existingRegion, existingRects);
nsAutoRef<pixman_region32> newRegion;
InitRegion(&newRegion, aRects);
nsAutoRef<pixman_region32> intersectRegion;
pixman_region32_init(&intersectRegion);
pixman_region32_intersect(&intersectRegion,
&newRegion, &existingRegion);
// If mClipRects is null we haven't set a clip rect yet, so we
// need to set the clip even if it is equal.
if (mClipRects &&
pixman_region32_equal(&intersectRegion, &existingRegion)) {
return;
}
if (!pixman_region32_equal(&intersectRegion, &newRegion)) {
GetIntRects(intersectRegion, &intersectRects);
newRects = &intersectRects;
}
}
if (!StoreWindowClipRegion(*newRects))
return;
if (!mGdkWindow)
return;
#if defined(MOZ_WIDGET_GTK2)
GdkRegion *region = gdk_region_new(); // aborts on OOM
for (uint32_t i = 0; i < newRects->Length(); ++i) {
const nsIntRect& r = newRects->ElementAt(i);
GdkRectangle rect = { r.x, r.y, r.width, r.height };
gdk_region_union_with_rect(region, &rect);
}
gdk_window_shape_combine_region(mGdkWindow, region, 0, 0);
gdk_region_destroy(region);
#else
cairo_region_t *region = cairo_region_create();
for (uint32_t i = 0; i < newRects->Length(); ++i) {
const nsIntRect& r = newRects->ElementAt(i);
cairo_rectangle_int_t rect = { r.x, r.y, r.width, r.height };
cairo_region_union_rectangle(region, &rect);
}
gdk_window_shape_combine_region(mGdkWindow, region, 0, 0);
cairo_region_destroy(region);
#endif
return;
}
void
nsWindow::ResizeTransparencyBitmap()
{
if (!mTransparencyBitmap)
return;
if (mBounds.width == mTransparencyBitmapWidth &&
mBounds.height == mTransparencyBitmapHeight)
return;
int32_t newRowBytes = GetBitmapStride(mBounds.width);
int32_t newSize = newRowBytes * mBounds.height;
gchar* newBits = new gchar[newSize];
// fill new mask with "transparent", first
memset(newBits, 0, newSize);
// Now copy the intersection of the old and new areas into the new mask
int32_t copyWidth = std::min(mBounds.width, mTransparencyBitmapWidth);
int32_t copyHeight = std::min(mBounds.height, mTransparencyBitmapHeight);
int32_t oldRowBytes = GetBitmapStride(mTransparencyBitmapWidth);
int32_t copyBytes = GetBitmapStride(copyWidth);
int32_t i;
gchar* fromPtr = mTransparencyBitmap;
gchar* toPtr = newBits;
for (i = 0; i < copyHeight; i++) {
memcpy(toPtr, fromPtr, copyBytes);
fromPtr += oldRowBytes;
toPtr += newRowBytes;
}
delete[] mTransparencyBitmap;
mTransparencyBitmap = newBits;
mTransparencyBitmapWidth = mBounds.width;
mTransparencyBitmapHeight = mBounds.height;
}
static bool
ChangedMaskBits(gchar* aMaskBits, int32_t aMaskWidth, int32_t aMaskHeight,
const nsIntRect& aRect, uint8_t* aAlphas, int32_t aStride)
{
int32_t x, y, xMax = aRect.XMost(), yMax = aRect.YMost();
int32_t maskBytesPerRow = GetBitmapStride(aMaskWidth);
for (y = aRect.y; y < yMax; y++) {
gchar* maskBytes = aMaskBits + y*maskBytesPerRow;
uint8_t* alphas = aAlphas;
for (x = aRect.x; x < xMax; x++) {
bool newBit = *alphas > 0x7f;
alphas++;
gchar maskByte = maskBytes[x >> 3];
bool maskBit = (maskByte & (1 << (x & 7))) != 0;
if (maskBit != newBit) {
return true;
}
}
aAlphas += aStride;
}
return false;
}
static
void UpdateMaskBits(gchar* aMaskBits, int32_t aMaskWidth, int32_t aMaskHeight,
const nsIntRect& aRect, uint8_t* aAlphas, int32_t aStride)
{
int32_t x, y, xMax = aRect.XMost(), yMax = aRect.YMost();
int32_t maskBytesPerRow = GetBitmapStride(aMaskWidth);
for (y = aRect.y; y < yMax; y++) {
gchar* maskBytes = aMaskBits + y*maskBytesPerRow;
uint8_t* alphas = aAlphas;
for (x = aRect.x; x < xMax; x++) {
bool newBit = *alphas > 0x7f;
alphas++;
gchar mask = 1 << (x & 7);
gchar maskByte = maskBytes[x >> 3];
// Note: '-newBit' turns 0 into 00...00 and 1 into 11...11
maskBytes[x >> 3] = (maskByte & ~mask) | (-newBit & mask);
}
aAlphas += aStride;
}
}
void
nsWindow::ApplyTransparencyBitmap()
{
#ifdef MOZ_X11
// We use X11 calls where possible, because GDK handles expose events
// for shaped windows in a way that's incompatible with us (Bug 635903).
// It doesn't occur when the shapes are set through X.
GdkWindow *shellWindow = gtk_widget_get_window(mShell);
Display* xDisplay = GDK_WINDOW_XDISPLAY(shellWindow);
Window xDrawable = GDK_WINDOW_XID(shellWindow);
Pixmap maskPixmap = XCreateBitmapFromData(xDisplay,
xDrawable,
mTransparencyBitmap,
mTransparencyBitmapWidth,
mTransparencyBitmapHeight);
XShapeCombineMask(xDisplay, xDrawable,
ShapeBounding, 0, 0,
maskPixmap, ShapeSet);
XFreePixmap(xDisplay, maskPixmap);
#else
#if defined(MOZ_WIDGET_GTK2)
gtk_widget_reset_shapes(mShell);
GdkBitmap* maskBitmap = gdk_bitmap_create_from_data(gtk_widget_get_window(mShell),
mTransparencyBitmap,
mTransparencyBitmapWidth, mTransparencyBitmapHeight);
if (!maskBitmap)
return;
gtk_widget_shape_combine_mask(mShell, maskBitmap, 0, 0);
g_object_unref(maskBitmap);
#else
cairo_surface_t *maskBitmap;
maskBitmap = cairo_image_surface_create_for_data((unsigned char*)mTransparencyBitmap,
CAIRO_FORMAT_A1,
mTransparencyBitmapWidth,
mTransparencyBitmapHeight,
GetBitmapStride(mTransparencyBitmapWidth));
if (!maskBitmap)
return;
cairo_region_t * maskRegion = gdk_cairo_region_create_from_surface(maskBitmap);
gtk_widget_shape_combine_region(mShell, maskRegion);
cairo_region_destroy(maskRegion);
cairo_surface_destroy(maskBitmap);
#endif // MOZ_WIDGET_GTK2
#endif // MOZ_X11
}
void
nsWindow::ClearTransparencyBitmap()
{
if (!mTransparencyBitmap)
return;
delete[] mTransparencyBitmap;
mTransparencyBitmap = nullptr;
mTransparencyBitmapWidth = 0;
mTransparencyBitmapHeight = 0;
if (!mShell)
return;
#ifdef MOZ_X11
GdkWindow *window = gtk_widget_get_window(mShell);
if (!window)
return;
Display* xDisplay = GDK_WINDOW_XDISPLAY(window);
Window xWindow = gdk_x11_window_get_xid(window);
XShapeCombineMask(xDisplay, xWindow, ShapeBounding, 0, 0, None, ShapeSet);
#endif
}
nsresult
nsWindow::UpdateTranslucentWindowAlphaInternal(const nsIntRect& aRect,
uint8_t* aAlphas, int32_t aStride)
{
if (!mShell) {
// Pass the request to the toplevel window
GtkWidget *topWidget = GetToplevelWidget();
if (!topWidget)
return NS_ERROR_FAILURE;
nsWindow *topWindow = get_window_for_gtk_widget(topWidget);
if (!topWindow)
return NS_ERROR_FAILURE;
return topWindow->UpdateTranslucentWindowAlphaInternal(aRect, aAlphas, aStride);
}
NS_ASSERTION(mIsTransparent, "Window is not transparent");
if (mTransparencyBitmap == nullptr) {
int32_t size = GetBitmapStride(mBounds.width)*mBounds.height;
mTransparencyBitmap = new gchar[size];
memset(mTransparencyBitmap, 255, size);
mTransparencyBitmapWidth = mBounds.width;
mTransparencyBitmapHeight = mBounds.height;
} else {
ResizeTransparencyBitmap();
}
nsIntRect rect;
rect.IntersectRect(aRect, nsIntRect(0, 0, mBounds.width, mBounds.height));
if (!ChangedMaskBits(mTransparencyBitmap, mBounds.width, mBounds.height,
rect, aAlphas, aStride))
// skip the expensive stuff if the mask bits haven't changed; hopefully
// this is the common case
return NS_OK;
UpdateMaskBits(mTransparencyBitmap, mBounds.width, mBounds.height,
rect, aAlphas, aStride);
if (!mNeedsShow) {
ApplyTransparencyBitmap();
}
return NS_OK;
}
void
nsWindow::GrabPointer(guint32 aTime)
{
LOG(("GrabPointer time=0x%08x retry=%d\n",
(unsigned int)aTime, mRetryPointerGrab));
mRetryPointerGrab = false;
sRetryGrabTime = aTime;
// If the window isn't visible, just set the flag to retry the
// grab. When this window becomes visible, the grab will be
// retried.
if (!mHasMappedToplevel || mIsFullyObscured) {
LOG(("GrabPointer: window not visible\n"));
mRetryPointerGrab = true;
return;
}
if (!mGdkWindow)
return;
gint retval;
retval = gdk_pointer_grab(mGdkWindow, TRUE,
(GdkEventMask)(GDK_BUTTON_PRESS_MASK |
GDK_BUTTON_RELEASE_MASK |
GDK_ENTER_NOTIFY_MASK |
GDK_LEAVE_NOTIFY_MASK |
#ifdef MOZ_PLATFORM_MAEMO
GDK_POINTER_MOTION_HINT_MASK |
#endif
GDK_POINTER_MOTION_MASK),
(GdkWindow *)NULL, NULL, aTime);
if (retval == GDK_GRAB_NOT_VIEWABLE) {
LOG(("GrabPointer: window not viewable; will retry\n"));
mRetryPointerGrab = true;
} else if (retval != GDK_GRAB_SUCCESS) {
LOG(("GrabPointer: pointer grab failed: %i\n", retval));
// A failed grab indicates that another app has grabbed the pointer.
// Check for rollup now, because, without the grab, we likely won't
// get subsequent button press events.
CheckForRollup(0, 0, false, true);
}
}
void
nsWindow::ReleaseGrabs(void)
{
LOG(("ReleaseGrabs\n"));
mRetryPointerGrab = false;
gdk_pointer_ungrab(GDK_CURRENT_TIME);
}
GtkWidget *
nsWindow::GetToplevelWidget()
{
if (mShell) {
return mShell;
}
GtkWidget *widget = GetMozContainerWidget();
if (!widget)
return nullptr;
return gtk_widget_get_toplevel(widget);
}
GtkWidget *
nsWindow::GetMozContainerWidget()
{
if (!mGdkWindow)
return NULL;
if (mContainer)
return GTK_WIDGET(mContainer);
GtkWidget *owningWidget =
get_gtk_widget_for_gdk_window(mGdkWindow);
return owningWidget;
}
nsWindow *
nsWindow::GetContainerWindow()
{
GtkWidget *owningWidget = GetMozContainerWidget();
if (!owningWidget)
return nullptr;
nsWindow *window = get_window_for_gtk_widget(owningWidget);
NS_ASSERTION(window, "No nsWindow for container widget");
return window;
}
void
nsWindow::SetUrgencyHint(GtkWidget *top_window, bool state)
{
if (!top_window)
return;
gdk_window_set_urgency_hint(gtk_widget_get_window(top_window), state);
}
void *
nsWindow::SetupPluginPort(void)
{
if (!mGdkWindow)
return nullptr;
if (gdk_window_is_destroyed(mGdkWindow) == TRUE)
return nullptr;
Window window = gdk_x11_window_get_xid(mGdkWindow);
// we have to flush the X queue here so that any plugins that
// might be running on separate X connections will be able to use
// this window in case it was just created
#ifdef MOZ_X11
XWindowAttributes xattrs;
Display *display = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
XGetWindowAttributes(display, window, &xattrs);
XSelectInput (display, window,
xattrs.your_event_mask |
SubstructureNotifyMask);
gdk_window_add_filter(mGdkWindow, plugin_window_filter_func, this);
XSync(display, False);
#endif /* MOZ_X11 */
return (void *)window;
}
void
nsWindow::SetDefaultIcon(void)
{
SetIcon(NS_LITERAL_STRING("default"));
}
void
nsWindow::SetPluginType(PluginType aPluginType)
{
mPluginType = aPluginType;
}
#ifdef MOZ_X11
void
nsWindow::SetNonXEmbedPluginFocus()
{
if (gPluginFocusWindow == this || mPluginType!=PluginType_NONXEMBED) {
return;
}
if (gPluginFocusWindow) {
nsRefPtr<nsWindow> kungFuDeathGrip = gPluginFocusWindow;
gPluginFocusWindow->LoseNonXEmbedPluginFocus();
}
LOGFOCUS(("nsWindow::SetNonXEmbedPluginFocus\n"));
Window curFocusWindow;
int focusState;
GdkDisplay *gdkDisplay = gdk_window_get_display(mGdkWindow);
XGetInputFocus(gdk_x11_display_get_xdisplay(gdkDisplay),
&curFocusWindow,
&focusState);
LOGFOCUS(("\t curFocusWindow=%p\n", curFocusWindow));
GdkWindow* toplevel = gdk_window_get_toplevel(mGdkWindow);
#if defined(MOZ_WIDGET_GTK2)
GdkWindow *gdkfocuswin = gdk_window_lookup(curFocusWindow);
#else
GdkWindow *gdkfocuswin = gdk_x11_window_lookup_for_display(gdkDisplay,
curFocusWindow);
#endif
// lookup with the focus proxy window is supposed to get the
// same GdkWindow as toplevel. If the current focused window
// is not the focus proxy, we return without any change.
if (gdkfocuswin != toplevel) {
return;
}
// switch the focus from the focus proxy to the plugin window
mOldFocusWindow = curFocusWindow;
XRaiseWindow(GDK_WINDOW_XDISPLAY(mGdkWindow),
gdk_x11_window_get_xid(mGdkWindow));
gdk_error_trap_push();
XSetInputFocus(GDK_WINDOW_XDISPLAY(mGdkWindow),
gdk_x11_window_get_xid(mGdkWindow),
RevertToNone,
CurrentTime);
gdk_flush();
gdk_error_trap_pop();
gPluginFocusWindow = this;
gdk_window_add_filter(NULL, plugin_client_message_filter, this);
LOGFOCUS(("nsWindow::SetNonXEmbedPluginFocus oldfocus=%p new=%p\n",
mOldFocusWindow, gdk_x11_window_get_xid(mGdkWindow)));
}
void
nsWindow::LoseNonXEmbedPluginFocus()
{
LOGFOCUS(("nsWindow::LoseNonXEmbedPluginFocus\n"));
// This method is only for the nsWindow which contains a
// Non-XEmbed plugin, for example, JAVA plugin.
if (gPluginFocusWindow != this || mPluginType!=PluginType_NONXEMBED) {
return;
}
Window curFocusWindow;
int focusState;
XGetInputFocus(GDK_WINDOW_XDISPLAY(mGdkWindow),
&curFocusWindow,
&focusState);
// we only switch focus between plugin window and focus proxy. If the
// current focused window is not the plugin window, just removing the
// event filter that blocks the WM_TAKE_FOCUS is enough. WM and gtk2
// will take care of the focus later.
if (!curFocusWindow ||
curFocusWindow == gdk_x11_window_get_xid(mGdkWindow)) {
gdk_error_trap_push();
XRaiseWindow(GDK_WINDOW_XDISPLAY(mGdkWindow),
mOldFocusWindow);
XSetInputFocus(GDK_WINDOW_XDISPLAY(mGdkWindow),
mOldFocusWindow,
RevertToParent,
CurrentTime);
gdk_flush();
gdk_error_trap_pop();
}
gPluginFocusWindow = NULL;
mOldFocusWindow = 0;
gdk_window_remove_filter(NULL, plugin_client_message_filter, this);
LOGFOCUS(("nsWindow::LoseNonXEmbedPluginFocus end\n"));
}
#endif /* MOZ_X11 */
gint
nsWindow::ConvertBorderStyles(nsBorderStyle aStyle)
{
gint w = 0;
if (aStyle == eBorderStyle_default)
return -1;
// note that we don't handle eBorderStyle_close yet
if (aStyle & eBorderStyle_all)
w |= GDK_DECOR_ALL;
if (aStyle & eBorderStyle_border)
w |= GDK_DECOR_BORDER;
if (aStyle & eBorderStyle_resizeh)
w |= GDK_DECOR_RESIZEH;
if (aStyle & eBorderStyle_title)
w |= GDK_DECOR_TITLE;
if (aStyle & eBorderStyle_menu)
w |= GDK_DECOR_MENU;
if (aStyle & eBorderStyle_minimize)
w |= GDK_DECOR_MINIMIZE;
if (aStyle & eBorderStyle_maximize)
w |= GDK_DECOR_MAXIMIZE;
return w;
}
NS_IMETHODIMP
nsWindow::MakeFullScreen(bool aFullScreen)
{
LOG(("nsWindow::MakeFullScreen [%p] aFullScreen %d\n",
(void *)this, aFullScreen));
if (aFullScreen) {
if (mSizeMode != nsSizeMode_Fullscreen)
mLastSizeMode = mSizeMode;
mSizeMode = nsSizeMode_Fullscreen;
gtk_window_fullscreen(GTK_WINDOW(mShell));
}
else {
mSizeMode = mLastSizeMode;
gtk_window_unfullscreen(GTK_WINDOW(mShell));
}
NS_ASSERTION(mLastSizeMode != nsSizeMode_Fullscreen,
"mLastSizeMode should never be fullscreen");
return NS_OK;
}
NS_IMETHODIMP
nsWindow::HideWindowChrome(bool aShouldHide)
{
if (!mShell) {
// Pass the request to the toplevel window
GtkWidget *topWidget = GetToplevelWidget();
if (!topWidget)
return NS_ERROR_FAILURE;
nsWindow *topWindow = get_window_for_gtk_widget(topWidget);
if (!topWindow)
return NS_ERROR_FAILURE;
return topWindow->HideWindowChrome(aShouldHide);
}
// Sawfish, metacity, and presumably other window managers get
// confused if we change the window decorations while the window
// is visible.
bool wasVisible = false;
GdkWindow *shellWindow = gtk_widget_get_window(mShell);
if (gdk_window_is_visible(shellWindow)) {
gdk_window_hide(shellWindow);
wasVisible = true;
}
gint wmd;
if (aShouldHide)
wmd = 0;
else
wmd = ConvertBorderStyles(mBorderStyle);
if (wmd != -1)
gdk_window_set_decorations(shellWindow, (GdkWMDecoration) wmd);
if (wasVisible)
gdk_window_show(shellWindow);
// For some window managers, adding or removing window decorations
// requires unmapping and remapping our toplevel window. Go ahead
// and flush the queue here so that we don't end up with a BadWindow
// error later when this happens (when the persistence timer fires
// and GetWindowPos is called)
#ifdef MOZ_X11
XSync(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()) , False);
#else
gdk_flush ();
#endif /* MOZ_X11 */
return NS_OK;
}
bool
nsWindow::CheckForRollup(gdouble aMouseX, gdouble aMouseY,
bool aIsWheel, bool aAlwaysRollup)
{
nsIRollupListener* rollupListener = GetActiveRollupListener();
nsCOMPtr<nsIWidget> rollupWidget;
if (rollupListener) {
rollupWidget = rollupListener->GetRollupWidget();
}
if (!rollupWidget) {
nsBaseWidget::gRollupListener = nullptr;
return false;
}
bool retVal = false;
GdkWindow *currentPopup =
(GdkWindow *)rollupWidget->GetNativeData(NS_NATIVE_WINDOW);
if (aAlwaysRollup || !is_mouse_in_window(currentPopup, aMouseX, aMouseY)) {
bool rollup = true;
if (aIsWheel) {
rollup = rollupListener->ShouldRollupOnMouseWheelEvent();
retVal = true;
}
// if we're dealing with menus, we probably have submenus and
// we don't want to rollup if the click is in a parent menu of
// the current submenu
uint32_t popupsToRollup = UINT32_MAX;
if (!aAlwaysRollup) {
nsAutoTArray<nsIWidget*, 5> widgetChain;
uint32_t sameTypeCount = rollupListener->GetSubmenuWidgetChain(&widgetChain);
for (uint32_t i=0; i<widgetChain.Length(); ++i) {
nsIWidget* widget = widgetChain[i];
GdkWindow* currWindow =
(GdkWindow*) widget->GetNativeData(NS_NATIVE_WINDOW);
if (is_mouse_in_window(currWindow, aMouseX, aMouseY)) {
// don't roll up if the mouse event occurred within a
// menu of the same type. If the mouse event occurred
// in a menu higher than that, roll up, but pass the
// number of popups to Rollup so that only those of the
// same type close up.
if (i < sameTypeCount) {
rollup = false;
}
else {
popupsToRollup = sameTypeCount;
}
break;
}
} // foreach parent menu widget
} // if rollup listener knows about menus
// if we've determined that we should still rollup, do it.
if (rollup && rollupListener->Rollup(popupsToRollup, nullptr)) {
retVal = true;
}
}
return retVal;
}
/* static */
bool
nsWindow::DragInProgress(void)
{
nsCOMPtr<nsIDragService> dragService = do_GetService(kCDragServiceCID);
if (!dragService)
return false;
nsCOMPtr<nsIDragSession> currentDragSession;
dragService->GetCurrentSession(getter_AddRefs(currentDragSession));
return currentDragSession != nullptr;
}
static bool
is_mouse_in_window (GdkWindow* aWindow, gdouble aMouseX, gdouble aMouseY)
{
gint x = 0;
gint y = 0;
gint w, h;
gint offsetX = 0;
gint offsetY = 0;
GdkWindow *window = aWindow;
while (window) {
gint tmpX = 0;
gint tmpY = 0;
gdk_window_get_position(window, &tmpX, &tmpY);
GtkWidget *widget = get_gtk_widget_for_gdk_window(window);
// if this is a window, compute x and y given its origin and our
// offset
if (GTK_IS_WINDOW(widget)) {
x = tmpX + offsetX;
y = tmpY + offsetY;
break;
}
offsetX += tmpX;
offsetY += tmpY;
window = gdk_window_get_parent(window);
}
#if defined(MOZ_WIDGET_GTK2)
gdk_drawable_get_size(aWindow, &w, &h);
#else
w = gdk_window_get_width(aWindow);
h = gdk_window_get_height(aWindow);
#endif
if (aMouseX > x && aMouseX < x + w &&
aMouseY > y && aMouseY < y + h)
return true;
return false;
}
static nsWindow *
get_window_for_gtk_widget(GtkWidget *widget)
{
gpointer user_data = g_object_get_data(G_OBJECT(widget), "nsWindow");
return static_cast<nsWindow *>(user_data);
}
static nsWindow *
get_window_for_gdk_window(GdkWindow *window)
{
gpointer user_data = g_object_get_data(G_OBJECT(window), "nsWindow");
return static_cast<nsWindow *>(user_data);
}
static GtkWidget *
get_gtk_widget_for_gdk_window(GdkWindow *window)
{
gpointer user_data = NULL;
gdk_window_get_user_data(window, &user_data);
return GTK_WIDGET(user_data);
}
static GdkCursor *
get_gtk_cursor(nsCursor aCursor)
{
GdkCursor *gdkcursor = nullptr;
uint8_t newType = 0xff;
if ((gdkcursor = gCursorCache[aCursor])) {
return gdkcursor;
}
switch (aCursor) {
case eCursor_standard:
gdkcursor = gdk_cursor_new(GDK_LEFT_PTR);
break;
case eCursor_wait:
gdkcursor = gdk_cursor_new(GDK_WATCH);
break;
case eCursor_select:
gdkcursor = gdk_cursor_new(GDK_XTERM);
break;
case eCursor_hyperlink:
gdkcursor = gdk_cursor_new(GDK_HAND2);
break;
case eCursor_n_resize:
gdkcursor = gdk_cursor_new(GDK_TOP_SIDE);
break;
case eCursor_s_resize:
gdkcursor = gdk_cursor_new(GDK_BOTTOM_SIDE);
break;
case eCursor_w_resize:
gdkcursor = gdk_cursor_new(GDK_LEFT_SIDE);
break;
case eCursor_e_resize:
gdkcursor = gdk_cursor_new(GDK_RIGHT_SIDE);
break;
case eCursor_nw_resize:
gdkcursor = gdk_cursor_new(GDK_TOP_LEFT_CORNER);
break;
case eCursor_se_resize:
gdkcursor = gdk_cursor_new(GDK_BOTTOM_RIGHT_CORNER);
break;
case eCursor_ne_resize:
gdkcursor = gdk_cursor_new(GDK_TOP_RIGHT_CORNER);
break;
case eCursor_sw_resize:
gdkcursor = gdk_cursor_new(GDK_BOTTOM_LEFT_CORNER);
break;
case eCursor_crosshair:
gdkcursor = gdk_cursor_new(GDK_CROSSHAIR);
break;
case eCursor_move:
gdkcursor = gdk_cursor_new(GDK_FLEUR);
break;
case eCursor_help:
gdkcursor = gdk_cursor_new(GDK_QUESTION_ARROW);
break;
case eCursor_copy: // CSS3
newType = MOZ_CURSOR_COPY;
break;
case eCursor_alias:
newType = MOZ_CURSOR_ALIAS;
break;
case eCursor_context_menu:
newType = MOZ_CURSOR_CONTEXT_MENU;
break;
case eCursor_cell:
gdkcursor = gdk_cursor_new(GDK_PLUS);
break;
case eCursor_grab:
newType = MOZ_CURSOR_HAND_GRAB;
break;
case eCursor_grabbing:
newType = MOZ_CURSOR_HAND_GRABBING;
break;
case eCursor_spinning:
newType = MOZ_CURSOR_SPINNING;
break;
case eCursor_zoom_in:
newType = MOZ_CURSOR_ZOOM_IN;
break;
case eCursor_zoom_out:
newType = MOZ_CURSOR_ZOOM_OUT;
break;
case eCursor_not_allowed:
case eCursor_no_drop:
newType = MOZ_CURSOR_NOT_ALLOWED;
break;
case eCursor_vertical_text:
newType = MOZ_CURSOR_VERTICAL_TEXT;
break;
case eCursor_all_scroll:
gdkcursor = gdk_cursor_new(GDK_FLEUR);
break;
case eCursor_nesw_resize:
newType = MOZ_CURSOR_NESW_RESIZE;
break;
case eCursor_nwse_resize:
newType = MOZ_CURSOR_NWSE_RESIZE;
break;
case eCursor_ns_resize:
case eCursor_row_resize:
gdkcursor = gdk_cursor_new(GDK_SB_V_DOUBLE_ARROW);
break;
case eCursor_ew_resize:
case eCursor_col_resize:
gdkcursor = gdk_cursor_new(GDK_SB_H_DOUBLE_ARROW);
break;
case eCursor_none:
newType = MOZ_CURSOR_NONE;
break;
default:
NS_ASSERTION(aCursor, "Invalid cursor type");
gdkcursor = gdk_cursor_new(GDK_LEFT_PTR);
break;
}
// If by now we don't have a xcursor, this means we have to make a custom
// one. First, we try creating a named cursor based on the hash of our
// custom bitmap, as libXcursor has some magic to convert bitmapped cursors
// to themed cursors
if (newType != 0xFF && GtkCursors[newType].hash) {
gdkcursor = gdk_cursor_new_from_name(gdk_display_get_default(),
GtkCursors[newType].hash);
}
// If we still don't have a xcursor, we now really create a bitmap cursor
if (newType != 0xff && !gdkcursor) {
GdkPixbuf * cursor_pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 32, 32);
if (!cursor_pixbuf)
return NULL;
guchar *data = gdk_pixbuf_get_pixels(cursor_pixbuf);
// Read data from GtkCursors and compose RGBA surface from 1bit bitmap and mask
// GtkCursors bits and mask are 32x32 monochrome bitmaps (1 bit for each pixel)
// so it's 128 byte array (4 bytes for are one bitmap row and there are 32 rows here).
const unsigned char *bits = GtkCursors[newType].bits;
const unsigned char *mask_bits = GtkCursors[newType].mask_bits;
for (int i = 0; i < 128; i++) {
char bit = *bits++;
char mask = *mask_bits++;
for (int j = 0; j < 8; j++) {
unsigned char pix = ~(((bit >> j) & 0x01) * 0xff);
*data++ = pix;
*data++ = pix;
*data++ = pix;
*data++ = (((mask >> j) & 0x01) * 0xff);
}
}
gdkcursor = gdk_cursor_new_from_pixbuf(gdk_display_get_default(), cursor_pixbuf,
GtkCursors[newType].hot_x,
GtkCursors[newType].hot_y);
g_object_unref(cursor_pixbuf);
}
gCursorCache[aCursor] = gdkcursor;
return gdkcursor;
}
// gtk callbacks
#if defined(MOZ_WIDGET_GTK2)
static gboolean
expose_event_cb(GtkWidget *widget, GdkEventExpose *event)
{
nsRefPtr<nsWindow> window = get_window_for_gdk_window(event->window);
if (!window)
return FALSE;
window->OnExposeEvent(event);
return FALSE;
}
#else
void
draw_window_of_widget(GtkWidget *widget, GdkWindow *aWindow, cairo_t *cr)
{
if (gtk_cairo_should_draw_window(cr, aWindow)) {
nsRefPtr<nsWindow> window = get_window_for_gdk_window(aWindow);
if (!window) {
NS_WARNING("Cannot get nsWindow from GtkWidget");
}
else {
cairo_save(cr);
gtk_cairo_transform_to_window(cr, widget, aWindow);
// TODO - window->OnExposeEvent() can destroy this or other windows,
// do we need to handle it somehow?
window->OnExposeEvent(cr);
cairo_restore(cr);
}
}
GList *children = gdk_window_get_children(aWindow);
GList *child = children;
while (child) {
GdkWindow *window = GDK_WINDOW(child->data);
gpointer windowWidget;
gdk_window_get_user_data(window, &windowWidget);
if (windowWidget == widget) {
draw_window_of_widget(widget, window, cr);
}
child = g_list_next(child);
}
g_list_free(children);
}
/* static */
gboolean
expose_event_cb(GtkWidget *widget, cairo_t *cr)
{
draw_window_of_widget(widget, gtk_widget_get_window(widget), cr);
return FALSE;
}
#endif //MOZ_WIDGET_GTK2
static gboolean
configure_event_cb(GtkWidget *widget,
GdkEventConfigure *event)
{
nsRefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
if (!window)
return FALSE;
return window->OnConfigureEvent(widget, event);
}
static void
container_unrealize_cb (GtkWidget *widget)
{
nsRefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
if (!window)
return;
window->OnContainerUnrealize();
}
static void
size_allocate_cb (GtkWidget *widget, GtkAllocation *allocation)
{
nsRefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
if (!window)
return;
window->OnSizeAllocate(allocation);
}
static gboolean
delete_event_cb(GtkWidget *widget, GdkEventAny *event)
{
nsRefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
if (!window)
return FALSE;
window->OnDeleteEvent();
return TRUE;
}
static gboolean
enter_notify_event_cb(GtkWidget *widget,
GdkEventCrossing *event)
{
nsRefPtr<nsWindow> window = get_window_for_gdk_window(event->window);
if (!window)
return TRUE;
window->OnEnterNotifyEvent(event);
return TRUE;
}
static gboolean
leave_notify_event_cb(GtkWidget *widget,
GdkEventCrossing *event)
{
if (is_parent_grab_leave(event)) {
return TRUE;
}
// bug 369599: Suppress LeaveNotify events caused by pointer grabs to
// avoid generating spurious mouse exit events.
gint x = gint(event->x_root);
gint y = gint(event->y_root);
GdkDisplay* display = gtk_widget_get_display(widget);
GdkWindow* winAtPt = gdk_display_get_window_at_pointer(display, &x, &y);
if (winAtPt == event->window) {
return TRUE;
}
nsRefPtr<nsWindow> window = get_window_for_gdk_window(event->window);
if (!window)
return TRUE;
window->OnLeaveNotifyEvent(event);
return TRUE;
}
static nsWindow*
GetFirstNSWindowForGDKWindow(GdkWindow *aGdkWindow)
{
nsWindow* window;
while (!(window = get_window_for_gdk_window(aGdkWindow))) {
// The event has bubbled to the moz_container widget as passed into each caller's *widget parameter,
// but its corresponding nsWindow is an ancestor of the window that we need. Instead, look at
// event->window and find the first ancestor nsWindow of it because event->window may be in a plugin.
aGdkWindow = gdk_window_get_parent(aGdkWindow);
if (!aGdkWindow) {
window = nullptr;
break;
}
}
return window;
}
static gboolean
motion_notify_event_cb(GtkWidget *widget, GdkEventMotion *event)
{
UpdateLastInputEventTime(event);
nsWindow *window = GetFirstNSWindowForGDKWindow(event->window);
if (!window)
return FALSE;
window->OnMotionNotifyEvent(event);
#ifdef MOZ_PLATFORM_MAEMO
gdk_event_request_motions(event);
#endif
return TRUE;
}
static gboolean
button_press_event_cb(GtkWidget *widget, GdkEventButton *event)
{
UpdateLastInputEventTime(event);
nsWindow *window = GetFirstNSWindowForGDKWindow(event->window);
if (!window)
return FALSE;
window->OnButtonPressEvent(event);
return TRUE;
}
static gboolean
button_release_event_cb(GtkWidget *widget, GdkEventButton *event)
{
UpdateLastInputEventTime(event);
nsWindow *window = GetFirstNSWindowForGDKWindow(event->window);
if (!window)
return FALSE;
window->OnButtonReleaseEvent(event);
return TRUE;
}
static gboolean
focus_in_event_cb(GtkWidget *widget, GdkEventFocus *event)
{
nsRefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
if (!window)
return FALSE;
window->OnContainerFocusInEvent(event);
return FALSE;
}
static gboolean
focus_out_event_cb(GtkWidget *widget, GdkEventFocus *event)
{
nsRefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
if (!window)
return FALSE;
window->OnContainerFocusOutEvent(event);
return FALSE;
}
#ifdef MOZ_X11
// For long-lived popup windows that don't really take focus themselves but
// may have elements that accept keyboard input when the parent window is
// active, focus is handled specially. These windows include noautohide
// panels. (This special handling is not necessary for temporary popups where
// the keyboard is grabbed.)
//
// Mousing over or clicking on these windows should not cause them to steal
// focus from their parent windows, so, the input field of WM_HINTS is set to
// False to request that the window manager not set the input focus to this
// window. http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.7
//
// However, these windows can still receive WM_TAKE_FOCUS messages from the
// window manager, so they can still detect when the user has indicated that
// they wish to direct keyboard input at these windows. When the window
// manager offers focus to these windows (after a mouse over or click, for
// example), a request to make the parent window active is issued. When the
// parent window becomes active, keyboard events will be received.
static GdkFilterReturn
popup_take_focus_filter(GdkXEvent *gdk_xevent,
GdkEvent *event,
gpointer data)
{
XEvent* xevent = static_cast<XEvent*>(gdk_xevent);
if (xevent->type != ClientMessage)
return GDK_FILTER_CONTINUE;
XClientMessageEvent& xclient = xevent->xclient;
if (xclient.message_type != gdk_x11_get_xatom_by_name("WM_PROTOCOLS"))
return GDK_FILTER_CONTINUE;
Atom atom = xclient.data.l[0];
if (atom != gdk_x11_get_xatom_by_name("WM_TAKE_FOCUS"))
return GDK_FILTER_CONTINUE;
guint32 timestamp = xclient.data.l[1];
GtkWidget* widget = get_gtk_widget_for_gdk_window(event->any.window);
if (!widget)
return GDK_FILTER_CONTINUE;
GtkWindow* parent = gtk_window_get_transient_for(GTK_WINDOW(widget));
if (!parent)
return GDK_FILTER_CONTINUE;
if (gtk_window_is_active(parent))
return GDK_FILTER_REMOVE; // leave input focus on the parent
GdkWindow* parent_window = gtk_widget_get_window(GTK_WIDGET(parent));
if (!parent_window)
return GDK_FILTER_CONTINUE;
// In case the parent has not been deconified.
gdk_window_show_unraised(parent_window);
// Request focus on the parent window.
// Use gdk_window_focus rather than gtk_window_present to avoid
// raising the parent window.
gdk_window_focus(parent_window, timestamp);
return GDK_FILTER_REMOVE;
}
static GdkFilterReturn
plugin_window_filter_func(GdkXEvent *gdk_xevent, GdkEvent *event, gpointer data)
{
GdkWindow *plugin_window;
XEvent *xevent;
Window xeventWindow;
nsRefPtr<nsWindow> nswindow = (nsWindow*)data;
GdkFilterReturn return_val;
xevent = (XEvent *)gdk_xevent;
return_val = GDK_FILTER_CONTINUE;
switch (xevent->type)
{
case CreateNotify:
case ReparentNotify:
if (xevent->type==CreateNotify) {
xeventWindow = xevent->xcreatewindow.window;
}
else {
if (xevent->xreparent.event != xevent->xreparent.parent)
break;
xeventWindow = xevent->xreparent.window;
}
#if defined(MOZ_WIDGET_GTK2)
plugin_window = gdk_window_lookup(xeventWindow);
#else
plugin_window = gdk_x11_window_lookup_for_display(
gdk_x11_lookup_xdisplay(xevent->xcreatewindow.display), xeventWindow);
#endif
if (plugin_window) {
GtkWidget *widget =
get_gtk_widget_for_gdk_window(plugin_window);
// TODO GTK3
#if defined(MOZ_WIDGET_GTK2)
if (GTK_IS_XTBIN(widget)) {
nswindow->SetPluginType(nsWindow::PluginType_NONXEMBED);
break;
}
else
#endif
if(GTK_IS_SOCKET(widget)) {
if (!g_object_get_data(G_OBJECT(widget), "enable-xt-focus")) {
nswindow->SetPluginType(nsWindow::PluginType_XEMBED);
break;
}
}
}
nswindow->SetPluginType(nsWindow::PluginType_NONXEMBED);
return_val = GDK_FILTER_REMOVE;
break;
case EnterNotify:
nswindow->SetNonXEmbedPluginFocus();
break;
case DestroyNotify:
gdk_window_remove_filter
((GdkWindow*)(nswindow->GetNativeData(NS_NATIVE_WINDOW)),
plugin_window_filter_func,
nswindow);
// Currently we consider all plugins are non-xembed and calls
// LoseNonXEmbedPluginFocus without any checking.
nswindow->LoseNonXEmbedPluginFocus();
break;
default:
break;
}
return return_val;
}
static GdkFilterReturn
plugin_client_message_filter(GdkXEvent *gdk_xevent,
GdkEvent *event,
gpointer data)
{
XEvent *xevent;
xevent = (XEvent *)gdk_xevent;
GdkFilterReturn return_val;
return_val = GDK_FILTER_CONTINUE;
if (!gPluginFocusWindow || xevent->type!=ClientMessage) {
return return_val;
}
// When WM sends out WM_TAKE_FOCUS, gtk2 will use XSetInputFocus
// to set the focus to the focus proxy. To prevent this happen
// while the focus is on the plugin, we filter the WM_TAKE_FOCUS
// out.
if (gdk_x11_get_xatom_by_name("WM_PROTOCOLS")
!= xevent->xclient.message_type) {
return return_val;
}
if ((Atom) xevent->xclient.data.l[0] ==
gdk_x11_get_xatom_by_name("WM_TAKE_FOCUS")) {
// block it from gtk2.0 focus proxy
return_val = GDK_FILTER_REMOVE;
}
return return_val;
}
#endif /* MOZ_X11 */
static gboolean
key_press_event_cb(GtkWidget *widget, GdkEventKey *event)
{
LOG(("key_press_event_cb\n"));
UpdateLastInputEventTime(event);
// find the window with focus and dispatch this event to that widget
nsWindow *window = get_window_for_gtk_widget(widget);
if (!window)
return FALSE;
nsRefPtr<nsWindow> focusWindow = gFocusWindow ? gFocusWindow : window;
#ifdef MOZ_X11
// Keyboard repeat can cause key press events to queue up when there are
// slow event handlers (bug 301029). Throttle these events by removing
// consecutive pending duplicate KeyPress events to the same window.
// We use the event time of the last one.
// Note: GDK calls XkbSetDetectableAutorepeat so that KeyRelease events
// are generated only when the key is physically released.
#define NS_GDKEVENT_MATCH_MASK 0x1FFF /* GDK_SHIFT_MASK .. GDK_BUTTON5_MASK */
GdkDisplay* gdkDisplay = gtk_widget_get_display(widget);
Display* dpy = GDK_DISPLAY_XDISPLAY(gdkDisplay);
while (XPending(dpy)) {
XEvent next_event;
XPeekEvent(dpy, &next_event);
GdkWindow* nextGdkWindow =
gdk_x11_window_lookup_for_display(gdkDisplay, next_event.xany.window);
if (nextGdkWindow != event->window ||
next_event.type != KeyPress ||
next_event.xkey.keycode != event->hardware_keycode ||
next_event.xkey.state != (event->state & NS_GDKEVENT_MATCH_MASK)) {
break;
}
XNextEvent(dpy, &next_event);
event->time = next_event.xkey.time;
}
#endif
return focusWindow->OnKeyPressEvent(event);
}
static gboolean
key_release_event_cb(GtkWidget *widget, GdkEventKey *event)
{
LOG(("key_release_event_cb\n"));
UpdateLastInputEventTime(event);
// find the window with focus and dispatch this event to that widget
nsWindow *window = get_window_for_gtk_widget(widget);
if (!window)
return FALSE;
nsRefPtr<nsWindow> focusWindow = gFocusWindow ? gFocusWindow : window;
return focusWindow->OnKeyReleaseEvent(event);
}
static gboolean
scroll_event_cb(GtkWidget *widget, GdkEventScroll *event)
{
nsWindow *window = GetFirstNSWindowForGDKWindow(event->window);
if (!window)
return FALSE;
window->OnScrollEvent(event);
return TRUE;
}
static gboolean
visibility_notify_event_cb (GtkWidget *widget, GdkEventVisibility *event)
{
nsRefPtr<nsWindow> window = get_window_for_gdk_window(event->window);
if (!window)
return FALSE;
window->OnVisibilityNotifyEvent(event);
return TRUE;
}
static void
hierarchy_changed_cb (GtkWidget *widget,
GtkWidget *previous_toplevel)
{
GtkWidget *toplevel = gtk_widget_get_toplevel(widget);
GdkWindowState old_window_state = GDK_WINDOW_STATE_WITHDRAWN;
GdkEventWindowState event;
event.new_window_state = GDK_WINDOW_STATE_WITHDRAWN;
if (GTK_IS_WINDOW(previous_toplevel)) {
g_signal_handlers_disconnect_by_func(previous_toplevel,
FuncToGpointer(window_state_event_cb),
widget);
GdkWindow *win = gtk_widget_get_window(previous_toplevel);
if (win) {
old_window_state = gdk_window_get_state(win);
}
}
if (GTK_IS_WINDOW(toplevel)) {
g_signal_connect_swapped(toplevel, "window-state-event",
G_CALLBACK(window_state_event_cb), widget);
GdkWindow *win = gtk_widget_get_window(toplevel);
if (win) {
event.new_window_state = gdk_window_get_state(win);
}
}
event.changed_mask = static_cast<GdkWindowState>
(old_window_state ^ event.new_window_state);
if (event.changed_mask) {
event.type = GDK_WINDOW_STATE;
event.window = NULL;
event.send_event = TRUE;
window_state_event_cb(widget, &event);
}
}
static gboolean
window_state_event_cb (GtkWidget *widget, GdkEventWindowState *event)
{
nsRefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
if (!window)
return FALSE;
window->OnWindowStateEvent(widget, event);
return FALSE;
}
static void
theme_changed_cb (GtkSettings *settings, GParamSpec *pspec, nsWindow *data)
{
nsRefPtr<nsWindow> window = data;
window->ThemeChanged();
}
//////////////////////////////////////////////////////////////////////
// These are all of our drag and drop operations
void
nsWindow::InitDragEvent(nsDragEvent &aEvent)
{
// set the keyboard modifiers
guint modifierState = KeymapWrapper::GetCurrentModifierState();
KeymapWrapper::InitInputEvent(aEvent, modifierState);
}
static gboolean
drag_motion_event_cb(GtkWidget *aWidget,
GdkDragContext *aDragContext,
gint aX,
gint aY,
guint aTime,
gpointer aData)
{
nsRefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget);
if (!window)
return FALSE;
// figure out which internal widget this drag motion actually happened on
nscoord retx = 0;
nscoord rety = 0;
GdkWindow *innerWindow =
get_inner_gdk_window(gtk_widget_get_window(aWidget), aX, aY,
&retx, &rety);
nsRefPtr<nsWindow> innerMostWindow = get_window_for_gdk_window(innerWindow);
if (!innerMostWindow) {
innerMostWindow = window;
}
LOGDRAG(("nsWindow drag-motion signal for %p\n", (void*)innerMostWindow));
return nsDragService::GetInstance()->
ScheduleMotionEvent(innerMostWindow, aDragContext,
nsIntPoint(retx, rety), aTime);
}
static void
drag_leave_event_cb(GtkWidget *aWidget,
GdkDragContext *aDragContext,
guint aTime,
gpointer aData)
{
nsRefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget);
if (!window)
return;
nsDragService *dragService = nsDragService::GetInstance();
nsWindow *mostRecentDragWindow = dragService->GetMostRecentDestWindow();
if (!mostRecentDragWindow) {
// This can happen when the target will not accept a drop. A GTK drag
// source sends the leave message to the destination before the
// drag-failed signal on the source widget, but the leave message goes
// via the X server, and so doesn't get processed at least until the
// event loop runs again.
return;
}
GtkWidget *mozContainer = mostRecentDragWindow->GetMozContainerWidget();
if (aWidget != mozContainer)
{
// When the drag moves between widgets, GTK can send leave signal for
// the old widget after the motion or drop signal for the new widget.
// We'll send the leave event when the motion or drop event is run.
return;
}
LOGDRAG(("nsWindow drag-leave signal for %p\n",
(void*)mostRecentDragWindow));
dragService->ScheduleLeaveEvent();
}
static gboolean
drag_drop_event_cb(GtkWidget *aWidget,
GdkDragContext *aDragContext,
gint aX,
gint aY,
guint aTime,
gpointer aData)
{
nsRefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget);
if (!window)
return FALSE;
// figure out which internal widget this drag motion actually happened on
nscoord retx = 0;
nscoord rety = 0;
GdkWindow *innerWindow =
get_inner_gdk_window(gtk_widget_get_window(aWidget), aX, aY,
&retx, &rety);
nsRefPtr<nsWindow> innerMostWindow = get_window_for_gdk_window(innerWindow);
if (!innerMostWindow) {
innerMostWindow = window;
}
LOGDRAG(("nsWindow drag-drop signal for %p\n", (void*)innerMostWindow));
return nsDragService::GetInstance()->
ScheduleDropEvent(innerMostWindow, aDragContext,
nsIntPoint(retx, rety), aTime);
}
static void
drag_data_received_event_cb(GtkWidget *aWidget,
GdkDragContext *aDragContext,
gint aX,
gint aY,
GtkSelectionData *aSelectionData,
guint aInfo,
guint aTime,
gpointer aData)
{
nsRefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget);
if (!window)
return;
window->OnDragDataReceivedEvent(aWidget,
aDragContext,
aX, aY,
aSelectionData,
aInfo, aTime, aData);
}
static nsresult
initialize_prefs(void)
{
gRaiseWindows =
Preferences::GetBool("mozilla.widget.raise-on-setfocus", true);
return NS_OK;
}
static GdkWindow *
get_inner_gdk_window (GdkWindow *aWindow,
gint x, gint y,
gint *retx, gint *rety)
{
gint cx, cy, cw, ch;
GList *children = gdk_window_peek_children(aWindow);
for (GList *child = g_list_last(children);
child;
child = g_list_previous(child)) {
GdkWindow *childWindow = (GdkWindow *) child->data;
if (get_window_for_gdk_window(childWindow)) {
#if defined(MOZ_WIDGET_GTK2)
gdk_window_get_geometry(childWindow, &cx, &cy, &cw, &ch, NULL);
#else
gdk_window_get_geometry(childWindow, &cx, &cy, &cw, &ch);
#endif
if ((cx < x) && (x < (cx + cw)) &&
(cy < y) && (y < (cy + ch)) &&
gdk_window_is_visible(childWindow)) {
return get_inner_gdk_window(childWindow,
x - cx, y - cy,
retx, rety);
}
}
}
*retx = x;
*rety = y;
return aWindow;
}
static inline bool
is_context_menu_key(const nsKeyEvent& aKeyEvent)
{
return ((aKeyEvent.keyCode == NS_VK_F10 && aKeyEvent.IsShift() &&
!aKeyEvent.IsControl() && !aKeyEvent.IsMeta() &&
!aKeyEvent.IsAlt()) ||
(aKeyEvent.keyCode == NS_VK_CONTEXT_MENU && !aKeyEvent.IsShift() &&
!aKeyEvent.IsControl() && !aKeyEvent.IsMeta() &&
!aKeyEvent.IsAlt()));
}
static int
is_parent_ungrab_enter(GdkEventCrossing *aEvent)
{
return (GDK_CROSSING_UNGRAB == aEvent->mode) &&
((GDK_NOTIFY_ANCESTOR == aEvent->detail) ||
(GDK_NOTIFY_VIRTUAL == aEvent->detail));
}
static int
is_parent_grab_leave(GdkEventCrossing *aEvent)
{
return (GDK_CROSSING_GRAB == aEvent->mode) &&
((GDK_NOTIFY_ANCESTOR == aEvent->detail) ||
(GDK_NOTIFY_VIRTUAL == aEvent->detail));
}
#ifdef ACCESSIBILITY
void
nsWindow::CreateRootAccessible()
{
if (mIsTopLevel && !mRootAccessible) {
LOG(("nsWindow:: Create Toplevel Accessibility\n"));
mRootAccessible = GetAccessible();
}
}
void
nsWindow::DispatchEventToRootAccessible(uint32_t aEventType)
{
if (!a11y::ShouldA11yBeEnabled()) {
return;
}
nsCOMPtr<nsIAccessibilityService> accService =
do_GetService("@mozilla.org/accessibilityService;1");
if (!accService) {
return;
}
// Get the root document accessible and fire event to it.
a11y::Accessible* acc = GetAccessible();
if (acc) {
accService->FireAccessibleEvent(aEventType, acc);
}
}
void
nsWindow::DispatchActivateEventAccessible(void)
{
DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_ACTIVATE);
}
void
nsWindow::DispatchDeactivateEventAccessible(void)
{
DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_DEACTIVATE);
}
void
nsWindow::DispatchMaximizeEventAccessible(void)
{
DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_MAXIMIZE);
}
void
nsWindow::DispatchMinimizeEventAccessible(void)
{
DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_MINIMIZE);
}
void
nsWindow::DispatchRestoreEventAccessible(void)
{
DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_RESTORE);
}
#endif /* #ifdef ACCESSIBILITY */
// nsChildWindow class
nsChildWindow::nsChildWindow()
{
}
nsChildWindow::~nsChildWindow()
{
}
NS_IMETHODIMP
nsWindow::NotifyIME(NotificationToIME aNotification)
{
if (MOZ_UNLIKELY(!mIMModule)) {
switch (aNotification) {
case NOTIFY_IME_OF_CURSOR_POS_CHANGED:
case REQUEST_TO_COMMIT_COMPOSITION:
case REQUEST_TO_CANCEL_COMPOSITION:
case NOTIFY_IME_OF_FOCUS:
case NOTIFY_IME_OF_BLUR:
return NS_ERROR_NOT_AVAILABLE;
default:
break;
}
}
switch (aNotification) {
// TODO: We should replace NOTIFY_IME_OF_CURSOR_POS_CHANGED with
// NOTIFY_IME_OF_SELECTION_CHANGE. The required behavior is
// really different from committing composition.
case NOTIFY_IME_OF_CURSOR_POS_CHANGED:
case REQUEST_TO_COMMIT_COMPOSITION:
return mIMModule->CommitIMEComposition(this);
case REQUEST_TO_CANCEL_COMPOSITION:
return mIMModule->CancelIMEComposition(this);
case NOTIFY_IME_OF_FOCUS:
mIMModule->OnFocusChangeInGecko(true);
return NS_OK;
case NOTIFY_IME_OF_BLUR:
mIMModule->OnFocusChangeInGecko(false);
return NS_OK;
default:
return NS_ERROR_NOT_IMPLEMENTED;
}
}
NS_IMETHODIMP_(void)
nsWindow::SetInputContext(const InputContext& aContext,
const InputContextAction& aAction)
{
if (!mIMModule) {
return;
}
mIMModule->SetInputContext(this, &aContext, &aAction);
}
NS_IMETHODIMP_(InputContext)
nsWindow::GetInputContext()
{
InputContext context;
if (!mIMModule) {
context.mIMEState.mEnabled = IMEState::DISABLED;
context.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED;
// If IME context isn't available on this widget, we should set |this|
// instead of nullptr since nullptr means that the platform has only one
// context per process.
context.mNativeIMEContext = this;
} else {
context = mIMModule->GetInputContext();
context.mNativeIMEContext = mIMModule;
}
return context;
}
NS_IMETHODIMP
nsWindow::GetToggledKeyState(uint32_t aKeyCode, bool* aLEDState)
{
NS_ENSURE_ARG_POINTER(aLEDState);
KeymapWrapper::Modifiers modifier;
switch (aKeyCode) {
case NS_VK_CAPS_LOCK: modifier = KeymapWrapper::CAPS_LOCK; break;
case NS_VK_NUM_LOCK: modifier = KeymapWrapper::NUM_LOCK; break;
case NS_VK_SCROLL_LOCK: modifier = KeymapWrapper::SCROLL_LOCK; break;
default: return NS_ERROR_INVALID_ARG;
}
*aLEDState =
KeymapWrapper::AreModifiersCurrentlyActive(modifier);
return NS_OK;
}
#if defined(MOZ_X11) && defined(MOZ_WIDGET_GTK2)
/* static */ already_AddRefed<gfxASurface>
nsWindow::GetSurfaceForGdkDrawable(GdkDrawable* aDrawable,
const nsIntSize& aSize)
{
GdkVisual* visual = gdk_drawable_get_visual(aDrawable);
Screen* xScreen =
gdk_x11_screen_get_xscreen(gdk_drawable_get_screen(aDrawable));
Display* xDisplay = DisplayOfScreen(xScreen);
Drawable xDrawable = gdk_x11_drawable_get_xid(aDrawable);
gfxASurface* result = nullptr;
if (visual) {
Visual* xVisual = gdk_x11_visual_get_xvisual(visual);
result = new gfxXlibSurface(xDisplay, xDrawable, xVisual,
gfxIntSize(aSize.width, aSize.height));
} else {
// no visual? we must be using an xrender format. Find a format
// for this depth.
XRenderPictFormat *pf = NULL;
switch (gdk_drawable_get_depth(aDrawable)) {
case 32:
pf = XRenderFindStandardFormat(xDisplay, PictStandardARGB32);
break;
case 24:
pf = XRenderFindStandardFormat(xDisplay, PictStandardRGB24);
break;
default:
NS_ERROR("Don't know how to handle the given depth!");
break;
}
result = new gfxXlibSurface(xScreen, xDrawable, pf,
gfxIntSize(aSize.width, aSize.height));
}
NS_IF_ADDREF(result);
return result;
}
#endif
// return the gfxASurface for rendering to this widget
gfxASurface*
#if defined(MOZ_WIDGET_GTK2)
nsWindow::GetThebesSurface()
#else
nsWindow::GetThebesSurface(cairo_t *cr)
#endif
{
if (!mGdkWindow)
return nullptr;
#if !defined(MOZ_WIDGET_GTK2)
cairo_surface_t *surf = cairo_get_target(cr);
if (cairo_surface_status(surf) != CAIRO_STATUS_SUCCESS) {
NS_NOTREACHED("Missing cairo target?");
return NULL;
}
#endif // MOZ_WIDGET_GTK2
#ifdef MOZ_X11
gint width, height;
#if defined(MOZ_WIDGET_GTK2)
gdk_drawable_get_size(GDK_DRAWABLE(mGdkWindow), &width, &height);
#else
width = gdk_window_get_width(mGdkWindow);
height = gdk_window_get_height(mGdkWindow);
#endif
// Owen Taylor says this is the right thing to do!
width = std::min(32767, width);
height = std::min(32767, height);
gfxIntSize size(width, height);
GdkVisual *gdkVisual = gdk_window_get_visual(mGdkWindow);
Visual* visual = gdk_x11_visual_get_xvisual(gdkVisual);
# ifdef MOZ_HAVE_SHMIMAGE
bool usingShm = false;
if (nsShmImage::UseShm()) {
// EnsureShmImage() is a dangerous interface, but we guarantee
// that the thebes surface and the shmimage have the same
// lifetime
mThebesSurface =
nsShmImage::EnsureShmImage(size,
visual, gdk_visual_get_depth(gdkVisual),
mShmImage);
usingShm = mThebesSurface != nullptr;
}
if (!usingShm)
# endif // MOZ_HAVE_SHMIMAGE
#if defined(MOZ_WIDGET_GTK2)
mThebesSurface = new gfxXlibSurface
(GDK_WINDOW_XDISPLAY(mGdkWindow),
gdk_x11_window_get_xid(mGdkWindow),
visual,
size);
#else
#if MOZ_TREE_CAIRO
#error "cairo-gtk3 target must be built with --enable-system-cairo"
#else
mThebesSurface = gfxASurface::Wrap(surf);
#endif
#endif
#endif
// if the surface creation is reporting an error, then
// we don't have a surface to give back
if (mThebesSurface && mThebesSurface->CairoStatus() != 0) {
mThebesSurface = nullptr;
}
return mThebesSurface;
}
// Code shared begin BeginMoveDrag and BeginResizeDrag
bool
nsWindow::GetDragInfo(nsMouseEvent* aMouseEvent,
GdkWindow** aWindow, gint* aButton,
gint* aRootX, gint* aRootY)
{
if (aMouseEvent->button != nsMouseEvent::eLeftButton) {
// we can only begin a move drag with the left mouse button
return false;
}
*aButton = 1;
// get the gdk window for this widget
GdkWindow* gdk_window = mGdkWindow;
if (!gdk_window) {
return false;
}
NS_ABORT_IF_FALSE(GDK_IS_WINDOW(gdk_window), "must really be window");
// find the top-level window
gdk_window = gdk_window_get_toplevel(gdk_window);
NS_ABORT_IF_FALSE(gdk_window,
"gdk_window_get_toplevel should not return null");
*aWindow = gdk_window;
if (!aMouseEvent->widget) {
return false;
}
// FIXME: It would be nice to have the widget position at the time
// of the event, but it's relatively unlikely that the widget has
// moved since the mousedown. (On the other hand, it's quite likely
// that the mouse has moved, which is why we use the mouse position
// from the event.)
nsIntPoint offset = aMouseEvent->widget->WidgetToScreenOffset();
*aRootX = aMouseEvent->refPoint.x + offset.x;
*aRootY = aMouseEvent->refPoint.y + offset.y;
return true;
}
NS_IMETHODIMP
nsWindow::BeginMoveDrag(nsMouseEvent* aEvent)
{
NS_ABORT_IF_FALSE(aEvent, "must have event");
NS_ABORT_IF_FALSE(aEvent->eventStructType == NS_MOUSE_EVENT,
"event must have correct struct type");
GdkWindow *gdk_window;
gint button, screenX, screenY;
if (!GetDragInfo(aEvent, &gdk_window, &button, &screenX, &screenY)) {
return NS_ERROR_FAILURE;
}
// tell the window manager to start the move
gdk_window_begin_move_drag(gdk_window, button, screenX, screenY,
aEvent->time);
return NS_OK;
}
NS_IMETHODIMP
nsWindow::BeginResizeDrag(nsGUIEvent* aEvent, int32_t aHorizontal, int32_t aVertical)
{
NS_ENSURE_ARG_POINTER(aEvent);
if (aEvent->eventStructType != NS_MOUSE_EVENT) {
// you can only begin a resize drag with a mouse event
return NS_ERROR_INVALID_ARG;
}
nsMouseEvent* mouse_event = static_cast<nsMouseEvent*>(aEvent);
GdkWindow *gdk_window;
gint button, screenX, screenY;
if (!GetDragInfo(mouse_event, &gdk_window, &button, &screenX, &screenY)) {
return NS_ERROR_FAILURE;
}
// work out what GdkWindowEdge we're talking about
GdkWindowEdge window_edge;
if (aVertical < 0) {
if (aHorizontal < 0) {
window_edge = GDK_WINDOW_EDGE_NORTH_WEST;
} else if (aHorizontal == 0) {
window_edge = GDK_WINDOW_EDGE_NORTH;
} else {
window_edge = GDK_WINDOW_EDGE_NORTH_EAST;
}
} else if (aVertical == 0) {
if (aHorizontal < 0) {
window_edge = GDK_WINDOW_EDGE_WEST;
} else if (aHorizontal == 0) {
return NS_ERROR_INVALID_ARG;
} else {
window_edge = GDK_WINDOW_EDGE_EAST;
}
} else {
if (aHorizontal < 0) {
window_edge = GDK_WINDOW_EDGE_SOUTH_WEST;
} else if (aHorizontal == 0) {
window_edge = GDK_WINDOW_EDGE_SOUTH;
} else {
window_edge = GDK_WINDOW_EDGE_SOUTH_EAST;
}
}
// tell the window manager to start the resize
gdk_window_begin_resize_drag(gdk_window, window_edge, button,
screenX, screenY, aEvent->time);
return NS_OK;
}
nsIWidget::LayerManager*
nsWindow::GetLayerManager(PLayersChild* aShadowManager,
LayersBackend aBackendHint,
LayerManagerPersistence aPersistence,
bool* aAllowRetaining)
{
if (!mLayerManager && eTransparencyTransparent == GetTransparencyMode()) {
mLayerManager = CreateBasicLayerManager();
}
return nsBaseWidget::GetLayerManager(aShadowManager, aBackendHint,
aPersistence, aAllowRetaining);
}
void
nsWindow::ClearCachedResources()
{
if (mLayerManager &&
mLayerManager->GetBackendType() == mozilla::layers::LAYERS_BASIC) {
static_cast<mozilla::layers::BasicLayerManager*> (mLayerManager.get())->
ClearCachedResources();
}
GList* children = gdk_window_peek_children(mGdkWindow);
for (GList* list = children; list; list = list->next) {
nsWindow* window = get_window_for_gdk_window(GDK_WINDOW(list->data));
if (window) {
window->ClearCachedResources();
}
}
}
nsresult
nsWindow::SynthesizeNativeMouseEvent(nsIntPoint aPoint,
uint32_t aNativeMessage,
uint32_t aModifierFlags)
{
if (!mGdkWindow) {
return NS_OK;
}
GdkDisplay* display = gdk_window_get_display(mGdkWindow);
GdkScreen* screen = gdk_window_get_screen(mGdkWindow);
gdk_display_warp_pointer(display, screen, aPoint.x, aPoint.y);
return NS_OK;
}